This commit is contained in:
@@ -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) => (
|
||||
<>
|
||||
<button class="square" onClick={moveUp}>
|
||||
{/* <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> */}
|
||||
{/* <button class="square"
|
||||
onClick={edit}
|
||||
>
|
||||
@@ -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' ? (
|
||||
<ActionCardAnswer
|
||||
action={action}
|
||||
moveUp={() => 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' ? (
|
||||
<ActionCardJolly
|
||||
action={action}
|
||||
moveUp={() => moveActionUp(index)}
|
||||
moveDown={() => moveActionDown(index)}
|
||||
// moveUp={() => moveActionUp(index)}
|
||||
// moveDown={() => moveActionDown(index)}
|
||||
remove={() => removeAction(index)}
|
||||
// editing={editing === index}
|
||||
// edit={() => editAction(index)}
|
||||
|
||||
@@ -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')}</>
|
||||
}
|
||||
@@ -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 | 'up' | 'down'>(false)
|
||||
const [page, setPage] = useState(0)
|
||||
const timerRef = useRef<Timer | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const PIXELS_PER_MILLISECOND = [1, 1000 / 30] // e.g. [1, 10] means 1px every 10ms
|
||||
timerRef.current = setInterval(() => {
|
||||
setPage(page => (page + 1) % 3)
|
||||
}, 5000)
|
||||
|
||||
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)
|
||||
}
|
||||
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 (
|
||||
<>
|
||||
<Leaderboard questions={room.questions} scoreboard={scoreboard} />
|
||||
|
||||
<div class="position-fixed bottom right">
|
||||
<button
|
||||
class={clsx('square', autoscroll !== false && 'active')}
|
||||
onClick={() => setAutoscroll(v => (v === false ? 'down' : false))}
|
||||
>
|
||||
<div class="icon">swap_vert</div>
|
||||
</button>
|
||||
</div>
|
||||
<h3>
|
||||
<Clock /> — {room.id} (pag. {page + 1}/3)
|
||||
</h3>
|
||||
<Leaderboard
|
||||
questions={room.questions}
|
||||
scoreboard={{
|
||||
teamJollyGroup: scoreboard.teamJollyGroup,
|
||||
board: scoreboard.board.slice(page * 20, (page + 1) * 20),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,12 +53,22 @@ export const NewRoom = ({}) => {
|
||||
<button
|
||||
class="square"
|
||||
onClick={() => {
|
||||
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]
|
||||
}),
|
||||
]
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
||||
+122
-11
@@ -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 (
|
||||
<div class="card tint-green stack-v center">
|
||||
<h3>Invia Risposta</h3>
|
||||
<h3>
|
||||
Invia Risposta Immediata (<Clock />)
|
||||
</h3>
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
@@ -71,17 +75,122 @@ export const SubmitActionAnswer = ({
|
||||
<option value="wrong">Sbagliata</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
sendAction({
|
||||
...answer,
|
||||
type: 'answer',
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
<div class="stack-h">
|
||||
<button
|
||||
onClick={() =>
|
||||
sendAction({
|
||||
...answer,
|
||||
type: 'answer',
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
>
|
||||
Invia risposta
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const SubmitActionAnswerAtTime = ({
|
||||
room,
|
||||
sendAction,
|
||||
onTeamQuestionIndex,
|
||||
}: {
|
||||
room: RoomData
|
||||
sendAction: (action: Action) => void
|
||||
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
|
||||
}) => {
|
||||
const [answer, setAnswer] = useState<ActionAnswer>({
|
||||
timestamp: '',
|
||||
question: '',
|
||||
team: '',
|
||||
outcome: 'correct',
|
||||
})
|
||||
|
||||
onTeamQuestionIndex?.('SubmitActionAnswerAtTime', ({ team, question }) => {
|
||||
console.log('onTeamQuestionIndex', team, question)
|
||||
setAnswer(answer => ({ ...answer, team, question }))
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="card tint-green stack-v center">
|
||||
<h3>Invia Risposta al Tempo</h3>
|
||||
|
||||
<div class="stack-h fill-w">
|
||||
<input
|
||||
type="text"
|
||||
class="fill"
|
||||
placeholder={'Tempo in formato HH:mm:ss...'}
|
||||
value={answer.timestamp}
|
||||
onInput={e => setAnswer({ ...answer, timestamp: e.currentTarget.value })}
|
||||
/>
|
||||
|
||||
<button onClick={() => setAnswer({ ...answer, timestamp: formatDate(new Date(), 'HH:mm:ss') })}>
|
||||
Aggiorna Tempo
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
placeholder="Domanda..."
|
||||
value={answer.question}
|
||||
onInput={e => setAnswer({ ...answer, question: e.currentTarget.value })}
|
||||
>
|
||||
Invia risposta
|
||||
</button>
|
||||
<option value="" disabled>
|
||||
Seleziona domanda...
|
||||
</option>
|
||||
{room.questions.map((question, i) => (
|
||||
<option value={question.id} key={i}>
|
||||
{question.id}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
placeholder="Squadra..."
|
||||
value={answer.team}
|
||||
onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Seleziona squadra...
|
||||
</option>
|
||||
{room.teams.map((team, i) => (
|
||||
<option value={team} key={i}>
|
||||
{team}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
value={answer.outcome}
|
||||
onInput={e => setAnswer({ ...answer, outcome: e.currentTarget.value as Outcome })}
|
||||
>
|
||||
<option value="correct">Corretta</option>
|
||||
<option value="partial">Parziale</option>
|
||||
<option value="wrong">Sbagliata</option>
|
||||
</select>
|
||||
|
||||
<div class="stack-h">
|
||||
<button
|
||||
onClick={() => {
|
||||
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
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -190,6 +299,8 @@ export const SubmitAction = ({
|
||||
return (
|
||||
<>
|
||||
<SubmitActionAnswer room={room} sendAction={sendAction} onTeamQuestionIndex={onTeamQuestionIndex} />
|
||||
<SubmitActionAnswerAtTime room={room} sendAction={sendAction} onTeamQuestionIndex={onTeamQuestionIndex} />
|
||||
|
||||
<SubmitActionJolly room={room} sendAction={sendAction} onTeamQuestionIndex={onTeamQuestionIndex} />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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'), {
|
||||
|
||||
+2
-2
@@ -44,8 +44,8 @@ body {
|
||||
justify-items: center;
|
||||
align-content: start;
|
||||
|
||||
padding: 6rem 1rem 12rem;
|
||||
gap: 3rem;
|
||||
padding: 2rem 1rem 12rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
Reference in New Issue
Block a user