Files
evening_detective_screen/electron/screen-plan.ts
T
2026-10-23 03:02:55 +07:00

111 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { timerLabels, validQR } from '../src/utils/presentation.js'
import type { TimerType } from '../src/types/screen.js'
import { ResponseFieldError } from './request-error.js'
import type { ScreenMode, ScreenPlan } from '../src/types/screen.js'
import { integer, object, text } from './remote-state.js'
const modes = [
'UNSPECIFIED',
'COVER',
'INTRO',
'INTRO_DONE',
'CONCLUSION',
'CONCLUSION_DONE',
'RESULTS',
'TIMER',
'TEXT',
'QR',
'TEAMS',
]
export function parsePlan(value: unknown): ScreenPlan {
const plan = value == null ? {} : object(value, 'plan')
const list = plan.steps ?? []
if (!Array.isArray(list))
throw new ResponseFieldError('plan.steps', 'массив', list, 'Некорректные шаги плана')
const steps = list.map((value, index) => {
const field = `plan.steps[${index}]`
const step = object(value, field)
let mode: number
try {
mode = integer(
typeof step.mode === 'string' && step.mode.startsWith('SCREEN_MODE_')
? modes.indexOf(step.mode.slice(12))
: step.mode,
1,
10,
`${field}.mode`,
)
} catch {
throw new ResponseFieldError(
`${field}.mode`,
'режим экрана 1–10 или enum ScreenMode',
step.mode,
'Некорректный режим экрана',
)
}
let content = text(step.text ?? '', `${field}.text`).trim()
if (mode === 3 || mode === 5) {
content = mode === 3 ? 'Успехов' : 'Спасибо за игру!'
mode = 8
}
const timerType = parseTimerType(step.timerType)
const qrUrl = text(step.qrUrl ?? '', `${field}.qrUrl`)
if (mode === 9 && !validQR(qrUrl, content)) throw new Error('Укажите ссылку HTTP/HTTPS до 2048 байт и текст до 10000 байт')
const label = text(step.timerLabel ?? '', `${field}.timerLabel`).trim()
const seconds = integer(
step.timerDurationSeconds ?? 0,
0,
86400,
`${field}.timerDurationSeconds`,
)
if (mode === 8 && (!content || Buffer.byteLength(content) > 10000))
throw new ResponseFieldError(
`${field}.text`,
'строка от 1 до 10000 байт',
step.text,
'Текст шага должен содержать от 1 до 10000 байт',
)
if (mode === 7 && (!seconds || Buffer.byteLength(label) > 200))
throw new ResponseFieldError(
!seconds ? `${field}.timerDurationSeconds` : `${field}.timerLabel`,
!seconds ? 'число от 1 до 86400' : 'строка до 200 байт',
!seconds ? step.timerDurationSeconds : step.timerLabel,
'Укажите таймер от 1 до 86400 секунд и подпись до 200 байт',
)
return {
mode: mode as ScreenMode,
text: content,
timerLabel: mode === 7 ? label || timerLabels[timerType] : label,
timerDurationSeconds: seconds,
...(qrUrl ? { qrUrl } : {}),
...(timerType ? { timerType } : {}),
}
})
return {
steps,
currentStep: integer(
plan.currentStep ?? 0,
0,
Math.max(0, steps.length - 1),
'plan.currentStep',
),
}
}
export function parseTimerType(value: unknown): TimerType {
const names = [
'SCREEN_TIMER_TYPE_BREAK',
'SCREEN_TIMER_TYPE_BEFORE_GAME',
'SCREEN_TIMER_TYPE_UNTIL_END',
]
return integer(
typeof value === 'string' && value.startsWith('SCREEN_TIMER_TYPE_')
? names.indexOf(value)
: (value ?? 0),
0,
2,
'timerType',
) as TimerType
}