up
This commit is contained in:
@@ -16,8 +16,8 @@ SCREEN-001/SCREEN-004 остаётся открытой; эта инструкц
|
|||||||
|
|
||||||
## Полный план
|
## Полный план
|
||||||
|
|
||||||
- Пройдите восемь шагов по порядку и назад: обложка, вступление, «Успехов»,
|
- Пройдите восемь шагов по порядку и назад: обложка, вступление, текст «Успехов»,
|
||||||
заключительное видео, «Спасибо за игру!», результаты, таймер и текст.
|
заключительное видео, текст «Спасибо за игру!», результаты, таймер и текст.
|
||||||
- Убедитесь, что шаги не запускают звук или таймер сами. Запустите вступление/видео
|
- Убедитесь, что шаги не запускают звук или таймер сами. Запустите вступление/видео
|
||||||
явной кнопкой, переключитесь в другой режим: основное медиа останавливается.
|
явной кнопкой, переключитесь в другой режим: основное медиа останавливается.
|
||||||
- Включите музыку и переключайте сцены: музыка остаётся независимой. Предпросмотр
|
- Включите музыку и переключайте сцены: музыка остаётся независимой. Предпросмотр
|
||||||
|
|||||||
+2
-2
@@ -90,8 +90,8 @@ Windows 11, macOS 13+, Ubuntu 24.04 LTS или Debian 13. Эти требова
|
|||||||
## Проведение показа
|
## Проведение показа
|
||||||
|
|
||||||
В «План экранов» нажмите «Показать» у нужного шага, используйте «Предыдущий шаг» / «Следующий шаг»
|
В «План экранов» нажмите «Показать» у нужного шага, используйте «Предыдущий шаг» / «Следующий шаг»
|
||||||
для переходов по плану. В редакторе шагов доступны восемь режимов: «Обложка»,
|
для переходов по плану. В редакторе шагов доступны шесть режимов: «Обложка»,
|
||||||
«Введение», «Успехов», «Закрывашка», «Спасибо за игру»,
|
«Введение», «Закрывашка»,
|
||||||
«Результаты», «Таймер» и «Текст». Переход к шагу или режиму сам не запускает
|
«Результаты», «Таймер» и «Текст». Переход к шагу или режиму сам не запускает
|
||||||
медиа и таймер. Дождитесь готовности; отправленная команда ещё не означает
|
медиа и таймер. Дождитесь готовности; отправленная команда ещё не означает
|
||||||
успешного воспроизведения — смотрите статус и фактическое изображение/звук.
|
успешного воспроизведения — смотрите статус и фактическое изображение/звук.
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ const modes = [
|
|||||||
'SCREEN_MODE_UNSPECIFIED',
|
'SCREEN_MODE_UNSPECIFIED',
|
||||||
'SCREEN_MODE_COVER',
|
'SCREEN_MODE_COVER',
|
||||||
'SCREEN_MODE_INTRO',
|
'SCREEN_MODE_INTRO',
|
||||||
'SCREEN_MODE_INTRO_DONE',
|
undefined,
|
||||||
'SCREEN_MODE_CONCLUSION',
|
'SCREEN_MODE_CONCLUSION',
|
||||||
'SCREEN_MODE_CONCLUSION_DONE',
|
undefined,
|
||||||
'SCREEN_MODE_RESULTS',
|
'SCREEN_MODE_RESULTS',
|
||||||
'SCREEN_MODE_TIMER',
|
'SCREEN_MODE_TIMER',
|
||||||
'SCREEN_MODE_TEXT',
|
'SCREEN_MODE_TEXT',
|
||||||
] as const satisfies readonly ScreenMode[]
|
] as const satisfies readonly (ScreenMode | undefined)[]
|
||||||
const commands = [
|
const commands = [
|
||||||
'SCREEN_COMMAND_UNSPECIFIED',
|
'SCREEN_COMMAND_UNSPECIFIED',
|
||||||
'SCREEN_COMMAND_PLAY',
|
'SCREEN_COMMAND_PLAY',
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ export type ScreenMode =
|
|||||||
| "SCREEN_MODE_UNSPECIFIED"
|
| "SCREEN_MODE_UNSPECIFIED"
|
||||||
| "SCREEN_MODE_COVER"
|
| "SCREEN_MODE_COVER"
|
||||||
| "SCREEN_MODE_INTRO"
|
| "SCREEN_MODE_INTRO"
|
||||||
| "SCREEN_MODE_INTRO_DONE"
|
|
||||||
| "SCREEN_MODE_CONCLUSION"
|
| "SCREEN_MODE_CONCLUSION"
|
||||||
| "SCREEN_MODE_CONCLUSION_DONE"
|
|
||||||
| "SCREEN_MODE_RESULTS"
|
| "SCREEN_MODE_RESULTS"
|
||||||
| "SCREEN_MODE_TIMER"
|
| "SCREEN_MODE_TIMER"
|
||||||
| "SCREEN_MODE_TEXT";
|
| "SCREEN_MODE_TEXT";
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
TimerState,
|
TimerState,
|
||||||
ScreenStep,
|
ScreenStep,
|
||||||
} from '../src/types/screen.js'
|
} from '../src/types/screen.js'
|
||||||
|
import { parsePlan } from './screen-plan.js'
|
||||||
import { parseResults } from './results.js'
|
import { parseResults } from './results.js'
|
||||||
import type { LocalPackage } from './package-store.js'
|
import type { LocalPackage } from './package-store.js'
|
||||||
|
|
||||||
@@ -32,7 +33,12 @@ export function parseCommand(value: unknown): Command {
|
|||||||
let intent: Intent
|
let intent: Intent
|
||||||
if (c.action === 'mute' && typeof c.enabled === 'boolean')
|
if (c.action === 'mute' && typeof c.enabled === 'boolean')
|
||||||
intent = { action: 'mute', enabled: c.enabled }
|
intent = { action: 'mute', enabled: c.enabled }
|
||||||
else if (c.action === 'mode' && Number.isInteger(c.mode) && number(c.mode, 1, 8))
|
else if (
|
||||||
|
c.action === 'mode' &&
|
||||||
|
Number.isInteger(c.mode) &&
|
||||||
|
number(c.mode, 1, 8) &&
|
||||||
|
[1, 2, 4, 6, 7, 8].includes(c.mode)
|
||||||
|
)
|
||||||
intent = { action: 'mode', mode: c.mode as ScreenMode }
|
intent = { action: 'mode', mode: c.mode as ScreenMode }
|
||||||
else if (c.action === 'step' && Number.isSafeInteger(c.index) && number(c.index, 0))
|
else if (c.action === 'step' && Number.isSafeInteger(c.index) && number(c.index, 0))
|
||||||
intent = { action: 'step', index: c.index }
|
intent = { action: 'step', index: c.index }
|
||||||
@@ -259,7 +265,8 @@ export class Controller {
|
|||||||
background: pkg.package.media.background
|
background: pkg.package.media.background
|
||||||
? `screen-app://app/packages/${pkg.key}/${pkg.package.media.background}`
|
? `screen-app://app/packages/${pkg.key}/${pkg.package.media.background}`
|
||||||
: null,
|
: null,
|
||||||
steps: pkg.package.steps.map((step) => ({ ...step, mode: step.mode as ScreenMode })),
|
// Преобразуем старые режимы после проверки хеша исходного пакета.
|
||||||
|
steps: parsePlan({ steps: pkg.package.steps }).steps,
|
||||||
playlists: pkg.package.playlists.map((p) => ({
|
playlists: pkg.package.playlists.map((p) => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
|
|||||||
@@ -1180,6 +1180,8 @@ test('таймер синхронизируется один раз на вер
|
|||||||
applyRemote(c, { ...next, sound: false }, next)
|
applyRemote(c, { ...next, sound: false }, next)
|
||||||
assert.equal(c.state.muted, false)
|
assert.equal(c.state.muted, false)
|
||||||
assert.throws(() => parseRemote({ stateVersion: '9223372036854775808' }))
|
assert.throws(() => parseRemote({ stateVersion: '9223372036854775808' }))
|
||||||
|
for (const mode of [3, 5, 'SCREEN_MODE_INTRO_DONE', 'SCREEN_MODE_CONCLUSION_DONE'])
|
||||||
|
assert.throws(() => parseRemote({ mode }))
|
||||||
assert.throws(() => parseRemote({ mode: 99 }))
|
assert.throws(() => parseRemote({ mode: 99 }))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1834,8 +1836,8 @@ test('локальные режим, музыка и таймер публику
|
|||||||
await f.prepare()
|
await f.prepare()
|
||||||
f.controller.output(true)
|
f.controller.output(true)
|
||||||
await f.online.poll()
|
await f.online.poll()
|
||||||
await f.online.planCommand({ action: 'mode', mode: 3 })
|
await f.online.planCommand({ action: 'mode', mode: 1 })
|
||||||
assert.equal(f.model.remote.mode, 'SCREEN_MODE_INTRO_DONE')
|
assert.equal(f.model.remote.mode, 'SCREEN_MODE_COVER')
|
||||||
await f.online.planCommand({ action: 'timer-set', label: 'Отдых', durationSeconds: 60 })
|
await f.online.planCommand({ action: 'timer-set', label: 'Отдых', durationSeconds: 60 })
|
||||||
assert.equal(f.model.remote.timerDurationSeconds, 60)
|
assert.equal(f.model.remote.timerDurationSeconds, 60)
|
||||||
await f.online.planCommand({ action: 'play', channel: 'music' })
|
await f.online.planCommand({ action: 'play', channel: 'music' })
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { readdirSync, writeFileSync } from 'node:fs'
|
|||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { test, type TestContext } from 'node:test'
|
import { test, type TestContext } from 'node:test'
|
||||||
|
import { Controller } from './controller.js'
|
||||||
import { buildScreenPackage, parseScreenPackage } from './screen-package.js'
|
import { buildScreenPackage, parseScreenPackage } from './screen-package.js'
|
||||||
import {
|
import {
|
||||||
PackageStore,
|
PackageStore,
|
||||||
@@ -570,3 +571,49 @@ test('инкрементальный кеш: ошибка и отмена не
|
|||||||
await assert.rejects(f.store.ensureAsset(f.pkg, second!.id, abort.signal))
|
await assert.rejects(f.store.ensureAsset(f.pkg, second!.id, abort.signal))
|
||||||
await assert.rejects(f.store.localFile(f.key, second!.id))
|
await assert.rejects(f.store.localFile(f.key, second!.id))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('старый пакет проходит проверку хеша после перезапуска и показывает текст без сети', async (t) => {
|
||||||
|
const f = await fixture(t)
|
||||||
|
f.pkg.steps = [3, 5].map((mode) => ({
|
||||||
|
mode,
|
||||||
|
text: '',
|
||||||
|
timerLabel: '',
|
||||||
|
timerDurationSeconds: 0,
|
||||||
|
}))
|
||||||
|
f.pkg.metadataVersion = createHash('sha256')
|
||||||
|
.update(
|
||||||
|
JSON.stringify(f.pkg, (key, value: unknown) =>
|
||||||
|
key === 'metadataVersion' ? undefined : value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.digest('hex')
|
||||||
|
const downloaded = await f.store.download(f.pkg)
|
||||||
|
const restored = new PackageStore(f.root, {
|
||||||
|
...f.dependencies,
|
||||||
|
fetch: async () => {
|
||||||
|
throw new Error('Нет сети')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await restored.initialize()
|
||||||
|
const local = restored.list().find((pkg) => pkg.key === f.key)!
|
||||||
|
assert.equal(local.version, downloaded.version)
|
||||||
|
const controller = new Controller(() => {})
|
||||||
|
controller.selectPackage(local)
|
||||||
|
assert.deepEqual(
|
||||||
|
controller.state.catalog.steps.map(({ mode, text }) => ({ mode, text })),
|
||||||
|
[
|
||||||
|
{ mode: 8, text: 'Успехов' },
|
||||||
|
{ mode: 8, text: 'Спасибо за игру!' },
|
||||||
|
],
|
||||||
|
)
|
||||||
|
for (const [index, text] of ['Успехов', 'Спасибо за игру!'].entries()) {
|
||||||
|
controller.command({
|
||||||
|
session: controller.state.session,
|
||||||
|
sequence: controller.state.sequence + 1,
|
||||||
|
action: 'step',
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
assert.equal(controller.state.scene.mode, 8)
|
||||||
|
assert.equal(controller.state.scene.text, text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import { test } from 'node:test'
|
import { test } from 'node:test'
|
||||||
import { Controller, parseCommand } from './controller.js'
|
import { Controller, parseCommand } from './controller.js'
|
||||||
import { buildScreenPackage } from './screen-package.js'
|
import { buildScreenPackage, parseScreenPackage } from './screen-package.js'
|
||||||
import { packageKey, type LocalPackage } from './package-store.js'
|
import { packageKey, type LocalPackage } from './package-store.js'
|
||||||
import { parseResults } from './results.js'
|
import { parseResults } from './results.js'
|
||||||
import { formatTimer, remainingTime } from '../src/utils/presentation.js'
|
import { formatTimer, remainingTime } from '../src/utils/presentation.js'
|
||||||
@@ -53,7 +53,7 @@ function fixture() {
|
|||||||
formatVersion: 1,
|
formatVersion: 1,
|
||||||
key: packageKey(pkg.server, 7),
|
key: packageKey(pkg.server, 7),
|
||||||
version: 'a'.repeat(64),
|
version: 'a'.repeat(64),
|
||||||
package: pkg,
|
package: parseScreenPackage(pkg),
|
||||||
files: [],
|
files: [],
|
||||||
}
|
}
|
||||||
c.selectPackage(local)
|
c.selectPackage(local)
|
||||||
@@ -79,7 +79,9 @@ test('проходит восемь шагов, подставляет лока
|
|||||||
assert.notEqual(f.c.state.catalog.cover, f.c.state.catalog.background)
|
assert.notEqual(f.c.state.catalog.cover, f.c.state.catalog.background)
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
f.send({ action: 'step', index: i })
|
f.send({ action: 'step', index: i })
|
||||||
assert.equal(f.c.state.scene.mode, i + 1)
|
assert.equal(f.c.state.scene.mode, [1, 2, 8, 4, 8, 6, 7, 8][i])
|
||||||
|
if (i === 2) assert.equal(f.c.state.scene.text, 'Успехов')
|
||||||
|
if (i === 4) assert.equal(f.c.state.scene.text, 'Спасибо за игру!')
|
||||||
assert.equal(f.c.state.scene.stepIndex, i)
|
assert.equal(f.c.state.scene.stepIndex, i)
|
||||||
assert.equal(f.c.state.channels.main.desired, 'stopped')
|
assert.equal(f.c.state.channels.main.desired, 'stopped')
|
||||||
assert.equal(f.c.state.timer.status, 'stopped')
|
assert.equal(f.c.state.timer.status, 'stopped')
|
||||||
@@ -104,7 +106,7 @@ test('смена сцены останавливает основное меди
|
|||||||
})
|
})
|
||||||
assert.equal(f.c.state.scene.stepIndex, 1)
|
assert.equal(f.c.state.scene.stepIndex, 1)
|
||||||
assert.equal(f.c.state.scene.mode, 2)
|
assert.equal(f.c.state.scene.mode, 2)
|
||||||
f.send({ action: 'mode', mode: 3 })
|
f.send({ action: 'text', text: 'Успехов' })
|
||||||
assert.equal(f.c.state.channels.main.desired, 'stopped')
|
assert.equal(f.c.state.channels.main.desired, 'stopped')
|
||||||
assert.equal(f.c.state.channels.music.desired, 'playing')
|
assert.equal(f.c.state.channels.music.desired, 'playing')
|
||||||
assert.equal(f.c.state.scene.stepIndex, null)
|
assert.equal(f.c.state.scene.stepIndex, null)
|
||||||
@@ -209,13 +211,15 @@ test('не допускает пустой текст и некорректны
|
|||||||
{ action: 'timer-set', label: '', durationSeconds: 0 },
|
{ action: 'timer-set', label: '', durationSeconds: 0 },
|
||||||
{ action: 'timer-set', label: '', durationSeconds: 1.5 },
|
{ action: 'timer-set', label: '', durationSeconds: 1.5 },
|
||||||
{ action: 'timer-set', label: 'x'.repeat(201), durationSeconds: 10 },
|
{ action: 'timer-set', label: 'x'.repeat(201), durationSeconds: 10 },
|
||||||
|
{ action: 'mode', mode: 3 },
|
||||||
|
{ action: 'mode', mode: 5 },
|
||||||
{ action: 'mode', mode: 9 },
|
{ action: 'mode', mode: 9 },
|
||||||
{ action: 'mode', mode: NaN },
|
{ action: 'mode', mode: NaN },
|
||||||
{ action: 'step', index: -1 },
|
{ action: 'step', index: -1 },
|
||||||
{ action: ['timer-start'] },
|
{ action: ['timer-start'] },
|
||||||
])
|
])
|
||||||
assert.throws(() => parseCommand({ ...base, ...intent }))
|
assert.throws(() => parseCommand({ ...base, ...intent }))
|
||||||
for (const mode of [1, 2, 3, 4, 5, 6, 7, 8] as ScreenMode[])
|
for (const mode of [1, 2, 4, 6, 7, 8] as ScreenMode[])
|
||||||
assert.equal(parseCommand({ ...base, action: 'mode', mode }).action, 'mode')
|
assert.equal(parseCommand({ ...base, action: 'mode', mode }).action, 'mode')
|
||||||
})
|
})
|
||||||
function snapshot(key: string) {
|
function snapshot(key: string) {
|
||||||
|
|||||||
@@ -93,15 +93,17 @@ export function parseRemote(value: unknown): RemoteState {
|
|||||||
'UNSPECIFIED',
|
'UNSPECIFIED',
|
||||||
'COVER',
|
'COVER',
|
||||||
'INTRO',
|
'INTRO',
|
||||||
'INTRO_DONE',
|
'',
|
||||||
'CONCLUSION',
|
'CONCLUSION',
|
||||||
'CONCLUSION_DONE',
|
'',
|
||||||
'RESULTS',
|
'RESULTS',
|
||||||
'TIMER',
|
'TIMER',
|
||||||
'TEXT',
|
'TEXT',
|
||||||
],
|
],
|
||||||
'state.mode',
|
'state.mode',
|
||||||
) || 1
|
) || 1
|
||||||
|
if (![1, 2, 4, 6, 7, 8].includes(mode))
|
||||||
|
throw fieldError('state.mode', 'актуальный режим экрана', s.mode, 'Некорректный режим экрана')
|
||||||
const duration = integer(s.timerDurationSeconds ?? 600, 1, 86400, 'state.timerDurationSeconds')
|
const duration = integer(s.timerDurationSeconds ?? 600, 1, 86400, 'state.timerDurationSeconds')
|
||||||
const remaining = integer(
|
const remaining = integer(
|
||||||
s.timerRemainingSeconds ?? 0,
|
s.timerRemainingSeconds ?? 0,
|
||||||
|
|||||||
@@ -22,3 +22,20 @@ test('план: режимы protobuf, значения по умолчанию
|
|||||||
])
|
])
|
||||||
assert.throws(() => parsePlan(value))
|
assert.throws(() => parsePlan(value))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('старые текстовые режимы преобразуются без изменения порядка и текущего шага', () => {
|
||||||
|
const plan = parsePlan({
|
||||||
|
steps: [3, 'SCREEN_MODE_INTRO_DONE', 5, 'SCREEN_MODE_CONCLUSION_DONE'].map((mode) => ({
|
||||||
|
mode,
|
||||||
|
text: 'Неиспользуемый текст',
|
||||||
|
})),
|
||||||
|
currentStep: 2,
|
||||||
|
})
|
||||||
|
assert.equal(plan.currentStep, 2)
|
||||||
|
assert.deepEqual(plan.steps.map(({ mode, text }) => ({ mode, text })), [
|
||||||
|
{ mode: 8, text: 'Успехов' },
|
||||||
|
{ mode: 8, text: 'Успехов' },
|
||||||
|
{ mode: 8, text: 'Спасибо за игру!' },
|
||||||
|
{ mode: 8, text: 'Спасибо за игру!' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function parsePlan(value: unknown): ScreenPlan {
|
|||||||
const steps = list.map((value, index) => {
|
const steps = list.map((value, index) => {
|
||||||
const field = `plan.steps[${index}]`
|
const field = `plan.steps[${index}]`
|
||||||
const step = object(value, field)
|
const step = object(value, field)
|
||||||
let mode: ScreenMode
|
let mode: number
|
||||||
try {
|
try {
|
||||||
mode = integer(
|
mode = integer(
|
||||||
typeof step.mode === 'string' && step.mode.startsWith('SCREEN_MODE_')
|
typeof step.mode === 'string' && step.mode.startsWith('SCREEN_MODE_')
|
||||||
@@ -31,7 +31,7 @@ export function parsePlan(value: unknown): ScreenPlan {
|
|||||||
1,
|
1,
|
||||||
8,
|
8,
|
||||||
`${field}.mode`,
|
`${field}.mode`,
|
||||||
) as ScreenMode
|
)
|
||||||
} catch {
|
} catch {
|
||||||
throw new ResponseFieldError(
|
throw new ResponseFieldError(
|
||||||
`${field}.mode`,
|
`${field}.mode`,
|
||||||
@@ -40,7 +40,11 @@ export function parsePlan(value: unknown): ScreenPlan {
|
|||||||
'Некорректный режим экрана',
|
'Некорректный режим экрана',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const content = text(step.text ?? '', `${field}.text`).trim()
|
let content = text(step.text ?? '', `${field}.text`).trim()
|
||||||
|
if (mode === 3 || mode === 5) {
|
||||||
|
content = mode === 3 ? 'Успехов' : 'Спасибо за игру!'
|
||||||
|
mode = 8
|
||||||
|
}
|
||||||
const label = text(step.timerLabel ?? '', `${field}.timerLabel`).trim()
|
const label = text(step.timerLabel ?? '', `${field}.timerLabel`).trim()
|
||||||
const seconds = integer(
|
const seconds = integer(
|
||||||
step.timerDurationSeconds ?? 0,
|
step.timerDurationSeconds ?? 0,
|
||||||
@@ -63,7 +67,7 @@ export function parsePlan(value: unknown): ScreenPlan {
|
|||||||
'Укажите таймер от 1 до 86400 секунд и подпись до 200 байт',
|
'Укажите таймер от 1 до 86400 секунд и подпись до 200 байт',
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
mode,
|
mode: mode as ScreenMode,
|
||||||
text: content,
|
text: content,
|
||||||
timerLabel: mode === 7 ? label || 'Перерыв' : label,
|
timerLabel: mode === 7 ? label || 'Перерыв' : label,
|
||||||
timerDurationSeconds: seconds,
|
timerDurationSeconds: seconds,
|
||||||
|
|||||||
+3
-3
@@ -575,11 +575,11 @@ app.whenReady().then(async () => {
|
|||||||
}, `scene ${mode} ready`)
|
}, `scene ${mode} ready`)
|
||||||
for (let index = 0; index < 8; index++) {
|
for (let index = 0; index < 8; index++) {
|
||||||
await command({ action: 'step', index })
|
await command({ action: 'step', index })
|
||||||
const s = await sceneReady(index + 1)
|
const s = await sceneReady([1, 2, 8, 4, 8, 6, 7, 8][index])
|
||||||
modes.push(s.scene.mode)
|
modes.push(s.scene.mode)
|
||||||
assert.equal(s.scene.stepIndex, index)
|
assert.equal(s.scene.stepIndex, index)
|
||||||
assert.equal(s.channels.main.desired, 'stopped')
|
assert.equal(s.channels.main.desired, 'stopped')
|
||||||
assert.equal(s.channels.music.desired, 'playing')
|
assert.equal(s.channels.music.desired, [2, 4].includes(s.scene.mode) ? 'paused' : 'playing')
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await inspect('document.querySelectorAll("button,input,textarea,select").length'),
|
await inspect('document.querySelectorAll("button,input,textarea,select").length'),
|
||||||
0,
|
0,
|
||||||
@@ -615,7 +615,7 @@ app.whenReady().then(async () => {
|
|||||||
}
|
}
|
||||||
await poll(async () => (await state()).channels.main.volume === 42, 'plan media volume')
|
await poll(async () => (await state()).channels.main.volume === 42, 'plan media volume')
|
||||||
assert.equal((await state()).channels.main.desired, 'paused')
|
assert.equal((await state()).channels.main.desired, 'paused')
|
||||||
assert.equal((await state()).channels.music.desired, 'playing')
|
assert.equal((await state()).channels.music.desired, 'paused')
|
||||||
await clickMedia('Продолжить')
|
await clickMedia('Продолжить')
|
||||||
await poll(
|
await poll(
|
||||||
async () => (await state()).channels.main.status === 'playing',
|
async () => (await state()).channels.main.status === 'playing',
|
||||||
|
|||||||
@@ -80,11 +80,9 @@ function imageURL(url: string, version: string) {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<p v-else-if="mode === 3" class="message">Успехов</p>
|
|
||||||
<p v-else-if="mode === 4 && !hasMainSource" class="message">
|
<p v-else-if="mode === 4 && !hasMainSource" class="message">
|
||||||
Видео закрывашки не задано
|
Видео закрывашки не задано
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="mode === 5" class="message">Спасибо за игру!</p>
|
|
||||||
<ResultsTable
|
<ResultsTable
|
||||||
v-else-if="mode === 6"
|
v-else-if="mode === 6"
|
||||||
:snapshot="results"
|
:snapshot="results"
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ export interface Playlist {
|
|||||||
name: string
|
name: string
|
||||||
tracks: { id: number; name: string; source: MediaSource }[]
|
tracks: { id: number; name: string; source: MediaSource }[]
|
||||||
}
|
}
|
||||||
export type ScreenMode = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
|
export type ScreenMode = 1 | 2 | 4 | 6 | 7 | 8
|
||||||
export interface ScreenStep {
|
export interface ScreenStep {
|
||||||
mode: ScreenMode
|
mode: ScreenMode
|
||||||
text: string
|
text: string
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import type { ScreenMode, TimerState } from '../types/screen.js'
|
|||||||
export const modeLabels: Record<ScreenMode, string> = {
|
export const modeLabels: Record<ScreenMode, string> = {
|
||||||
1: 'Обложка',
|
1: 'Обложка',
|
||||||
2: 'Введение',
|
2: 'Введение',
|
||||||
3: 'Успехов',
|
|
||||||
4: 'Закрывашка',
|
4: 'Закрывашка',
|
||||||
5: 'Спасибо за игру',
|
|
||||||
6: 'Результаты',
|
6: 'Результаты',
|
||||||
7: 'Таймер',
|
7: 'Таймер',
|
||||||
8: 'Текст',
|
8: 'Текст',
|
||||||
|
|||||||
Reference in New Issue
Block a user