2026-03-30 23:49:20 +02:00
|
|
|
import gleam/int
|
2026-03-29 13:53:16 +02:00
|
|
|
import gleam/json
|
|
|
|
|
import gleam/list
|
2026-03-30 23:49:20 +02:00
|
|
|
import gleam/option.{type Option, None, Some}
|
2026-03-29 13:53:16 +02:00
|
|
|
import gleam/result
|
2026-03-30 23:49:20 +02:00
|
|
|
import gleam/string
|
2026-03-29 13:53:16 +02:00
|
|
|
import lustre
|
2026-03-30 23:49:20 +02:00
|
|
|
import lustre/attribute.{class}
|
2026-03-29 13:53:16 +02:00
|
|
|
import lustre/effect.{type Effect}
|
|
|
|
|
import lustre/element.{type Element}
|
|
|
|
|
import lustre/element/html
|
2026-03-30 23:49:20 +02:00
|
|
|
import lustre/event
|
2026-03-29 13:53:16 +02:00
|
|
|
import plinth/browser/document
|
|
|
|
|
import plinth/browser/element as plinth_element
|
2026-03-30 23:49:20 +02:00
|
|
|
import shared.{type Room}
|
2026-03-29 13:53:16 +02:00
|
|
|
|
|
|
|
|
pub fn main() {
|
|
|
|
|
let initial_items =
|
|
|
|
|
document.query_selector("#model")
|
|
|
|
|
|> result.map(plinth_element.inner_text)
|
|
|
|
|
|> result.try(fn(json) {
|
|
|
|
|
json.parse(json, shared.grocery_list_decoder())
|
|
|
|
|
|> result.replace_error(Nil)
|
|
|
|
|
})
|
|
|
|
|
|> result.unwrap([])
|
|
|
|
|
|
|
|
|
|
let app = lustre.application(init, update, view)
|
|
|
|
|
let assert Ok(_) = lustre.start(app, "#app", initial_items)
|
|
|
|
|
|
|
|
|
|
Nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Model {
|
2026-03-30 23:49:20 +02:00
|
|
|
Model(
|
|
|
|
|
rooms: List(Room),
|
|
|
|
|
name: Option(String),
|
|
|
|
|
pin: Option(String),
|
|
|
|
|
typed: String,
|
|
|
|
|
)
|
2026-03-29 13:53:16 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 23:49:20 +02:00
|
|
|
fn init(items: List(Room)) -> #(Model, Effect(State)) {
|
|
|
|
|
let model = Model(rooms: items, name: None, pin: None, typed: "")
|
2026-03-29 13:53:16 +02:00
|
|
|
|
|
|
|
|
#(model, effect.none())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 23:49:20 +02:00
|
|
|
type State {
|
|
|
|
|
SelectRoom(String)
|
|
|
|
|
Initial
|
|
|
|
|
KeyIn(String)
|
2026-03-29 13:53:16 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 23:49:20 +02:00
|
|
|
fn update(model: Model, msg: State) -> #(Model, Effect(msg)) {
|
2026-03-29 13:53:16 +02:00
|
|
|
case msg {
|
2026-03-30 23:49:20 +02:00
|
|
|
Initial ->
|
2026-03-29 13:53:16 +02:00
|
|
|
case model.name {
|
2026-03-30 23:49:20 +02:00
|
|
|
_ -> #(model, effect.none())
|
|
|
|
|
}
|
|
|
|
|
SelectRoom(text) -> {
|
|
|
|
|
#(Model(..model, name: Some(text)), effect.none())
|
|
|
|
|
}
|
|
|
|
|
KeyIn(newpin) -> {
|
|
|
|
|
case string.length(newpin) < 4 {
|
|
|
|
|
False -> #(Model(..model, pin: Some(newpin)), effect.none())
|
|
|
|
|
True -> #(Model(..model, typed: newpin), effect.none())
|
2026-03-29 13:53:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-30 23:49:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-29 13:53:16 +02:00
|
|
|
|
2026-03-30 23:49:20 +02:00
|
|
|
fn view(model: Model) -> Element(State) {
|
|
|
|
|
case model.name, model.pin {
|
|
|
|
|
None, _ ->
|
|
|
|
|
html.div([], [
|
|
|
|
|
html.div([class("terminal-header")], [
|
|
|
|
|
html.div([class("terminal-status")], [
|
|
|
|
|
html.span([class("status-blink")], [html.text("●")]),
|
|
|
|
|
html.text(" SYSTEM READY"),
|
|
|
|
|
html.span([class("ml-8")], [
|
|
|
|
|
html.text("<< Please Log On to use QuizTerm. >>"),
|
|
|
|
|
]),
|
|
|
|
|
]),
|
|
|
|
|
]),
|
|
|
|
|
view_room_list(model.rooms),
|
|
|
|
|
])
|
|
|
|
|
Some(_), None -> {
|
|
|
|
|
html.div([], [
|
|
|
|
|
html.div([class("terminal-header")], [
|
|
|
|
|
html.div([class("terminal-status")], [
|
|
|
|
|
html.span([class("status-blink")], [html.text("●")]),
|
|
|
|
|
html.text(" SYSTEM READY"),
|
|
|
|
|
html.span([class("ml-8")], [
|
|
|
|
|
html.text("<< Please Log On to use QuizTerm. >>"),
|
|
|
|
|
]),
|
|
|
|
|
]),
|
|
|
|
|
]),
|
|
|
|
|
pin(),
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
Some(_), Some(_) -> {
|
|
|
|
|
html.div([], [
|
|
|
|
|
html.div([class("terminal-header")], [
|
|
|
|
|
html.div([class("terminal-status")], [
|
|
|
|
|
html.span([class("status-blink")], [html.text("●")]),
|
|
|
|
|
html.text(" SYSTEM READY"),
|
|
|
|
|
html.span([class("ml-8")], [
|
|
|
|
|
html.text("<< Please Log On to use QuizTerm. >>"),
|
|
|
|
|
]),
|
|
|
|
|
]),
|
|
|
|
|
]),
|
|
|
|
|
html.text("FETCHING USERS FOR ROOM"),
|
|
|
|
|
])
|
|
|
|
|
}
|
2026-03-29 13:53:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 23:49:20 +02:00
|
|
|
fn pin() -> Element(State) {
|
|
|
|
|
html.div([attribute.class("terminal-section")], [
|
|
|
|
|
html.div([attribute.class("terminal-label mb-4")], [
|
|
|
|
|
html.text("Select room to play in"),
|
|
|
|
|
]),
|
|
|
|
|
html.div([attribute.class("participants-grid")], [
|
|
|
|
|
html.text("Enter PIN code for room"),
|
|
|
|
|
html.input([
|
|
|
|
|
attribute.type_("password"),
|
|
|
|
|
event.on_input(KeyIn),
|
|
|
|
|
attribute.autofocus(True),
|
|
|
|
|
]),
|
|
|
|
|
]),
|
|
|
|
|
])
|
|
|
|
|
}
|
2026-03-29 13:53:16 +02:00
|
|
|
|
2026-03-30 23:49:20 +02:00
|
|
|
fn view_room_list(items: List(Room)) -> Element(State) {
|
|
|
|
|
html.div([attribute.class("terminal-section")], [
|
|
|
|
|
html.div([attribute.class("terminal-label mb-4")], [
|
|
|
|
|
html.text("Select room to play in"),
|
|
|
|
|
]),
|
|
|
|
|
html.div([attribute.class("participants-grid")], case items {
|
|
|
|
|
[] -> [html.text("No items in your list yet.")]
|
|
|
|
|
_ -> {
|
|
|
|
|
list.index_map(items, fn(item, index) {
|
|
|
|
|
content_cell(index, item, SelectRoom)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}),
|
2026-03-29 13:53:16 +02:00
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 23:49:20 +02:00
|
|
|
fn content_cell(
|
|
|
|
|
number: Int,
|
|
|
|
|
room: Room,
|
|
|
|
|
on_click: fn(String) -> msg,
|
|
|
|
|
) -> Element(msg) {
|
|
|
|
|
html.div([class("participant-login"), event.on_click(on_click(room.name))], [
|
|
|
|
|
html.div([class("participant-name")], [
|
|
|
|
|
html.text("► " <> "[#" <> int.to_string(number) <> "] Team " <> room.name),
|
|
|
|
|
]),
|
|
|
|
|
])
|
2026-03-29 13:53:16 +02:00
|
|
|
}
|