import gleam/int import gleam/json import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import lustre import lustre/attribute.{class} import lustre/effect.{type Effect} import lustre/element.{type Element} import lustre/element/html import lustre/event import plinth/browser/document import plinth/browser/element as plinth_element import shared.{type Room} 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 { Model( rooms: List(Room), name: Option(String), pin: Option(String), typed: String, ) } fn init(items: List(Room)) -> #(Model, Effect(State)) { let model = Model(rooms: items, name: None, pin: None, typed: "") #(model, effect.none()) } type State { SelectRoom(String) Initial KeyIn(String) } fn update(model: Model, msg: State) -> #(Model, Effect(msg)) { case msg { Initial -> case model.name { _ -> #(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()) } } } } 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"), ]) } } } 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), ]), ]), ]) } 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) }) } }), ]) } 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), ]), ]) }