quizterm/client/src/view.gleam

143 lines
4 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, AwaitPlayers, Empty, EnterPin, JoinLive, JoinSingle,
KeyPin, PickPlayer, SelectGamestyle, SelectedPlayer, SelectedRoom,
}
import shared.{type Room}
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()
AwaitPlayers(_, _) -> html.text("FETCHING USERS FOR ROOM")
PickPlayer(_, _, players) -> view_player_list(players)
JoinLive(room:, pin:) -> view_join_live(room, pin)
JoinSingle(room:, pin:, player:) -> view_join_single(room, pin, player)
}
}
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 {
[] -> [html.text("No items in your list yet.")]
_ -> {
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, [
html.input([
attribute.type_("password"),
event.on_input(KeyPin),
attribute.autofocus(True),
]),
])
}
fn view_join_live(room: String, pin: String) -> Element(Msg) {
html.div([attribute.class("terminal-section")], [
html.div([attribute.class("terminal-label mb-4")], [
server_component.element(
[server_component.route("/socket/live/" <> room)],
[],
),
server_component.element(
[server_component.route("/socket/control/" <> room)],
[],
),
]),
])
}
fn view_join_single(room: String, pin: String, player: String) -> Element(Msg) {
html.div([attribute.class("terminal-section")], [
html.div([attribute.class("terminal-label mb-4")], [
server_component.element(
[server_component.route("/socket/single/" <> room)],
[],
),
]),
])
}
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 view_player_list(items: List(String)) -> Element(Msg) {
layout("Select or enter your player", None, case items {
[] -> [html.text("No items in your list yet.")]
_ -> {
list.index_map(items, fn(item, index) {
click_cell(index, item, SelectedPlayer)
})
}
})
}
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),
]),
])
}
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),
]),
])
}