diff --git a/src/components/ActionRegistry.tsx b/src/components/ActionRegistry.tsx
index f1e97a8..73c0d74 100644
--- a/src/components/ActionRegistry.tsx
+++ b/src/components/ActionRegistry.tsx
@@ -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) => (
+ <>
+
+
+ {/* */}
+
+ >
+)
+
+const ActionCardAnswer = ({ action, ...props }: { action: ActionAnswer } & ActionCardProps) => {
return (
-
{action.question}
-
{action.team}
-
{action.outcome}
+
Risposta
+
+ Team: {action.team}
+
+
+ Domanda: {action.question}
+
+
+ Risultato: {action.outcome}
+
+
+
)
}
-const ActionCardJolly = ({ action }: { action: ActionJolly }) => {
+const ActionCardJolly = ({ action, ...props }: { action: ActionJolly } & ActionCardProps) => {
return (
-
{action.team}
-
{action.groupId}
+
Jolly
+
+ Team: {action.team}
+
+
+ Problem: {action.group}
+
+
+
)
}
-export const ActionRegistry = ({ roomId }: Props) => {
- const [room, setRoom] = useState(null)
- useEffect(() => {
- requestJSON(`/api/room/${roomId}`).then(room => {
- setRoom(room)
- })
- }, [])
-
+export const ActionRegistry = ({ room, refreshRoom }: Props) => {
if (!room) {
return Loading...
}
@@ -42,13 +82,84 @@ export const ActionRegistry = ({ roomId }: Props) => {
return Ancora nessuna azione
}
+ // const [editing, setEditing] = useState(null)
+
+ const setActions: Dispatch> = 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' ? (
-
+ moveActionUp(index)}
+ moveDown={() => moveActionDown(index)}
+ remove={() => removeAction(index)}
+ // editing={editing === index}
+ // edit={() => editAction(index)}
+ // doneEdit={doneEdit}
+ />
) : action.type === 'jolly' ? (
-
+ moveActionUp(index)}
+ moveDown={() => moveActionDown(index)}
+ remove={() => removeAction(index)}
+ // editing={editing === index}
+ // edit={() => editAction(index)}
+ // doneEdit={doneEdit}
+ />
) : (
Unknown action type:
{JSON.stringify(action)}
diff --git a/src/components/AdminPage.tsx b/src/components/AdminPage.tsx
index 5d0e6d2..aa17001 100644
--- a/src/components/AdminPage.tsx
+++ b/src/components/AdminPage.tsx
@@ -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
void>>({})
+ const [room, setRoom] = useState(null)
+ const fetchRoom = async () => {
+ await requestJSON(`/api/room/${roomId}`).then(room => {
+ setRoom({ id: roomId, ...room })
+ })
+ }
+
+ useEffect(() => {
+ fetchRoom()
+ }, [])
+
+ if (!room) {
+ return Loading...
+ }
+
+ const scoreboard = computeScoreboardState(
+ {
+ teamIds: room.teams,
+ questions: room.questions,
+ },
+ room.actions
+ )
+
return (
<>
Admin
- {room}
- {room.id}
+
Object.values(listeners.current).forEach(listener => listener(teamQuestion))
}
/>
Azioni
- (listeners.current[key] = listener)} />
+ (listeners.current[key] = listener)}
+ />
Registro Azioni
-
+
>
)
}
diff --git a/src/components/LiveLeaderboard.tsx b/src/components/LiveLeaderboard.tsx
index b5862b1..f652c82 100644
--- a/src/components/LiveLeaderboard.tsx
+++ b/src/components/LiveLeaderboard.tsx
@@ -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>
+
selectTeamQuestion?: ({ team, question }: { team: string; question: string }) => void
}
-export const LiveLeaderboard = ({ roomId, selectTeamQuestion }: Props) => {
- const [room, setRoom] = useState(null)
- useEffect(() => {
- requestJSON(`/api/room/${roomId}`).then(room => {
- setRoom(room)
- })
- }, [])
-
- console.log(room)
-
+export const LiveLeaderboard = ({ room, selectTeamQuestion }: Props) => {
if (!room) {
return Loading...
}
-
- const scoreboard = computeScoreboardState(
- {
- teamIds: room.teams,
- questions: room.questions,
- },
- room.actions
- )
-
- return
}
diff --git a/src/components/SubmitAction.tsx b/src/components/SubmitAction.tsx
index 3b880d4..79cf19f 100644
--- a/src/components/SubmitAction.tsx
+++ b/src/components/SubmitAction.tsx
@@ -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({
@@ -31,6 +28,7 @@ export const SubmitActionAnswer = ({
return (
Invia Risposta
+
)
}
@@ -98,9 +80,11 @@ type Receiver = (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 = {}
@@ -117,12 +101,12 @@ export const SubmitActionJolly = ({
const [answer, setAnswer] = useState({
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 = ({
setAnswer({ ...answer, groupId: e.currentTarget.value })}
+ value={answer.group}
+ onInput={e => setAnswer({ ...answer, group: e.currentTarget.value })}
>
-
+
)
}
export const SubmitAction = ({
- roomId,
+ room,
+ refreshRoom,
+
onTeamQuestionIndex,
}: {
- roomId: string
+ room: Room
+ refreshRoom: () => void
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
}) => {
- const [room, setRoom] = useState(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 Loading...
@@ -193,8 +169,8 @@ export const SubmitAction = ({
return (
<>
-
-
+
+
>
)
}
diff --git a/src/db/index.ts b/src/db/index.ts
index 126fd34..b73ebe5 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -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<
diff --git a/src/ggwp/index.ts b/src/ggwp/index.ts
index 185aa9e..04dc026 100644
--- a/src/ggwp/index.ts
+++ b/src/ggwp/index.ts
@@ -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) {
diff --git a/src/pages/[room]/admin.astro b/src/pages/[room]/admin.astro
index dd594d8..82cd0b1 100644
--- a/src/pages/[room]/admin.astro
+++ b/src/pages/[room]/admin.astro
@@ -35,5 +35,5 @@ console.log('listeners', listeners)
---
-
+
diff --git a/src/pages/api/room/[id]/action.ts b/src/pages/api/room/[id]/action.ts
new file mode 100644
index 0000000..fd88290
--- /dev/null
+++ b/src/pages/api/room/[id]/action.ts
@@ -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' },
+ })
+}
diff --git a/src/pages/api/room/[id].ts b/src/pages/api/room/[id]/index.ts
similarity index 54%
rename from src/pages/api/room/[id].ts
rename to src/pages/api/room/[id]/index.ts
index e25e28b..302b516 100644
--- a/src/pages/api/room/[id].ts
+++ b/src/pages/api/room/[id]/index.ts
@@ -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' },
+ })
+}
diff --git a/src/styles.css b/src/styles.css
index aaa8323..ed2d90f 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -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%;