even more changes

main
parent 2c84fc74b4
commit 2ad01a11a9

@ -1,39 +1,79 @@
import { requestJSON } from '@/client/utils'
import type { RoomData } from '@/db/model'
import type { ActionAnswer, ActionJolly } from '@/ggwp'
import { useEffect, useState } from 'preact/hooks'
import { requestJSON, sendJSON } from '@/client/utils'
import type { Room, RoomData } from '@/db/model'
import type { Action, ActionAnswer, ActionJolly } from '@/ggwp'
import { useEffect, useState, type Dispatch, type StateUpdater } from 'preact/hooks'
type ActionCardProps = {
moveUp: () => void
moveDown: () => void
remove: () => void
// editing: boolean
// edit: () => void
// doneEdit: (newAction: Action) => void
}
type Props = {
roomId: string
room: Room
refreshRoom: () => void
}
const ActionCardAnswer = ({ action }: { action: ActionAnswer }) => {
const CardActions = ({ moveUp, moveDown, remove }: ActionCardProps) => (
<>
<button class="square" onClick={moveUp}>
<div class="icon">arrow_upward</div>
</button>
<button class="square" onClick={moveDown}>
<div class="icon">arrow_downward</div>
</button>
{/* <button class="square"
onClick={edit}
>
<div class="icon">edit</div>
</button> */}
<button class="square" onClick={remove}>
<div class="icon">delete</div>
</button>
</>
)
const ActionCardAnswer = ({ action, ...props }: { action: ActionAnswer } & ActionCardProps) => {
return (
<div class="card tint-blue stack-h center">
<strong>{action.question}</strong>
<div>{action.team}</div>
<div>{action.outcome}</div>
<div class="chip">Risposta</div>
<div>
<strong>Team:</strong> <code>{action.team}</code>
</div>
<div>
<strong>Domanda:</strong> <code>{action.question}</code>
</div>
<div>
<strong>Risultato:</strong> <code>{action.outcome}</code>
</div>
<div class="fill"></div>
<CardActions {...props} />
</div>
)
}
const ActionCardJolly = ({ action }: { action: ActionJolly }) => {
const ActionCardJolly = ({ action, ...props }: { action: ActionJolly } & ActionCardProps) => {
return (
<div class="card tint-blue stack-h center">
<strong>{action.team}</strong>
<div>{action.groupId}</div>
<div class="chip">Jolly</div>
<div>
<strong>Team:</strong> {action.team}
</div>
<div>
<strong>Problem:</strong> {action.group}
</div>
<div class="fill"></div>
<CardActions {...props} />
</div>
)
}
export const ActionRegistry = ({ roomId }: Props) => {
const [room, setRoom] = useState<RoomData | null>(null)
useEffect(() => {
requestJSON(`/api/room/${roomId}`).then(room => {
setRoom(room)
})
}, [])
export const ActionRegistry = ({ room, refreshRoom }: Props) => {
if (!room) {
return <div>Loading...</div>
}
@ -42,13 +82,84 @@ export const ActionRegistry = ({ roomId }: Props) => {
return <p>Ancora nessuna azione</p>
}
// const [editing, setEditing] = useState<number | null>(null)
const setActions: Dispatch<StateUpdater<Action[]>> = async actions => {
const newActions = typeof actions === 'function' ? actions(room.actions) : actions
await sendJSON(`/api/room/${room.id}`, { ...room, actions: newActions })
refreshRoom()
}
const moveActionUp = (index: number) => {
if (index === 0) return
setActions(actions => {
const newActions = [...actions]
const [action] = newActions.splice(index, 1)
newActions.splice(index - 1, 0, action)
return newActions
})
}
const moveActionDown = (index: number) => {
if (index === room.actions.length - 1) return
setActions(actions => {
const newActions = [...actions]
const [action] = newActions.splice(index, 1)
newActions.splice(index + 1, 0, action)
return newActions
})
}
// const editAction = (index: number) => {
// setEditing(index)
// }
// const doneEdit = (newAction: Action) => {
// if (editing === null) return
// setActions(actions => {
// const newActions = [...actions]
// newActions[editing] = newAction
// return newActions
// })
// setEditing(null)
// }
const removeAction = (index: number) => {
setActions(actions => {
const newActions = [...actions]
newActions.splice(index, 1)
return newActions
})
}
return (
<>
{room.actions.map(action =>
{room.actions.map((action, index) =>
action.type === 'answer' ? (
<ActionCardAnswer action={action} />
<ActionCardAnswer
action={action}
moveUp={() => moveActionUp(index)}
moveDown={() => moveActionDown(index)}
remove={() => removeAction(index)}
// editing={editing === index}
// edit={() => editAction(index)}
// doneEdit={doneEdit}
/>
) : action.type === 'jolly' ? (
<ActionCardJolly action={action} />
<ActionCardJolly
action={action}
moveUp={() => moveActionUp(index)}
moveDown={() => moveActionDown(index)}
remove={() => removeAction(index)}
// editing={editing === index}
// edit={() => editAction(index)}
// doneEdit={doneEdit}
/>
) : (
<div class="card tint-red">
Unknown action type: <code>{JSON.stringify(action)}</code>

@ -1,27 +1,59 @@
import { LiveLeaderboard } from '@/components/LiveLeaderboard'
import { SubmitAction } from '@/components/SubmitAction'
import { ActionRegistry } from '@/components/ActionRegistry'
import { useRef, useState } from 'preact/hooks'
import { useEffect, useRef, useState } from 'preact/hooks'
import type { Room } from '@/db/model'
import { requestJSON } from '@/client/utils'
import { computeScoreboardState } from '@/ggwp'
import { Leaderboard } from './Leaderboard'
export const AdminPage = ({ room }: { room: string }) => {
export const AdminPage = ({ roomId }: { roomId: string }) => {
const listeners = useRef<Record<string, (teamQuestion: { team: string; question: string }) => void>>({})
const [room, setRoom] = useState<Room | null>(null)
const fetchRoom = async () => {
await requestJSON(`/api/room/${roomId}`).then(room => {
setRoom({ id: roomId, ...room })
})
}
useEffect(() => {
fetchRoom()
}, [])
if (!room) {
return <div>Loading...</div>
}
const scoreboard = computeScoreboardState(
{
teamIds: room.teams,
questions: room.questions,
},
room.actions
)
return (
<>
<h1>Admin</h1>
<h2>{room}</h2>
<LiveLeaderboard
roomId={room}
<h2>{room.id}</h2>
<Leaderboard
questions={room.questions}
scoreboard={scoreboard}
selectTeamQuestion={teamQuestion =>
Object.values(listeners.current).forEach(listener => listener(teamQuestion))
}
/>
&nbsp;
<h2>Azioni</h2>
<SubmitAction roomId={room} onTeamQuestionIndex={(key, listener) => (listeners.current[key] = listener)} />
<SubmitAction
room={room}
refreshRoom={fetchRoom}
onTeamQuestionIndex={(key, listener) => (listeners.current[key] = listener)}
/>
&nbsp;
<h2>Registro Azioni</h2>
<ActionRegistry roomId={room} />
<ActionRegistry room={room} refreshRoom={fetchRoom} />
</>
)
}

@ -1,35 +1,17 @@
import type { RoomData } from '@/db/model'
import type { Room } from '@/db/model'
import { Leaderboard } from './Leaderboard'
import { useEffect, useState } from 'preact/hooks'
import { requestJSON, sendJSON } from '@/client/utils'
import { type Dispatch, type StateUpdater } from 'preact/hooks'
import { computeScoreboardState } from '@/ggwp'
type Props = {
roomId: string
room: Room
setRoom: Dispatch<StateUpdater<Room>>
selectTeamQuestion?: ({ team, question }: { team: string; question: string }) => void
}
export const LiveLeaderboard = ({ roomId, selectTeamQuestion }: Props) => {
const [room, setRoom] = useState<RoomData | null>(null)
useEffect(() => {
requestJSON(`/api/room/${roomId}`).then(room => {
setRoom(room)
})
}, [])
console.log(room)
export const LiveLeaderboard = ({ room, selectTeamQuestion }: Props) => {
if (!room) {
return <div>Loading...</div>
}
const scoreboard = computeScoreboardState(
{
teamIds: room.teams,
questions: room.questions,
},
room.actions
)
return <Leaderboard questions={room.questions} scoreboard={scoreboard} selectTeamQuestion={selectTeamQuestion} />
}

@ -1,20 +1,17 @@
import { requestJSON } from '@/client/utils'
import type { RoomData } from '@/db/model'
import { requestJSON, sendJSON } from '@/client/utils'
import type { Room, RoomData } from '@/db/model'
import { type Action, type ActionAnswer, type ActionJolly } from '@/ggwp'
import { useEffect, useState } from 'preact/hooks'
type Outcome = 'correct' | 'partial' | 'wrong'
const handleSendAnswer = (action: Action) => {
// TODO: send action to server
console.log(action)
}
export const SubmitActionAnswer = ({
room,
sendAction,
onTeamQuestionIndex,
}: {
room: RoomData
sendAction: (action: Action) => void
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
}) => {
const [answer, setAnswer] = useState<ActionAnswer>({
@ -31,6 +28,7 @@ export const SubmitActionAnswer = ({
return (
<div class="card tint-red stack-v center">
<h3>Invia Risposta</h3>
<select
class="fill-w"
placeholder="Domanda..."
@ -47,14 +45,6 @@ export const SubmitActionAnswer = ({
))}
</select>
{/* <input
type="text"
class="fill-w"
placeholder="Squadra..."
value={answer.team}
onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })}
/> */}
<select
class="fill-w"
placeholder="Squadra..."
@ -80,16 +70,8 @@ export const SubmitActionAnswer = ({
<option value="partial">Parziale</option>
<option value="wrong">Sbagliata</option>
</select>
<button
onClick={() =>
handleSendAnswer({
type: 'answer',
...answer,
})
}
>
Invia risposta
</button>
<button onClick={() => sendAction({ type: 'answer', ...answer })}>Invia risposta</button>
</div>
)
}
@ -98,9 +80,11 @@ type Receiver<T> = (key: string, cb: (data: T) => void) => void
export const SubmitActionJolly = ({
room,
sendAction,
onTeamQuestionIndex,
}: {
room: RoomData
sendAction: (action: Action) => void
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
}) => {
const groupsMap: Record<string, boolean> = {}
@ -117,12 +101,12 @@ export const SubmitActionJolly = ({
const [answer, setAnswer] = useState<ActionJolly>({
team: '',
groupId: '',
group: '',
})
onTeamQuestionIndex?.('SubmitActionJolly', ({ team, question }) => {
console.log('onTeamQuestionIndex', team, question)
setAnswer(answer => ({ ...answer, team, groupId: questionToGroup[question] }))
setAnswer(answer => ({ ...answer, team, group: questionToGroup[question] }))
})
return (
@ -148,8 +132,8 @@ export const SubmitActionJolly = ({
<select
class="fill-w"
placeholder="Gruppo..."
value={answer.groupId}
onInput={e => setAnswer({ ...answer, groupId: e.currentTarget.value })}
value={answer.group}
onInput={e => setAnswer({ ...answer, group: e.currentTarget.value })}
>
<option value="" disabled>
Seleziona gruppo...
@ -159,33 +143,25 @@ export const SubmitActionJolly = ({
))}
</select>
<button
onClick={() =>
handleSendAnswer({
type: 'jolly',
...answer,
})
}
>
Imposta jolly
</button>
<button onClick={() => sendAction({ type: 'jolly', ...answer })}>Imposta jolly</button>
</div>
)
}
export const SubmitAction = ({
roomId,
room,
refreshRoom,
onTeamQuestionIndex,
}: {
roomId: string
room: Room
refreshRoom: () => void
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
}) => {
const [room, setRoom] = useState<RoomData | null>(null)
useEffect(() => {
requestJSON(`/api/room/${roomId}`).then(room => {
setRoom(room)
})
}, [])
const sendAction = async (action: Action) => {
await sendJSON(`/api/room/${room.id}/action`, action)
refreshRoom()
}
if (!room) {
return <div>Loading...</div>
@ -193,8 +169,8 @@ export const SubmitAction = ({
return (
<>
<SubmitActionAnswer room={room} onTeamQuestionIndex={onTeamQuestionIndex} />
<SubmitActionJolly room={room} onTeamQuestionIndex={onTeamQuestionIndex} />
<SubmitActionAnswer room={room} sendAction={sendAction} onTeamQuestionIndex={onTeamQuestionIndex} />
<SubmitActionJolly room={room} sendAction={sendAction} onTeamQuestionIndex={onTeamQuestionIndex} />
</>
)
}

@ -53,6 +53,16 @@ export function createRoom(id: string, teams: string[], questions: Question[]):
return password
}
export function updateRoom(id: string, data: RoomData): void {
db.prepare<[string, string]>(
`
UPDATE rooms
SET data = ?
WHERE id = ?;
`
).run(JSON.stringify(data), id)
}
export function getRoom(id: string): RoomData | null {
const row = db
.prepare<

@ -51,7 +51,7 @@ export type ActionAnswer = {
export type ActionJolly = {
team: string
groupId: string
group: string
}
export type Action = ({ type: 'answer' } & ActionAnswer) | ({ type: 'jolly' } & ActionJolly)
@ -134,7 +134,7 @@ export function computeScoreboardState(game: Game, rawActions: Action[]): Scoreb
// dict from team to jolly question group
const teamsJollyGroup = Object.fromEntries(
actions.filter(action => action.type === 'jolly').map(action => [action.team, action.groupId])
actions.filter(action => action.type === 'jolly').map(action => [action.team, action.group])
)
for (const team of game.teamIds) {

@ -35,5 +35,5 @@ console.log('listeners', listeners)
---
<Base>
<AdminPage client:load room={room} />
<AdminPage client:load roomId={room} />
</Base>

@ -0,0 +1,24 @@
import { getRoom, updateRoom } from '@/db'
import type { Action } from '@/ggwp'
import type { APIRoute } from 'astro'
export const POST: APIRoute = async ({ params, request }) => {
const { id: roomId } = params
if (!roomId) {
return new Response('Invalid room id', { status: 400 })
}
const room = getRoom(roomId)
if (!room) {
return new Response('Room not found', { status: 404 })
}
const action = (await request.json()) as Action
room.actions.push(action)
updateRoom(roomId, room)
return new Response(JSON.stringify('ok'), {
headers: { 'content-type': 'application/json' },
})
}

@ -1,4 +1,5 @@
import { getRoom } from '@/db'
import { getRoom, updateRoom } from '@/db'
import type { RoomData } from '@/db/model'
import type { APIRoute } from 'astro'
async function sseHandler() {
@ -27,3 +28,26 @@ export const GET: APIRoute = async ({ params, url }) => {
headers: { 'content-type': 'application/json' },
})
}
export const POST: APIRoute = async ({ params, request }) => {
const { id: roomId } = params
if (!roomId) {
return new Response('Invalid room id', { status: 400 })
}
const room = getRoom(roomId)
if (!room) {
return new Response('Room not found', { status: 404 })
}
const newRoom = (await request.json()) as RoomData
// @ts-ignore
delete newRoom.id
updateRoom(roomId, newRoom)
return new Response(JSON.stringify('ok'), {
headers: { 'content-type': 'application/json' },
})
}

@ -101,6 +101,19 @@ a {
text-decoration: underline 2px solid;
}
.chip {
display: inline-block;
align-content: center;
height: 1.75rem;
padding: 0 0.5rem;
border-radius: 0.25rem;
color: oklch(from var(--tint) 0.15 0.1 h);
background: oklch(from var(--tint) calc(l + 0.3) c h);
box-shadow: 2px 2px 0 oklch(from var(--tint) calc(l - 0.2) 0.1 h);
}
.text-large {
font-size: 20px;
}
@ -108,7 +121,7 @@ a {
/* Components */
.card {
padding: 1rem;
padding: 0.75rem;
border: 2px solid #333;
border-radius: 0.5rem;
@ -116,7 +129,7 @@ a {
background: linear-gradient(-15deg, var(--tint), oklch(from var(--tint) calc(l + 0.25) c h));
box-shadow: 3px 3px 0 oklch(from var(--tint) l 0.1 h);
min-width: 50ch;
min-width: 80ch;
@media screen and (max-width: 768px) {
min-width: 100%;

Loading…
Cancel
Save