up
This commit is contained in:
@@ -4,7 +4,6 @@ import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import type { OnlineIntent, OnlineState } from '../types/online'
|
||||
|
||||
defineProps<{ outputOpen: boolean; selectedKey: string | null; selectedName: string | null }>()
|
||||
const emit = defineEmits<{ packagesChanged: [] }>()
|
||||
const bridge = window.screenBridge
|
||||
const message = useMessage()
|
||||
const isReady = ref(false)
|
||||
@@ -45,8 +44,6 @@ const connectionLabels = {
|
||||
let timer: ReturnType<typeof setInterval> | undefined
|
||||
let refreshing = false
|
||||
function accept(value: OnlineState) {
|
||||
if (state.value && value.packagesRevision !== state.value.packagesRevision)
|
||||
emit('packagesChanged')
|
||||
state.value = value
|
||||
isReady.value = true
|
||||
}
|
||||
@@ -72,7 +69,6 @@ async function run(intent: OnlineIntent) {
|
||||
if (intent.action === 'login') password.value = ''
|
||||
isBusy.value = false
|
||||
await refresh()
|
||||
emit('packagesChanged')
|
||||
}
|
||||
}
|
||||
async function cancel() {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
NButton,
|
||||
NCard,
|
||||
NInputNumber,
|
||||
NInput,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
@@ -16,10 +15,8 @@ import type {
|
||||
Channel,
|
||||
HostInfo,
|
||||
Intent,
|
||||
PackageSummary,
|
||||
Playback,
|
||||
PreviewFrame,
|
||||
ScreenMode,
|
||||
State,
|
||||
ScreenStep,
|
||||
PlanStatus,
|
||||
@@ -27,7 +24,6 @@ import type {
|
||||
|
||||
import ConnectionPanel from './ConnectionPanel.vue'
|
||||
import ScreenPlanPanel from './ScreenPlanPanel.vue'
|
||||
import { modeLabels } from '../utils/presentation'
|
||||
import { useScreenTimer } from '../composables/useScreenTimer'
|
||||
|
||||
const message = useMessage()
|
||||
@@ -40,7 +36,6 @@ const originalPlan = ref('')
|
||||
const isPlanDirty = computed(() => JSON.stringify(planDraft.value) !== originalPlan.value)
|
||||
const bridge = window.screenBridge
|
||||
const state = ref<State | null>(null)
|
||||
const packages = ref<PackageSummary[]>([])
|
||||
const host = ref<HostInfo>({
|
||||
displays: [],
|
||||
notice: '',
|
||||
@@ -50,15 +45,8 @@ const host = ref<HostInfo>({
|
||||
const display = ref<number | null>(null)
|
||||
const positions = ref<Record<Channel, number | null>>({ main: 10, music: 10 })
|
||||
const isBusy = ref(false)
|
||||
const textDraft = ref('')
|
||||
const timerLabel = ref('Перерыв')
|
||||
const timerSeconds = ref<number | null>(300)
|
||||
const timerDisplay = useScreenTimer(() => state.value)
|
||||
const preview = ref<PreviewFrame | null>(null)
|
||||
const modeOptions = Object.entries(modeLabels).map(([key, label]) => ({
|
||||
label,
|
||||
value: Number(key) as ScreenMode,
|
||||
}))
|
||||
const previewURL = computed(() =>
|
||||
preview.value?.session === state.value?.session &&
|
||||
preview.value?.revision === state.value?.scene.revision
|
||||
@@ -110,10 +98,6 @@ const channels = ['main', 'music'] as const
|
||||
const playlist = computed(() =>
|
||||
state.value?.catalog.playlists.find((p) => p.id === state.value?.channels.music.playlistId),
|
||||
)
|
||||
const packageOptions = computed(() => [
|
||||
{ label: 'Тестовый режим SCREEN-001', value: 'fixtures' },
|
||||
...packages.value.map((p) => ({ label: p.name, value: p.key })),
|
||||
])
|
||||
const statusLabels: Record<Playback, string> = {
|
||||
idle: 'Ресурс не выбран',
|
||||
pending: 'Подготовка или выполнение команды',
|
||||
@@ -181,16 +165,6 @@ function acceptState(value: State) {
|
||||
isPlanEditing.value = false
|
||||
planDraft.value = []
|
||||
}
|
||||
if (value.session !== state.value?.session || value.scene.text !== state.value?.scene.text)
|
||||
textDraft.value = value.scene.text
|
||||
if (
|
||||
value.session !== state.value?.session ||
|
||||
value.timer.label !== state.value?.timer.label ||
|
||||
value.timer.durationMs !== state.value?.timer.durationMs
|
||||
) {
|
||||
timerLabel.value = value.timer.label
|
||||
timerSeconds.value = value.timer.durationMs / 1000
|
||||
}
|
||||
if (
|
||||
!value.outputOpen ||
|
||||
value.session !== state.value?.session ||
|
||||
@@ -221,30 +195,6 @@ async function send(intent: Intent) {
|
||||
message.error(error instanceof Error ? error.message : 'Команда не выполнена')
|
||||
}
|
||||
}
|
||||
function selectPackage(key: string) {
|
||||
discardPlan(() => {
|
||||
void changePackage(key)
|
||||
})
|
||||
}
|
||||
async function changePackage(key: string) {
|
||||
await run(async () => {
|
||||
if (!bridge) return
|
||||
try {
|
||||
acceptState(await bridge.selectPackage(key === 'fixtures' ? null : key))
|
||||
} finally {
|
||||
packages.value = await bridge.packages()
|
||||
}
|
||||
})
|
||||
}
|
||||
async function refreshPackages() {
|
||||
if (bridge) packages.value = await bridge.packages()
|
||||
}
|
||||
async function removePackage(key: string) {
|
||||
await run(async () => {
|
||||
await bridge?.onlineAction({ action: 'remove', key })
|
||||
await refreshPackages()
|
||||
})
|
||||
}
|
||||
async function refreshHost() {
|
||||
if (!bridge) return
|
||||
host.value = await bridge.host()
|
||||
@@ -269,7 +219,6 @@ onMounted(async () => {
|
||||
if (!bridge) throw new Error('Откройте пульт в приложении Electron')
|
||||
unsubscribe = bridge.subscribe(acceptState)
|
||||
acceptState(await bridge.state())
|
||||
packages.value = await bridge.packages()
|
||||
await refreshHost()
|
||||
})
|
||||
previewTimer = setInterval(() => {
|
||||
@@ -301,29 +250,7 @@ onUnmounted(() => {
|
||||
:output-open="state?.outputOpen ?? false"
|
||||
:selected-key="state?.catalog.key ?? null"
|
||||
:selected-name="state?.catalog.name ?? null"
|
||||
@packages-changed="refreshPackages"
|
||||
/>
|
||||
<n-card title="Материалы показа">
|
||||
<n-select
|
||||
:value="state?.catalog.key ?? 'fixtures'"
|
||||
:options="packageOptions"
|
||||
:disabled="!bridge || isBusy || isPlanSaving"
|
||||
:input-props="{ 'aria-label': 'Игра показа' }"
|
||||
@update:value="selectPackage"
|
||||
/>
|
||||
<n-alert v-if="state && !state.catalog.key" type="info"
|
||||
>Тестовый режим: синтетические аудио и видео.</n-alert
|
||||
>
|
||||
<n-space v-for="pkg in packages" :key="pkg.key">
|
||||
<span>{{ pkg.name }}</span>
|
||||
<n-button
|
||||
:disabled="isBusy || pkg.key === state?.catalog.key"
|
||||
@click="removePackage(pkg.key)"
|
||||
>Удалить материалы</n-button
|
||||
>
|
||||
</n-space>
|
||||
<p v-if="!packages.length">Готовых игровых пакетов на диске пока нет.</p>
|
||||
</n-card>
|
||||
<n-card v-if="bridge" title="Дисплей показа">
|
||||
<n-space vertical>
|
||||
<n-alert v-if="host.notice" type="warning">{{ host.notice }}</n-alert>
|
||||
@@ -385,15 +312,13 @@ onUnmounted(() => {
|
||||
@update-step="(index, patch) => Object.assign(planDraft[index]!, patch)"
|
||||
@command="send"
|
||||
/>
|
||||
<label>Режим показа</label>
|
||||
<n-select
|
||||
:value="state.scene.mode"
|
||||
:options="modeOptions"
|
||||
:disabled="isBusy"
|
||||
:input-props="{ 'aria-label': 'Режим показа' }"
|
||||
@update:value="send({ action: 'mode', mode: $event })"
|
||||
/>
|
||||
<p role="status">{{ sceneLabels[state.scene.status] }}</p>
|
||||
<p v-if="state.timer.durationMs > 0" role="status" class="timer-status">
|
||||
{{ state.timer.label }} · {{ timerDisplay }} · {{ timerLabels[state.timer.status] }}
|
||||
</p>
|
||||
<p v-if="state.scene.mode !== 7 && state.timer.status === 'running'">
|
||||
Таймер продолжает отсчёт в фоне.
|
||||
</p>
|
||||
<n-alert v-if="state.scene.error" type="error">{{ state.scene.error }}</n-alert>
|
||||
<n-button
|
||||
v-if="state.scene.status === 'error'"
|
||||
@@ -429,70 +354,6 @@ onUnmounted(() => {
|
||||
<p>Кадр реального окна показа, без второго звука.</p>
|
||||
</n-card>
|
||||
</section>
|
||||
<n-card title="Текст и таймер">
|
||||
<n-space vertical>
|
||||
<label for="scene-text">Текст для показа</label>
|
||||
<n-input
|
||||
v-model:value="textDraft"
|
||||
type="textarea"
|
||||
:maxlength="10000"
|
||||
:autosize="{ minRows: 2, maxRows: 6 }"
|
||||
:input-props="{ id: 'scene-text' }"
|
||||
/>
|
||||
<n-button
|
||||
:disabled="isBusy || !textDraft.trim()"
|
||||
@click="send({ action: 'text', text: textDraft })"
|
||||
>Показать текст</n-button
|
||||
>
|
||||
<label for="timer-label">Подпись таймера</label>
|
||||
<n-input
|
||||
v-model:value="timerLabel"
|
||||
:maxlength="200"
|
||||
:input-props="{ id: 'timer-label' }"
|
||||
/>
|
||||
<label for="timer-seconds">Длительность, секунды</label>
|
||||
<n-input-number
|
||||
v-model:value="timerSeconds"
|
||||
:min="1"
|
||||
:max="86400"
|
||||
:precision="0"
|
||||
:input-props="{ id: 'timer-seconds' }"
|
||||
/>
|
||||
<n-button
|
||||
:disabled="isBusy || !timerSeconds"
|
||||
@click="
|
||||
send({ action: 'timer-set', label: timerLabel, durationSeconds: timerSeconds ?? 0 })
|
||||
"
|
||||
>Показать таймер</n-button
|
||||
>
|
||||
<p role="status" class="timer-status">
|
||||
{{ state.timer.label }} · {{ timerDisplay }} · {{ timerLabels[state.timer.status] }}
|
||||
</p>
|
||||
<p v-if="state.scene.mode !== 7 && state.timer.status === 'running'">
|
||||
Таймер продолжает отсчёт в фоне.
|
||||
</p>
|
||||
<n-space>
|
||||
<n-button
|
||||
:disabled="isBusy || state.timer.status === 'running'"
|
||||
@click="send({ action: 'timer-start' })"
|
||||
>Запустить таймер</n-button
|
||||
>
|
||||
<n-button
|
||||
:disabled="isBusy || state.timer.status !== 'running'"
|
||||
@click="send({ action: 'timer-pause' })"
|
||||
>Пауза таймера</n-button
|
||||
>
|
||||
<n-button
|
||||
:disabled="isBusy || state.timer.status !== 'paused'"
|
||||
@click="send({ action: 'timer-resume' })"
|
||||
>Продолжить таймер</n-button
|
||||
>
|
||||
<n-button :disabled="isBusy" @click="send({ action: 'timer-reset' })"
|
||||
>Сбросить таймер</n-button
|
||||
>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
<label class="mute"
|
||||
>Выключить весь звук
|
||||
<n-switch
|
||||
|
||||
@@ -110,7 +110,7 @@ const modeOptions = Object.entries(modeLabels).map(([mode, label]) => ({
|
||||
</n-space>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p v-if="!steps.length">План экранов ещё не создан. Используйте переключатель режимов.</p>
|
||||
<p v-if="!steps.length">План экранов ещё не создан. Создайте план, чтобы настроить экраны.</p>
|
||||
<n-space
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
|
||||
Reference in New Issue
Block a user