This commit is contained in:
2026-10-04 23:08:03 +07:00
parent 31d93a9916
commit 68fd0760f6
4 changed files with 394 additions and 371 deletions
+4 -4
View File
@@ -5,10 +5,10 @@
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Вечерний детектив</title> <title>Вечерний детектив</title>
<script type="module" crossorigin src="/assets/index-D9DHo4Ux.js"></script> <script type="module" crossorigin src="/assets/index-DVruj17R.js"></script>
<link rel="modulepreload" crossorigin href="/assets/client-Dr2LhGo3.js"> <link rel="modulepreload" crossorigin href="/assets/client-CmW03vy2.js">
<link rel="modulepreload" crossorigin href="/assets/light-DXa4IKh6.js"> <link rel="modulepreload" crossorigin href="/assets/light-CU7ioAUB.js">
<link rel="modulepreload" crossorigin href="/assets/light-4JJV7N1X.js"> <link rel="modulepreload" crossorigin href="/assets/light-CBsH9Klj.js">
<link rel="stylesheet" crossorigin href="/assets/index-CF62QB4P.css"> <link rel="stylesheet" crossorigin href="/assets/index-CF62QB4P.css">
</head> </head>
<body> <body>
+240 -2
View File
@@ -1,11 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { NButton, NFlex, NSpace, NText } from 'naive-ui' import { NButton, NFlex, NInput, NInputNumber, NSelect, NSpace, NText } from 'naive-ui'
import { computed, onBeforeUnmount, ref } from 'vue' import { computed, onBeforeUnmount, ref } from 'vue'
import type { import type {
Application, Application,
MapItem, MapItem,
ResultsQuestion, ResultsQuestion,
ScreenCommand,
ScreenMode,
ScreenPlan,
ScreenPlanStep,
ScreenState, ScreenState,
Team, Team,
TeamResult, TeamResult,
@@ -18,11 +22,30 @@ const props = defineProps<{
screens: ScreenState[] screens: ScreenState[]
previewQuestions: ResultsQuestion[] previewQuestions: ResultsQuestion[]
previewTeams: TeamResult[] previewTeams: TeamResult[]
screenPlan?: ScreenPlan
screenPlanDraft: ScreenPlanStep[]
screenPlanDirty: boolean
screenPlanSaving: boolean
screenPlanMode: 'use' | 'edit'
screenVolume: number
screenPosition: number
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
saveItem: [item: MapItem, hidden?: boolean] saveItem: [item: MapItem, hidden?: boolean]
giveApplication: [team: Team, application: Application] giveApplication: [team: Team, application: Application]
addStep: []
edit: []
cancel: []
removeStep: [index: number]
moveStep: [index: number, offset: number]
dirty: []
clearPlan: []
save: []
setStep: [index: number]
command: [mode: ScreenMode | undefined, command: ScreenCommand]
'update:screenVolume': [value: number | null]
'update:screenPosition': [value: number | null]
}>() }>()
const mapScale = ref(1) const mapScale = ref(1)
@@ -34,6 +57,16 @@ const teamsNeedingApplications = computed(
() => () =>
new Map(props.teams.filter((team) => team.applications?.length).map((team) => [team.ID, team])), new Map(props.teams.filter((team) => team.applications?.length).map((team) => [team.ID, team])),
) )
const screenPlanModeOptions = [
{ label: 'Обложка', value: 'SCREEN_MODE_COVER' },
{ label: 'Введение', value: 'SCREEN_MODE_INTRO' },
{ label: 'Успехов', value: 'SCREEN_MODE_INTRO_DONE' },
{ label: 'Закрывашка', value: 'SCREEN_MODE_CONCLUSION' },
{ label: 'Спасибо за игру', value: 'SCREEN_MODE_CONCLUSION_DONE' },
{ label: 'Результаты', value: 'SCREEN_MODE_RESULTS' },
{ label: 'Таймер', value: 'SCREEN_MODE_TIMER' },
{ label: 'Текст', value: 'SCREEN_MODE_TEXT' },
]
let dragState: let dragState:
{ item: MapItem; startX: number; startY: number; itemX: number; itemY: number } | undefined { item: MapItem; startX: number; startY: number; itemX: number; itemY: number } | undefined
let panState: { startX: number; startY: number; offsetX: number; offsetY: number } | undefined let panState: { startX: number; startY: number; offsetX: number; offsetY: number } | undefined
@@ -211,6 +244,210 @@ onBeforeUnmount(() => {
:teams="previewTeams" :teams="previewTeams"
/> />
<div v-else class="screen-preview-placeholder">Экран не подключён</div> <div v-else class="screen-preview-placeholder">Экран не подключён</div>
<div class="screen-plan">
<n-flex align="center" justify="space-between">
<p class="screens-title">План экранов</p>
<n-space>
<n-button
v-if="!screenPlan && screenPlanMode === 'use'"
size="small"
@click="emit('addStep')"
>
Создать план
</n-button>
<n-button
v-if="screenPlan && screenPlanMode === 'use'"
size="small"
@click="emit('edit')"
>
Редактировать
</n-button>
<template v-if="screenPlanMode === 'edit'">
<n-button size="small" :disabled="screenPlanSaving" @click="emit('cancel')">
Отменить
</n-button>
<n-button
size="small"
type="primary"
:loading="screenPlanSaving"
:disabled="screenPlanSaving"
@click="emit('save')"
>
Сохранить план
</n-button>
</template>
</n-space>
</n-flex>
<p v-if="!screenPlan && screenPlanMode === 'use'" class="screen-empty">
План экранов ещё не создан.
</p>
<template v-else-if="screenPlanMode === 'edit'">
<n-space v-for="(step, index) in screenPlanDraft" :key="index" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-select
v-model:value="step.mode"
:options="screenPlanModeOptions"
:disabled="screenPlanSaving"
size="small"
style="width: 180px"
@update:value="emit('dirty')"
/>
<n-input
v-if="step.mode === 'SCREEN_MODE_TEXT'"
v-model:value="step.text"
:disabled="screenPlanSaving"
size="small"
placeholder="Текст экрана"
style="min-width: 240px"
@update:value="emit('dirty')"
/>
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
<n-input
v-model:value="step.timerLabel"
:disabled="screenPlanSaving"
size="small"
placeholder="Надпись таймера"
@update:value="emit('dirty')"
/>
<n-input-number
v-model:value="step.timerDurationSeconds"
:min="1"
:max="86400"
:disabled="screenPlanSaving"
size="small"
placeholder="Секунды"
@update:value="emit('dirty')"
/>
</template>
<n-button
size="small"
:disabled="screenPlanSaving || index === 0"
@click="emit('moveStep', index, -1)"
>
↑
</n-button>
<n-button
size="small"
:disabled="screenPlanSaving || index === screenPlanDraft.length - 1"
@click="emit('moveStep', index, 1)"
>
↓
</n-button>
<n-button
size="small"
type="error"
ghost
:disabled="screenPlanSaving"
@click="emit('removeStep', index)"
>
Удалить
</n-button>
</n-space>
<n-space>
<n-button size="small" :disabled="screenPlanSaving" @click="emit('addStep')">
Добавить шаг
</n-button>
<n-button
v-if="screenPlanDraft.length"
size="small"
:disabled="screenPlanSaving"
@click="emit('clearPlan')"
>
Удалить план
</n-button>
</n-space>
</template>
<template v-else-if="screenPlan">
<n-space v-for="(step, index) in screenPlan.steps || []" :key="index" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-text>
{{ screenPlanModeOptions.find((option) => option.value === step.mode)?.label }}
<template v-if="step.mode === 'SCREEN_MODE_TEXT'">: {{ step.text }}</template>
<template v-else-if="step.mode === 'SCREEN_MODE_TIMER'">
: {{ step.timerLabel }} ({{ step.timerDurationSeconds }} с)
</template>
</n-text>
<n-button
size="small"
:type="screenPlan.currentStep === index ? 'primary' : 'default'"
:disabled="screenPlan.currentStep === index"
@click="emit('setStep', index)"
>
Показать
</n-button>
<template v-if="screenPlan.currentStep === index">
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_START')"
>
Старт
</n-button>
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_PAUSE')"
>
Пауза
</n-button>
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_RESUME')"
>
Продолжить
</n-button>
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_RESET')"
>
Сбросить
</n-button>
</template>
<template
v-else-if="
step.mode === 'SCREEN_MODE_INTRO' || step.mode === 'SCREEN_MODE_CONCLUSION'
"
>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_PLAY')">
Запуск
</n-button>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_PAUSE')">
Пауза
</n-button>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_RESUME')">
Продолжить
</n-button>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_STOP')">
Стоп
</n-button>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_REPLAY')">
Повтор
</n-button>
<n-input-number
:value="screenVolume"
:min="0"
:max="100"
:step="5"
size="small"
@update:value="emit('update:screenVolume', $event)"
/>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_VOLUME')">
Громкость
</n-button>
<n-input-number
:value="screenPosition"
:min="0"
:step="5"
size="small"
@update:value="emit('update:screenPosition', $event)"
/>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_SEEK')">
Перемотать
</n-button>
</template>
</template>
</n-space>
</template>
</div>
</aside> </aside>
</div> </div>
</template> </template>
@@ -247,10 +484,11 @@ onBeforeUnmount(() => {
} }
.map-field { .map-field {
aspect-ratio: 1;
background-color: #151515; background-color: #151515;
border: 1px dashed rgb(255 255 255 / 20%); border: 1px dashed rgb(255 255 255 / 20%);
cursor: grab; cursor: grab;
height: 420px; height: auto;
min-height: 200px; min-height: 200px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
+145 -119
View File
@@ -12,6 +12,8 @@ import {
NRate, NRate,
NSelect, NSelect,
NSpace, NSpace,
NTabPane,
NTabs,
NText, NText,
} from 'naive-ui' } from 'naive-ui'
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
@@ -52,6 +54,20 @@ const isDownloadingQR = ref(false)
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const gameId = route.params.id const gameId = route.params.id
type GameTab = 'game' | 'teams' | 'screens-map'
function isGameTab(value: string): value is GameTab {
return ['game', 'teams', 'screens-map'].includes(value)
}
const queryTab = typeof route.query.tab === 'string' ? route.query.tab : ''
const activeTab = ref<GameTab>(isGameTab(queryTab) ? queryTab : 'game')
async function setActiveTab(value: string) {
if (!isGameTab(value)) return
activeTab.value = value
await router.replace({ query: { ...route.query, tab: value } })
}
const message = useMessage() const message = useMessage()
@@ -680,127 +696,137 @@ useSimplePolling(() => {
<template> <template>
<div class="center-block-custom"> <div class="center-block-custom">
<div class="width1200"> <div class="width1200">
<div class="settings-block"> <n-tabs :value="activeTab" type="line" animated @update:value="setActiveTab">
<n-flex justify="space-between"> <n-tab-pane name="game" tab="Игра">
<n-space> <div class="settings-block">
<n-tag :bordered="false" type="info"> Участвуют команд: {{ activeTeamCount }} </n-tag> <n-flex justify="space-between">
<n-tag type="info"> <n-space>
{{ timeString }} <n-tag :bordered="false" type="info">
</n-tag> Участвуют команд: {{ activeTeamCount }}
<n-tag v-if="formatGameDateTime(game.startAt)" type="info"> </n-tag>
Начало: {{ formatGameDateTime(game.startAt) }} <n-tag type="info">
</n-tag> {{ timeString }}
</n-space> </n-tag>
<n-tag v-if="formatGameDateTime(game.startAt)" type="info">
Начало: {{ formatGameDateTime(game.startAt) }}
</n-tag>
</n-space>
<n-flex justify="end"> <n-flex justify="end">
<n-button quaternary @click="openQR(url, 'Админка')"> QR </n-button> <n-button quaternary @click="openQR(url, 'Админка')"> QR </n-button>
<n-button quaternary @click="openIssueTickets()"> Выпустить билеты </n-button> <n-button quaternary @click="openIssueTickets()"> Выпустить билеты </n-button>
<n-button quaternary @click="openAnswersModal()"> Ответы команд </n-button> <n-button quaternary @click="openAnswersModal()"> Ответы команд </n-button>
<n-button v-if="game.status == 'finish'" quaternary @click="toResults()"> <n-button v-if="game.status == 'finish'" quaternary @click="toResults()">
<Icon class="settings-icon"> <Icon class="settings-icon">
<Trophy /> <Trophy />
</Icon> </Icon>
Итоги Итоги
</n-button> </n-button>
<n-button <n-button
v-if="activeTeamCount" v-if="activeTeamCount"
quaternary quaternary
:loading="isDownloadingQR" :loading="isDownloadingQR"
:disabled="isDownloadingQR" :disabled="isDownloadingQR"
@click="downloadTeamQR()" @click="downloadTeamQR()"
> >
QR команд QR команд
</n-button> </n-button>
<n-button quaternary @click="openSettings()"> <n-button quaternary @click="openSettings()">
<Icon class="settings-icon"> <Icon class="settings-icon">
<Settings /> <Settings />
</Icon> </Icon>
Настройки Настройки
</n-button> </n-button>
<n-button <n-button
v-if="(game.teams?.length || 0) == 0" v-if="(game.teams?.length || 0) == 0"
type="error" type="error"
quaternary quaternary
@click="showDeleteGameModal = true" @click="showDeleteGameModal = true"
> >
Удалить игру Удалить игру
</n-button> </n-button>
<n-space> <n-space>
<n-button type="info" v-if="game.status != 'draft'" dashed @click="resetGame()"> <n-button type="info" v-if="game.status != 'draft'" dashed @click="resetGame()">
<Icon> <Icon>
<Reset /> <Reset />
</Icon> </Icon>
</n-button> </n-button>
<n-button type="info" v-if="game.status == 'draft'" dashed @click="startGame()"> <n-button type="info" v-if="game.status == 'draft'" dashed @click="startGame()">
<Icon> <Icon>
<Play /> <Play />
</Icon> </Icon>
</n-button> </n-button>
<n-button type="info" v-if="game.status == 'pause'" dashed @click="playGame()"> <n-button type="info" v-if="game.status == 'pause'" dashed @click="playGame()">
<Icon> <Icon>
<Play /> <Play />
</Icon> </Icon>
</n-button> </n-button>
<n-button type="info" v-if="game.status == 'run'" dashed @click="pauseGame()"> <n-button type="info" v-if="game.status == 'run'" dashed @click="pauseGame()">
<Icon> <Icon>
<Pause /> <Pause />
</Icon> </Icon>
</n-button> </n-button>
<n-button type="info" v-if="game.status != 'finish'" dashed @click="finishGame()"> <n-button type="info" v-if="game.status != 'finish'" dashed @click="finishGame()">
<Icon> <Icon>
<Flag /> <Flag />
</Icon> </Icon>
</n-button> </n-button>
</n-space> </n-space>
</n-flex> </n-flex>
</n-flex> </n-flex>
</div> </div>
</n-tab-pane>
<GameScreensPanel <n-tab-pane name="teams" tab="Команды">
:screens="screens" <n-tag :bordered="false" type="info"> Участвуют команд: {{ activeTeamCount }} </n-tag>
:screen-plan="screenPlan" <GameTeamsPanel
:screen-plan-draft="screenPlanDraft" :teams="game.teams || []"
:screen-plan-dirty="screenPlanDirty" :participation-options="participationOptions"
:screen-plan-saving="screenPlanSaving" @add="showAddTeamModal = true"
:screen-plan-mode="screenPlanMode" @open-qr="openTeamQR"
:screen-volume="screenVolume" @preview="(id, password) => toTeamStory(id, password, true)"
:screen-position="screenPosition" @delete="deleteTeam"
:screen-binding="screenBinding" @give-application="giveApplication"
@bind="bindScannedScreen" />
@set-sound="setSoundScreen" </n-tab-pane>
@unbind="unbindScreen" <n-tab-pane name="screens-map" tab="Экраны и карта">
@add-step="addScreenPlanStep" <GameScreensPanel
@edit="editScreenPlan" :screens="screens"
@cancel="cancelScreenPlanEdit" :screen-binding="screenBinding"
@remove-step="removeScreenPlanStep" @bind="bindScannedScreen"
@move-step="moveScreenPlanStep" @set-sound="setSoundScreen"
@dirty="markScreenPlanDirty" @unbind="unbindScreen"
@clear-plan="clearScreenPlan" />
@save="saveScreenPlan" <GameMapPanel
@set-step="setScreenPlanStep" :map-items="mapItems"
@command="sendScreenPlanCommand" :teams="game.teams || []"
@update:screen-volume="screenVolume = $event ?? 0" :screens="screens"
@update:screen-position="screenPosition = $event ?? 0" :preview-questions="previewQuestions"
/> :preview-teams="previewTeams"
<GameMapPanel :screen-plan="screenPlan"
:map-items="mapItems" :screen-plan-draft="screenPlanDraft"
:teams="game.teams || []" :screen-plan-dirty="screenPlanDirty"
:screens="screens" :screen-plan-saving="screenPlanSaving"
:preview-questions="previewQuestions" :screen-plan-mode="screenPlanMode"
:preview-teams="previewTeams" :screen-volume="screenVolume"
@save-item="saveMapItemAndReload" :screen-position="screenPosition"
@give-application="giveApplication" @save-item="saveMapItemAndReload"
/> @give-application="giveApplication"
<GameTeamsPanel @add-step="addScreenPlanStep"
:teams="game.teams || []" @edit="editScreenPlan"
:participation-options="participationOptions" @cancel="cancelScreenPlanEdit"
@add="showAddTeamModal = true" @remove-step="removeScreenPlanStep"
@open-qr="openTeamQR" @move-step="moveScreenPlanStep"
@preview="(id, password) => toTeamStory(id, password, true)" @dirty="markScreenPlanDirty"
@delete="deleteTeam" @clear-plan="clearScreenPlan"
@give-application="giveApplication" @save="saveScreenPlan"
/> @set-step="setScreenPlanStep"
@command="sendScreenPlanCommand"
@update:screen-volume="screenVolume = $event ?? 0"
@update:screen-position="screenPosition = $event ?? 0"
/>
</n-tab-pane>
</n-tabs>
</div> </div>
</div> </div>
+5 -246
View File
@@ -1,35 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { NAlert, NButton, NFlex, NTag } from 'naive-ui'
NAlert,
NButton,
NFlex,
NInput,
NInputNumber,
NSelect,
NSpace,
NTag,
NText,
} from 'naive-ui'
import { ref } from 'vue' import { ref } from 'vue'
import type { import type { ScreenState } from '@/api/generated/crabs/evening_detective_server'
ScreenCommand,
ScreenMode,
ScreenPlan,
ScreenPlanStep,
ScreenState,
} from '@/api/generated/crabs/evening_detective_server'
import ScreenBindingModal from '@/components/ScreenBindingModal.vue' import ScreenBindingModal from '@/components/ScreenBindingModal.vue'
const props = defineProps<{ const props = defineProps<{
screens: ScreenState[] screens: ScreenState[]
screenPlan?: ScreenPlan
screenPlanDraft: ScreenPlanStep[]
screenPlanDirty: boolean
screenPlanSaving: boolean
screenPlanMode: 'use' | 'edit'
screenVolume: number
screenPosition: number
screenBinding: boolean screenBinding: boolean
}>() }>()
@@ -37,32 +14,9 @@ const emit = defineEmits<{
bind: [screenID: string, accessToken: string] bind: [screenID: string, accessToken: string]
setSound: [screen: ScreenState] setSound: [screen: ScreenState]
unbind: [screen: ScreenState] unbind: [screen: ScreenState]
addStep: []
edit: []
cancel: []
removeStep: [index: number]
moveStep: [index: number, offset: number]
dirty: []
clearPlan: []
save: []
setStep: [index: number]
command: [mode: ScreenMode | undefined, command: ScreenCommand]
'update:screenVolume': [value: number | null]
'update:screenPosition': [value: number | null]
}>() }>()
const showBindingModal = ref(false) const showBindingModal = ref(false)
const screenPlanModeOptions = [
{ label: 'Обложка', value: 'SCREEN_MODE_COVER' },
{ label: 'Введение', value: 'SCREEN_MODE_INTRO' },
{ label: 'Успехов', value: 'SCREEN_MODE_INTRO_DONE' },
{ label: 'Закрывашка', value: 'SCREEN_MODE_CONCLUSION' },
{ label: 'Спасибо за игру', value: 'SCREEN_MODE_CONCLUSION_DONE' },
{ label: 'Результаты', value: 'SCREEN_MODE_RESULTS' },
{ label: 'Таймер', value: 'SCREEN_MODE_TIMER' },
{ label: 'Текст', value: 'SCREEN_MODE_TEXT' },
]
</script> </script>
<template> <template>
@@ -93,206 +47,11 @@ const screenPlanModeOptions = [
<n-flex justify="end"> <n-flex justify="end">
<n-tag v-if="screen.soundEnabled" type="success">Со звуком</n-tag> <n-tag v-if="screen.soundEnabled" type="success">Со звуком</n-tag>
<n-button v-else size="small" @click="emit('setSound', screen)">Назначить звук</n-button> <n-button v-else size="small" @click="emit('setSound', screen)">Назначить звук</n-button>
<n-button size="small" type="error" ghost @click="emit('unbind', screen)" <n-button size="small" type="error" ghost @click="emit('unbind', screen)">
>Отвязать</n-button Отвязать
> </n-button>
</n-flex> </n-flex>
</div> </div>
<div class="screen-plan">
<n-flex align="center" justify="space-between">
<p class="screens-title">План экранов</p>
<n-space>
<n-button
v-if="!screenPlan && screenPlanMode === 'use'"
size="small"
@click="emit('addStep')"
>
Создать план
</n-button>
<n-button
v-if="screenPlan && screenPlanMode === 'use'"
size="small"
@click="emit('edit')"
>
Редактировать
</n-button>
<template v-if="screenPlanMode === 'edit'">
<n-button size="small" :disabled="screenPlanSaving" @click="emit('cancel')"
>Отменить</n-button
>
<n-button
size="small"
type="primary"
:loading="screenPlanSaving"
:disabled="screenPlanSaving"
@click="emit('save')"
>
Сохранить план
</n-button>
</template>
</n-space>
</n-flex>
<p v-if="!screenPlan && screenPlanMode === 'use'" class="screen-empty">
План экранов ещё не создан.
</p>
<template v-else-if="screenPlanMode === 'edit'">
<n-space v-for="(step, index) in screenPlanDraft" :key="index" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-select
v-model:value="step.mode"
:options="screenPlanModeOptions"
:disabled="screenPlanSaving"
size="small"
style="width: 180px"
@update:value="emit('dirty')"
/>
<n-input
v-if="step.mode === 'SCREEN_MODE_TEXT'"
v-model:value="step.text"
:disabled="screenPlanSaving"
size="small"
placeholder="Текст экрана"
style="min-width: 240px"
@update:value="emit('dirty')"
/>
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
<n-input
v-model:value="step.timerLabel"
:disabled="screenPlanSaving"
size="small"
placeholder="Надпись таймера"
@update:value="emit('dirty')"
/>
<n-input-number
v-model:value="step.timerDurationSeconds"
:min="1"
:max="86400"
:disabled="screenPlanSaving"
size="small"
placeholder="Секунды"
@update:value="emit('dirty')"
/>
</template>
<n-button
size="small"
:disabled="screenPlanSaving || index === 0"
@click="emit('moveStep', index, -1)"
>↑</n-button
>
<n-button
size="small"
:disabled="screenPlanSaving || index === screenPlanDraft.length - 1"
@click="emit('moveStep', index, 1)"
>↓</n-button
>
<n-button
size="small"
type="error"
ghost
:disabled="screenPlanSaving"
@click="emit('removeStep', index)"
>Удалить</n-button
>
</n-space>
<n-space>
<n-button size="small" :disabled="screenPlanSaving" @click="emit('addStep')"
>Добавить шаг</n-button
>
<n-button
v-if="screenPlanDraft.length"
size="small"
:disabled="screenPlanSaving"
@click="emit('clearPlan')"
>Удалить план</n-button
>
</n-space>
</template>
<template v-else-if="screenPlan">
<n-space v-for="(step, index) in screenPlan.steps || []" :key="index" align="center" wrap>
<n-text>{{ index + 1 }}</n-text>
<n-text>
{{ screenPlanModeOptions.find((option) => option.value === step.mode)?.label }}
<template v-if="step.mode === 'SCREEN_MODE_TEXT'">: {{ step.text }}</template>
<template v-else-if="step.mode === 'SCREEN_MODE_TIMER'"
>: {{ step.timerLabel }} ({{ step.timerDurationSeconds }} с)</template
>
</n-text>
<n-button
size="small"
:type="screenPlan.currentStep === index ? 'primary' : 'default'"
:disabled="screenPlan.currentStep === index"
@click="emit('setStep', index)"
>Показать</n-button
>
<template v-if="screenPlan.currentStep === index">
<template v-if="step.mode === 'SCREEN_MODE_TIMER'">
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_START')"
>Старт</n-button
>
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_PAUSE')"
>Пауза</n-button
>
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_RESUME')"
>Продолжить</n-button
>
<n-button
size="small"
@click="emit('command', step.mode, 'SCREEN_COMMAND_TIMER_RESET')"
>Сбросить</n-button
>
</template>
<template
v-else-if="
step.mode === 'SCREEN_MODE_INTRO' || step.mode === 'SCREEN_MODE_CONCLUSION'
"
>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_PLAY')"
>Запуск</n-button
>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_PAUSE')"
>Пауза</n-button
>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_RESUME')"
>Продолжить</n-button
>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_STOP')"
>Стоп</n-button
>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_REPLAY')"
>Повтор</n-button
>
<n-input-number
:value="screenVolume"
:min="0"
:max="100"
:step="5"
size="small"
@update:value="emit('update:screenVolume', $event)"
/>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_VOLUME')"
>Громкость</n-button
>
<n-input-number
:value="screenPosition"
:min="0"
:step="5"
size="small"
@update:value="emit('update:screenPosition', $event)"
/>
<n-button size="small" @click="emit('command', step.mode, 'SCREEN_COMMAND_SEEK')"
>Перемотать</n-button
>
</template>
</template>
</n-space>
</template>
</div>
</template> </template>
</div> </div>
<ScreenBindingModal <ScreenBindingModal