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.{
|
2026-04-10 19:36:28 +02:00
|
|
|
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)
|
2026-04-10 19:36:28 +02:00
|
|
|
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 {
|
|
|
|
|
[] -> [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, [
|
2026-04-10 19:36:28 +02:00
|
|
|
input_cell("[#ENTER PIN]", True, KeyPin),
|
2026-04-06 14:03:33 +02:00
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn view_join_live(room: String, pin: String) -> Element(Msg) {
|
2026-04-10 19:36:28 +02:00
|
|
|
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
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 19:36:28 +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),
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 19:36:28 +02:00
|
|
|
|
|
|
|
|
fn input_cell(
|
|
|
|
|
header: String,
|
|
|
|
|
password: Bool,
|
|
|
|
|
on_input: fn(String) -> Msg,
|
|
|
|
|
) -> Element(Msg) {
|
|
|
|
|
html.div([class("participant-login")], [
|
|
|
|
|
html.div([class("participant-name")], [
|
|
|
|
|
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),
|
|
|
|
|
]),
|
|
|
|
|
])
|
|
|
|
|
}
|