The first onUpdate call used raw milliseconds (e.g. total: 10000), while subsequent ticks used seconds (total: 10). This caused the progress bar to jump on the first interval tick.
77 lines
1.9 KiB
TypeScript
77 lines
1.9 KiB
TypeScript
import type { Choice } from '../types'
|
|
|
|
export interface ChoiceTimerState {
|
|
total: number
|
|
remaining: number
|
|
}
|
|
|
|
type TimerUpdateCallback = (state: ChoiceTimerState) => void
|
|
type TimeoutCallback = (choice: Choice) => void
|
|
|
|
export class ChoiceSystem {
|
|
private timerId: ReturnType<typeof setInterval> | null = null
|
|
private timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
private timeLimit = 0
|
|
private elapsed = 0
|
|
private tickMs = 100
|
|
private onUpdate: TimerUpdateCallback | null = null
|
|
private onTimeout: TimeoutCallback | null = null
|
|
|
|
start(choices: Choice[], onUpdate: TimerUpdateCallback, onTimeout: TimeoutCallback) {
|
|
this.clear()
|
|
|
|
const timed = choices.filter((c) => c.timeLimit && c.timeLimit > 0)
|
|
if (timed.length === 0) return
|
|
|
|
const maxLimit = Math.max(...timed.map((c) => c.timeLimit!))
|
|
|
|
const maxLimitSec = maxLimit / 1000
|
|
|
|
this.timeLimit = maxLimit
|
|
this.elapsed = 0
|
|
this.onUpdate = onUpdate
|
|
this.onTimeout = onTimeout
|
|
|
|
this.onUpdate({ total: maxLimitSec, remaining: maxLimitSec })
|
|
|
|
this.timerId = setInterval(() => {
|
|
this.elapsed += this.tickMs
|
|
const remaining = Math.max(0, this.timeLimit - this.elapsed) / 1000
|
|
const nextState: ChoiceTimerState = {
|
|
total: this.timeLimit / 1000,
|
|
remaining: Math.ceil(remaining * 10) / 10,
|
|
}
|
|
this.onUpdate?.(nextState)
|
|
}, this.tickMs)
|
|
|
|
this.timeoutId = setTimeout(() => {
|
|
this.clear()
|
|
// Pick the first choice as default on timeout
|
|
if (choices.length > 0) {
|
|
this.onTimeout?.(choices[0])
|
|
}
|
|
}, this.timeLimit)
|
|
}
|
|
|
|
stop() {
|
|
this.clear()
|
|
}
|
|
|
|
private clear() {
|
|
if (this.timerId !== null) {
|
|
clearInterval(this.timerId)
|
|
this.timerId = null
|
|
}
|
|
if (this.timeoutId !== null) {
|
|
clearTimeout(this.timeoutId)
|
|
this.timeoutId = null
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.clear()
|
|
this.onUpdate = null
|
|
this.onTimeout = null
|
|
}
|
|
}
|