2026-04-05 17:12:08 +02:00
|
|
|
import gleam/dynamic/decode
|
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
|
|
|
|
|
import lustre/effect.{type Effect}
|
2026-04-06 14:03:33 +02:00
|
|
|
import model.{
|
|
|
|
|
type Model, type Msg, AwaitPlayers, Empty, EnterPin, Initialize, KeyPin, Model,
|
|
|
|
|
PickPlayer, Players, SelectedGamestyle, SelectedPlayer, SelectedRoom,
|
|
|
|
|
}
|
2026-03-29 13:53:16 +02:00
|
|
|
import plinth/browser/document
|
|
|
|
|
import plinth/browser/element as plinth_element
|
2026-04-05 17:12:08 +02:00
|
|
|
import rsvp
|
2026-03-30 23:49:20 +02:00
|
|
|
import shared.{type Room}
|
2026-04-06 14:03:33 +02:00
|
|
|
import view.{view}
|
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)
|
2026-04-05 17:12:08 +02:00
|
|
|
let assert Ok(_) = lustre.start(app, "#app", #(initial_items, None))
|
2026-03-29 13:53:16 +02:00
|
|
|
|
|
|
|
|
Nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 17:12:08 +02:00
|
|
|
fn init(initial: #(List(Room), Option(String))) -> #(Model, Effect(Msg)) {
|
|
|
|
|
let #(rooms, ohno) = initial
|
|
|
|
|
let model = Model(rooms:, state: Empty, ohno:)
|
2026-03-29 13:53:16 +02:00
|
|
|
|
|
|
|
|
#(model, effect.none())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 17:12:08 +02:00
|
|
|
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
|
2026-03-29 13:53:16 +02:00
|
|
|
case msg {
|
2026-04-05 17:12:08 +02:00
|
|
|
Initialize -> init(#(model.rooms, None))
|
2026-04-06 14:03:33 +02:00
|
|
|
SelectedRoom(room) -> #(
|
2026-04-05 17:12:08 +02:00
|
|
|
Model(..model, state: EnterPin(room:, pin: "")),
|
|
|
|
|
effect.none(),
|
|
|
|
|
)
|
2026-04-06 14:03:33 +02:00
|
|
|
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)
|
|
|
|
|
// }
|
2026-04-05 17:12:08 +02:00
|
|
|
case model.state {
|
2026-04-06 14:03:33 +02:00
|
|
|
EnterPin(room, _) -> #(
|
|
|
|
|
Model(..model, state: case string.length(pin) < 4 {
|
|
|
|
|
False -> model.SelectGamestyle(room:, pin:)
|
|
|
|
|
True -> EnterPin(room:, pin:)
|
|
|
|
|
}),
|
|
|
|
|
effect.none(),
|
|
|
|
|
)
|
2026-04-05 17:12:08 +02:00
|
|
|
_ ->
|
2026-04-06 14:03:33 +02:00
|
|
|
init(#(
|
|
|
|
|
model.rooms,
|
|
|
|
|
Some("(fail: enterpin) Invalid state, starting over"),
|
|
|
|
|
))
|
2026-03-30 23:49:20 +02:00
|
|
|
}
|
2026-04-06 14:03:33 +02:00
|
|
|
}
|
|
|
|
|
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),
|
|
|
|
|
// ),
|
|
|
|
|
// )
|
|
|
|
|
}
|
2026-04-05 17:12:08 +02:00
|
|
|
Players(Ok(players)) -> {
|
|
|
|
|
echo "got players"
|
|
|
|
|
case model.state {
|
|
|
|
|
AwaitPlayers(room:, pin:) -> #(
|
|
|
|
|
Model(..model, state: PickPlayer(room:, pin:, players: [players])),
|
|
|
|
|
effect.none(),
|
|
|
|
|
)
|
2026-04-06 14:03:33 +02:00
|
|
|
_ ->
|
|
|
|
|
init(#(
|
|
|
|
|
model.rooms,
|
|
|
|
|
Some("(fail: awaitplayers) Invalid state, starting over"),
|
|
|
|
|
))
|
2026-03-29 13:53:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-05 17:12:08 +02:00
|
|
|
Players(Error(x)) ->
|
|
|
|
|
init(#(
|
|
|
|
|
model.rooms,
|
|
|
|
|
Some("Error fetching players " <> decode_rsvp_error(x)),
|
|
|
|
|
))
|
2026-04-06 14:03:33 +02:00
|
|
|
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"),
|
|
|
|
|
))
|
|
|
|
|
}
|
2026-03-30 23:49:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-29 13:53:16 +02:00
|
|
|
|
2026-04-05 17:12:08 +02:00
|
|
|
fn decode_rsvp_error(rsvp_error: rsvp.Error) {
|
|
|
|
|
case rsvp_error {
|
|
|
|
|
rsvp.BadBody -> "Bad body"
|
2026-04-06 14:03:33 +02:00
|
|
|
rsvp.BadUrl(_x) -> "Bad url"
|
|
|
|
|
rsvp.HttpError(_x) -> "Http error"
|
2026-04-05 17:12:08 +02:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|