111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
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
|
||
}
|