quizterm/client/src/view.gleam

142 lines
3.7 KiB
Gleam
Raw Normal View History

2026-04-06 14:03:33 +02:00
import gleam/int
import gleam/list
import gleam/option.{None, Some}
import lustre/attribute.{class}
import lustre/element.{type Element}
import lustre/element/html
import lustre/event
import lustre/server_component
import model.{
type Model, type Msg, type Room, Empty, EnterPin, JoinLive, JoinSingle, KeyPin,
SelectGamestyle, SelectedRoom,
2026-04-06 14:03:33 +02:00
}
pub fn view(model: Model) -> Element(Msg) {
case model.state {
Empty -> view_room_list(model.rooms)
EnterPin(_, _) -> view_enter_pin()
SelectGamestyle(_, _) -> view_live_or_single()
JoinLive(room:, pin:) -> view_join_live(room, pin)
JoinSingle(room:, pin:) -> view_join_single(room, pin)
2026-04-06 14:03:33 +02:00
}
}
fn layout(header: String, ohno: option.Option(String), body: List(Element(Msg))) {
html.div([], [
html.div([class("terminal-header")], [
html.div([class("terminal-status")], [
html.span([class("status-blink")], [html.text("")]),
html.div([], [
html.text(" SYSTEM READY"),
]),
html.div([], [
case ohno {
None -> element.none()
Some(x) -> html.h3([], [html.text("Fail: " <> x)])
},
]),
html.span([class("ml-8")], [
html.text("<< Please Log On to use QuizTerm. >>"),
]),
]),
]),
html.div([attribute.class("terminal-section")], [
html.div([attribute.class("terminal-label mb-4")], [
html.text(header),
]),
html.div([attribute.class("participants-grid")], body),
]),
])
}
fn view_room_list(items: List(Room)) -> Element(Msg) {
layout("Select room to play in", None, case items {
2026-04-13 12:17:50 +02:00
[] -> [html.text("No rooms exist, nowhere to play! (ohno!)")]
2026-04-06 14:03:33 +02:00
_ -> {
list.index_map(items, fn(item, index) {
room_cell(index, item, SelectedRoom)
})
}
})
}
fn view_enter_pin() -> Element(Msg) {
layout("Enter PIN code for room", None, [
input_cell("[#ENTER PIN]", True, KeyPin),
2026-04-06 14:03:33 +02:00
])
}
fn view_join_live(room: String, pin: String) -> Element(Msg) {
element.fragment([
server_component.element(
[server_component.route("/socket/live/" <> room)],
[],
),
server_component.element(
[server_component.route("/socket/control/" <> room)],
[],
),
2026-04-06 14:03:33 +02:00
])
}
fn view_join_single(room: String, pin: String) -> Element(Msg) {
server_component.element(
[server_component.route("/socket/single/" <> room)],
[],
)
2026-04-06 14:03:33 +02:00
}
fn view_live_or_single() -> Element(Msg) {
layout("Select type of play", None, [
click_cell(1, "Live Game", model.SelectedGamestyle),
click_cell(2, "Single Game", model.SelectedGamestyle),
])
}
fn input_cell(
header: String,
password: Bool,
on_input: fn(String) -> Msg,
) -> Element(Msg) {
html.div([class("participant-login")], [
html.div([class("participant-name")], [
2026-04-13 12:17:50 +02:00
html.p([], [html.text("" <> header)]),
html.div([], [
html.input([
attribute.type_(case password {
True -> "password"
False -> "text"
}),
event.on_input(on_input),
attribute.autofocus(True),
]),
]),
]),
])
2026-04-06 14:03:33 +02:00
}
fn click_cell(
number: Int,
player: String,
on_click: fn(String) -> msg,
) -> Element(msg) {
html.div([class("participant-login"), event.on_click(on_click(player))], [
html.div([class("participant-name")], [
html.text("" <> "[#" <> int.to_string(number) <> "] " <> player),
]),
])
}
2026-04-11 14:11:11 +02:00
// TODO: merge with shared.click_cell
2026-04-06 14:03:33 +02:00
fn room_cell(
number: Int,
room: Room,
on_click: fn(String) -> msg,
) -> Element(msg) {
html.div([class("participant-login"), event.on_click(on_click(room.id))], [
html.div([class("participant-name")], [
html.text("" <> "[#" <> int.to_string(number) <> "] Team " <> room.name),
]),
])
}