import gleam/dynamic/decode import gleam/json import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import lustre import lustre/effect.{type Effect} import model.{ type Model, type Msg, AwaitPlayers, Empty, EnterPin, Initialize, KeyPin, Model, PickPlayer, Players, SelectedGamestyle, SelectedPlayer, SelectedRoom, } import plinth/browser/document import plinth/browser/element as plinth_element import rsvp import shared.{type Room} import view.{view} 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, None)) Nil } fn init(initial: #(List(Room), Option(String))) -> #(Model, Effect(Msg)) { let #(rooms, ohno) = initial let model = Model(rooms:, state: Empty, ohno:) #(model, effect.none()) } fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { case msg { Initialize -> init(#(model.rooms, None)) SelectedRoom(room) -> #( Model(..model, state: EnterPin(room:, pin: "")), effect.none(), ) KeyPin(pin) -> { //let _decode_answer = { // use id <- decode.field("id", decode.string) // use text <- decode.field("text", decode.string) // decode.success(id) // } // let decode_list = { // decode.list(decode.string) // } case model.state { EnterPin(room, _) -> #( Model(..model, state: case string.length(pin) < 4 { False -> model.SelectGamestyle(room:, pin:) True -> EnterPin(room:, pin:) }), effect.none(), ) _ -> init(#( model.rooms, Some("(fail: enterpin) Invalid state, starting over"), )) } } SelectedGamestyle(style) -> { case model.state { model.SelectGamestyle(room:, pin:) -> #( Model(..model, state: case style { "Single Game" -> PickPlayer(room:, pin:, players: [ "Player a", "Player b", "Player c", "Player d", "Player e", "Player f", ]) _ -> model.JoinLive(room:, pin:) }), effect.none(), ) _ -> init(#( model.rooms, Some("(fail: selectgamestyle) Invalid state, starting over"), )) } // Model(..model, state: AwaitPlayers(room:, pin:)), // rsvp.post( // "http://localhost:1234/api/room/players", // json.object([#("id", json.string("1234"))]), // rsvp.expect_json(decode_answer, Players), // ), // ) } Players(Ok(players)) -> { echo "got players" case model.state { AwaitPlayers(room:, pin:) -> #( Model(..model, state: PickPlayer(room:, pin:, players: [players])), effect.none(), ) _ -> init(#( model.rooms, Some("(fail: awaitplayers) Invalid state, starting over"), )) } } Players(Error(x)) -> init(#( model.rooms, Some("Error fetching players " <> decode_rsvp_error(x)), )) SelectedPlayer(player) -> case model.state { PickPlayer(room:, pin:, players: _) -> #( Model(..model, state: model.JoinSingle(room:, pin:, player:)), effect.none(), ) _ -> init(#( model.rooms, Some("(fail: pickplayer) Invalid state, starting over"), )) } } } fn decode_rsvp_error(rsvp_error: rsvp.Error) { case rsvp_error { rsvp.BadBody -> "Bad body" rsvp.BadUrl(_x) -> "Bad url" rsvp.HttpError(_x) -> "Http error" rsvp.JsonError(x) -> "Json error" <> decode_decode_error(x) rsvp.NetworkError -> "Network error" rsvp.UnhandledResponse(_) -> "Unhandled response" } } fn decode_decode_error(json_error: json.DecodeError) -> String { case json_error { json.UnableToDecode(x) -> "[unable to decode" <> string.concat( list.map(x, fn(x) { "(" <> decode_ddecode_error(x) <> ")" }), ) <> "]" json.UnexpectedSequence(s) -> "[unexpected sequence " <> s <> "]" json.UnexpectedByte(x) -> "[unexpected byte " <> x <> "]" json.UnexpectedEndOfInput -> "[unexpected end of input]" } } fn decode_ddecode_error(decode_error: decode.DecodeError) -> String { case decode_error { decode.DecodeError(expected:, found:, path:) -> "(e: " <> expected <> ", f: " <> found <> ", p: " <> string.concat(path) } }