diff --git a/src/components/ActionRegistry.tsx b/src/components/ActionRegistry.tsx index c37d82b..3685dc9 100644 --- a/src/components/ActionRegistry.tsx +++ b/src/components/ActionRegistry.tsx @@ -5,8 +5,8 @@ import { format } from 'date-fns' import { type Dispatch, type StateUpdater } from 'preact/hooks' type ActionCardProps = { - moveUp: () => void - moveDown: () => void + // moveUp: () => void + // moveDown: () => void remove: () => void @@ -20,14 +20,17 @@ type Props = { refreshRoom: () => void } -const CardActions = ({ moveUp, moveDown, remove }: ActionCardProps) => ( +const CardActions = ({ + // moveUp, moveDown, + remove, +}: ActionCardProps) => ( <> - + {/* arrow_upward arrow_downward - + */} {/* @@ -118,27 +121,27 @@ export const ActionRegistry = ({ room, refreshRoom }: Props) => { refreshRoom() } - const moveActionUp = (index: number) => { - if (index === 0) return + // 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 - }) - } + // 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 + // 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 - }) - } + // 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) @@ -170,8 +173,8 @@ export const ActionRegistry = ({ room, refreshRoom }: Props) => { action.type === 'answer' ? ( moveActionUp(index)} - moveDown={() => moveActionDown(index)} + // moveUp={() => moveActionUp(index)} + // moveDown={() => moveActionDown(index)} remove={() => removeAction(index)} // editing={editing === index} // edit={() => editAction(index)} @@ -180,8 +183,8 @@ export const ActionRegistry = ({ room, refreshRoom }: Props) => { ) : action.type === 'jolly' ? ( moveActionUp(index)} - moveDown={() => moveActionDown(index)} + // moveUp={() => moveActionUp(index)} + // moveDown={() => moveActionDown(index)} remove={() => removeAction(index)} // editing={editing === index} // edit={() => editAction(index)} diff --git a/src/components/Clock.jsx b/src/components/Clock.jsx new file mode 100644 index 0000000..7d0ab14 --- /dev/null +++ b/src/components/Clock.jsx @@ -0,0 +1,18 @@ +import { formatDate } from 'date-fns' +import { useEffect, useState } from 'preact/hooks' + +export const Clock = ({}) => { + const [time, setTime] = useState(new Date()) + + useEffect(() => { + const timer = setInterval(() => { + setTime(new Date()) + }, 1000) + + return () => { + clearInterval(timer) + } + }, []) + + return <>{formatDate(time, 'HH:mm:ss')}> +} diff --git a/src/components/LiveLeaderboard.tsx b/src/components/LiveLeaderboard.tsx index f954d5d..873dd94 100644 --- a/src/components/LiveLeaderboard.tsx +++ b/src/components/LiveLeaderboard.tsx @@ -4,6 +4,8 @@ import { useEffect, useRef, useState } from 'preact/hooks' import { Leaderboard } from './Leaderboard' import { computeScoreboardState } from '@/ggwp' import clsx from 'clsx' +import { formatDate } from 'date-fns' +import { Clock } from './Clock' type Props = { roomId: string @@ -51,50 +53,62 @@ export const LiveLeaderboard = ({ roomId }: Props) => { room.actions ) - const [autoscroll, setAutoscroll] = useState(false) + const [page, setPage] = useState(0) const timerRef = useRef(null) useEffect(() => { - const PIXELS_PER_MILLISECOND = [1, 1000 / 30] // e.g. [1, 10] means 1px every 10ms - - if (autoscroll !== false) { - timerRef.current = setInterval(() => { - console.log('scrolling') - - window.scrollBy({ - top: autoscroll === 'down' ? PIXELS_PER_MILLISECOND[0] : -PIXELS_PER_MILLISECOND[0], - behavior: 'instant', - }) - - if (window.scrollY + window.innerHeight + 1 > document.body.scrollHeight) { - setAutoscroll('up') - } - - if (window.scrollY === 0) { - setAutoscroll('down') - } - }, PIXELS_PER_MILLISECOND[1]) - - return () => { - if (timerRef.current) { - clearInterval(timerRef.current) - } + timerRef.current = setInterval(() => { + setPage(page => (page + 1) % 3) + }, 5000) + + return () => { + if (timerRef.current) { + clearInterval(timerRef.current) } } - }, [autoscroll]) + }, []) + + // useEffect(() => { + // const PIXELS_PER_MILLISECOND = [1, 1000 / 30] // e.g. [1, 10] means 1px every 10ms + + // if (autoscroll !== false) { + // timerRef.current = setInterval(() => { + // console.log('scrolling') + + // window.scrollBy({ + // top: autoscroll === 'down' ? PIXELS_PER_MILLISECOND[0] : -PIXELS_PER_MILLISECOND[0], + // behavior: 'instant', + // }) + + // if (window.scrollY + window.innerHeight + 1 > document.body.scrollHeight) { + // setAutoscroll('up') + // } + + // if (window.scrollY === 0) { + // setAutoscroll('down') + // } + // }, PIXELS_PER_MILLISECOND[1]) + + // return () => { + // if (timerRef.current) { + // clearInterval(timerRef.current) + // } + // } + // } + // }, [autoscroll]) return ( <> - - - - setAutoscroll(v => (v === false ? 'down' : false))} - > - swap_vert - - + + — {room.id} (pag. {page + 1}/3) + + > ) } diff --git a/src/components/NewRoom.tsx b/src/components/NewRoom.tsx index 129890e..071f450 100644 --- a/src/components/NewRoom.tsx +++ b/src/components/NewRoom.tsx @@ -53,12 +53,22 @@ export const NewRoom = ({}) => { { - const teamsSyntax = prompt(`Aggiungi squadre separate da virgola, e.g. "Team 1, Team 2"`) + const teamsSyntax = prompt(`Aggiungi squadre separate da virgola, e.g. "Team 1,Team 2,..."`) if (!teamsSyntax) return setTeams(teams => { - return [...teams, ...teamsSyntax.split(',')] + return [ + ...teams, + ...teamsSyntax.split(',').flatMap(t => { + if (t.includes('*')) { + const [team, count] = t.split('*') + return Array.from({ length: Number(count) }, (_, i) => `${team}-${i + 1}`) + } + + return [t] + }), + ] }) }} > diff --git a/src/components/SubmitAction.tsx b/src/components/SubmitAction.tsx index 844faad..a2f0bda 100644 --- a/src/components/SubmitAction.tsx +++ b/src/components/SubmitAction.tsx @@ -1,7 +1,9 @@ import { sendJSON } from '@/client/utils' import type { Room, RoomData } from '@/db/model' import { type Action, type ActionAnswer, type ActionJolly } from '@/ggwp' +import { formatDate, parse } from 'date-fns' import { useState } from 'preact/hooks' +import { Clock } from './Clock' type Outcome = 'correct' | 'partial' | 'wrong' @@ -27,7 +29,9 @@ export const SubmitActionAnswer = ({ return ( - Invia Risposta + + Invia Risposta Immediata () + Sbagliata - - sendAction({ - ...answer, - type: 'answer', - timestamp: new Date().toISOString(), - }) - } + + + sendAction({ + ...answer, + type: 'answer', + timestamp: new Date().toISOString(), + }) + } + > + Invia risposta + + + + ) +} + +export const SubmitActionAnswerAtTime = ({ + room, + sendAction, + onTeamQuestionIndex, +}: { + room: RoomData + sendAction: (action: Action) => void + onTeamQuestionIndex?: Receiver<{ team: string; question: string }> +}) => { + const [answer, setAnswer] = useState({ + timestamp: '', + question: '', + team: '', + outcome: 'correct', + }) + + onTeamQuestionIndex?.('SubmitActionAnswerAtTime', ({ team, question }) => { + console.log('onTeamQuestionIndex', team, question) + setAnswer(answer => ({ ...answer, team, question })) + }) + + return ( + + Invia Risposta al Tempo + + + setAnswer({ ...answer, timestamp: e.currentTarget.value })} + /> + + setAnswer({ ...answer, timestamp: formatDate(new Date(), 'HH:mm:ss') })}> + Aggiorna Tempo + + + + setAnswer({ ...answer, question: e.currentTarget.value })} > - Invia risposta - + + Seleziona domanda... + + {room.questions.map((question, i) => ( + + {question.id} + + ))} + + + setAnswer({ ...answer, team: e.currentTarget.value })} + > + + Seleziona squadra... + + {room.teams.map((team, i) => ( + + {team} + + ))} + + + setAnswer({ ...answer, outcome: e.currentTarget.value as Outcome })} + > + Corretta + Parziale + Sbagliata + + + + { + if (answer.timestamp.trim() === '') { + alert('Inserisci un tempo valido') + return + } + + sendAction({ + ...answer, + type: 'answer', + timestamp: parse(answer.timestamp, 'HH:mm:ss', new Date()).toISOString(), + }) + }} + > + Invia risposta + + ) } @@ -190,6 +299,8 @@ export const SubmitAction = ({ return ( <> + + > ) diff --git a/src/pages/api/room/[id]/action.ts b/src/pages/api/room/[id]/action.ts index acd3126..c96748c 100644 --- a/src/pages/api/room/[id]/action.ts +++ b/src/pages/api/room/[id]/action.ts @@ -28,6 +28,10 @@ export const POST: APIRoute = async ({ params, request, cookies }) => { const action = (await request.json()) as Action room.actions.push(action) + room.actions.sort((a, b) => { + return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + }) + updateRoom(roomId, room) return new Response(JSON.stringify('ok'), { diff --git a/src/styles.css b/src/styles.css index 896bc10..75f2885 100644 --- a/src/styles.css +++ b/src/styles.css @@ -44,8 +44,8 @@ body { justify-items: center; align-content: start; - padding: 6rem 1rem 12rem; - gap: 3rem; + padding: 2rem 1rem 12rem; + gap: 2rem; } /* Typography */