Answer handling yay

This commit is contained in:
Lett Osprey 2026-04-13 20:49:57 +02:00
parent f3020b7cb0
commit 1a410072c2
9 changed files with 79 additions and 42 deletions

View file

@ -62,18 +62,21 @@ fn view_room_list(items: List(Room)) -> Element(Msg) {
fn view_enter_pin() -> Element(Msg) {
layout("Enter PIN code for room", None, [
html.div([class("participant-hidden")], []),
input_cell("[#ENTER PIN]", True, KeyPin),
html.div([class("participant-hidden")], []),
])
}
fn view_join_live(room: String, pin: String) -> Element(Msg) {
element.fragment([
server_component.element(
[server_component.route("/socket/live/" <> room)],
[server_component.route("/socket/live/" <> room <> "/" <> pin)],
[],
),
server_component.element(
[server_component.route("/socket/control/" <> room)],
[server_component.route("/socket/control/" <> room <> "/" <> pin)],
[],
),
])
@ -81,7 +84,7 @@ fn view_join_live(room: String, pin: String) -> Element(Msg) {
fn view_join_single(room: String, pin: String) -> Element(Msg) {
server_component.element(
[server_component.route("/socket/single/" <> room)],
[server_component.route("/socket/single/" <> room <> "/" <> pin)],
[],
)
}
@ -98,7 +101,7 @@ fn input_cell(
password: Bool,
on_input: fn(String) -> Msg,
) -> Element(Msg) {
html.div([class("participant-login")], [
html.div([class("participant-box")], [
html.div([class("participant-name")], [
html.p([], [html.text("" <> header)]),
html.div([], [

View file

@ -136,7 +136,7 @@ body {
}
.participant-hidden {
border: 0px dashed #005500;
border: none;
padding: 1rem;
color: #000000;
transition: all 0.2s;

View file

@ -112,16 +112,10 @@ fn add_player(name: String, players: List(#(String, #(String, List(#(_, _))))))
}
fn fetch_players(
players: List(#(String, #(String, List(#(_, String))))),
subject: Subject(List(#(String, String))),
players: List(#(String, #(String, List(#(String, String))))),
subject: Subject(List(#(String, #(String, List(#(String, String)))))),
) {
actor.send(
subject,
list.map(players, fn(player) {
let #(id, #(name, _)) = player
#(id, name)
}),
)
actor.send(subject, players)
}
// Reschedule a new ping request, and ask clients to ping us back

View file

@ -1,8 +1,11 @@
import backend/playerhandler as player_handler
import gleam/bit_array
import gleam/crypto
import gleam/erlang/process.{type Subject}
import gleam/list
import gleam/option.{Some}
import gleam/option.{None, Some}
import gleam/otp/actor.{type Started}
import gleam/string
import group_registry
import shared/message.{
type Room, type RoomControl, type StateControl, CreateRoom, FetchRoom,
@ -56,12 +59,22 @@ pub fn initialize(state_handler: Started(Subject(StateControl))) {
Ok(_) -> state
}
}
FetchRoom(id:, subject:) -> {
FetchRoom(id:, pin:, subject:) -> {
case
// Find the room, if it exists
state.rooms |> list.key_find(id)
{
Ok(Room(_, _, actors)) -> actor.send(subject, Some(actors))
Ok(Room(_, pin_enc, actors)) -> {
case
string.uppercase(pin_enc)
== bit_array.base16_encode(
crypto.hash(crypto.Sha256, <<pin:utf8>>),
)
{
True -> actor.send(subject, Some(actors))
False -> actor.send(subject, None)
}
}
_ -> actor.send(subject, option.None)
}
state

View file

@ -14,9 +14,10 @@ pub fn serve(
request: Request(Connection),
component: lustre.App(message.ClientsServer, model, msg),
id: String,
pin: String,
actor: actor.Started(Subject(message.RoomControl)),
) -> Response(ResponseData) {
let start_args = actor.call(actor.data, 1000, message.FetchRoom(id, _))
let start_args = actor.call(actor.data, 1000, message.FetchRoom(id, pin, _))
case start_args {
Some(start_args) ->
mist.websocket(
@ -35,13 +36,18 @@ pub fn serve(
pub fn serve_slow(
request: Request(Connection),
component: lustre.App(#(List(#(String, String)), message.ClientsServer), model, msg),
component: lustre.App(
#(List(#(String, String)), message.ClientsServer),
model,
msg,
),
id: String,
pin: String,
roomhandler: actor.Started(Subject(message.RoomControl)),
statehandler: actor.Started(Subject(message.StateControl)),
) -> Response(ResponseData) {
let start_args_opt =
actor.call(roomhandler.data, 1000, message.FetchRoom(id, _))
actor.call(roomhandler.data, 1000, message.FetchRoom(id, pin, _))
let answer_list = actor.call(statehandler.data, 1000, message.FetchQuestions)
case start_args_opt {

View file

@ -37,15 +37,22 @@ pub fn main() {
["lustre", "runtime.mjs"] -> serve_runtime()
["client.js"] -> serve_static("client.js")
["static", file] -> serve_static(file)
["socket", "live", id] ->
sockethandler.serve(req, card.component(), id, room_handler)
["socket", "control", id] ->
sockethandler.serve(req, control.component(), id, room_handler)
["socket", "single", id] ->
["socket", "live", id, pin] ->
sockethandler.serve(req, card.component(), id, pin, room_handler)
["socket", "control", id, pin] ->
sockethandler.serve(
req,
control.component(),
id,
pin,
room_handler,
)
["socket", "single", id, pin] ->
sockethandler.serve_slow(
req,
answerlist.component(),
id,
pin,
room_handler,
state_handler,
)

View file

@ -15,7 +15,7 @@ pub type NotifyServer {
GiveName(name: String)
GiveAnswer(name: String, answer: Option(String))
GiveSingleAnswer(id: String, question: String, answer: String)
FetchPlayers(subject: Subject(List(#(String, String))))
FetchPlayers(subject: Subject(List(#(String, #(String, List(#(String, String)))))))
AddPlayer(String)
}
@ -37,7 +37,7 @@ pub type RoomInfo {
pub type RoomControl {
CreateRoom(id: String, room: RoomInfo)
FetchRoom(id: String, subject: Subject(Option(ClientsServer)))
FetchRoom(id: String, pin: String, subject: Subject(Option(ClientsServer)))
FetchRooms(subject: Subject(List(#(String, RoomInfo))))
}

View file

@ -29,13 +29,24 @@ pub fn component() -> lustre.App(
pub opaque type Model {
Model(
state: Msg,
players: List(#(String, String)),
players: List(#(String, #(String, List(#(String, String))))),
player: Option(#(String, String)),
answers: List(#(String, #(String, String))),
handler: Started(Subject(NotifyServer)),
)
}
pub opaque type Msg {
Initial
PickedPlayer(player: Option(#(String, String)))
SharedMessage(message: NotifyClient)
ReceiveName(name: Option(String))
AcceptPlayer(accept: Option(#(String, String)))
PickQuestion
PickedQuestion(question: Option(#(String, String)))
GiveAnswer(question: #(String, String), answer: Option(String))
}
fn init(
start_args: #(List(#(String, String)), message.ClientsServer),
) -> #(Model, Effect(Msg)) {
@ -63,17 +74,6 @@ fn init(
)
}
pub opaque type Msg {
Initial
PickedPlayer(player: Option(#(String, String)))
SharedMessage(message: NotifyClient)
ReceiveName(name: Option(String))
AcceptPlayer(accept: Option(#(String, String)))
PickQuestion
PickedQuestion(question: Option(#(String, String)))
GiveAnswer(question: #(String, String), answer: Option(String))
}
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
case msg {
Initial -> #(Model(..model, state: msg), effect.none())
@ -168,7 +168,14 @@ fn view(model: Model) -> Element(Msg) {
Initial ->
case model.players {
[] -> shared.input_new_player(ReceiveName)
_ -> shared.view_players(model.players, PickedPlayer)
_ ->
shared.view_players(
list.map(model.players, fn(player) {
let #(id, #(name, _)) = player
#(id, name)
}),
PickedPlayer,
)
}
PickQuestion -> view_questions(model.answers)
ReceiveName(_) -> shared.input_new_player(ReceiveName)

View file

@ -29,7 +29,7 @@ type State {
pub opaque type Model {
Model(
state: State,
players: List(#(String, String)),
players: List(#(String, #(String, List(#(String, String))))),
lobby: #(String, List(User)),
registry: GroupRegistry(NotifyClient),
handler: Started(Subject(NotifyServer)),
@ -137,7 +137,14 @@ fn view(model: Model) -> Element(Msg) {
AskName ->
case model.players {
[] -> input_new_player(ReceiveName)
_ -> view_players(model.players, AcceptName)
_ ->
view_players(
list.map(model.players, fn(player) {
let #(id, #(name, _)) = player
#(id, name)
}),
AcceptName,
)
}
NameOk(name) -> {
shared.confirm_cells(