fix
This commit is contained in:
@@ -259,7 +259,7 @@ const gameOptions = computed(() => {
|
||||
data-testid="game-selection-status"
|
||||
:type="selectedKey ? 'success' : 'error'"
|
||||
>
|
||||
{{ selectedKey ? 'Выбрана' : 'Не выбрана' }}
|
||||
{{ selectedKey ? 'Игра выбрана' : 'Игра не выбрана' }}
|
||||
</n-tag>
|
||||
</template>
|
||||
<div class="connection-content">
|
||||
@@ -358,6 +358,16 @@ const gameOptions = computed(() => {
|
||||
:title="`Материалы — загружено ${loadedMaterials} из ${state.materials.length}`"
|
||||
name="materials"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-button
|
||||
data-testid="clear-games"
|
||||
text
|
||||
type="error"
|
||||
size="small"
|
||||
:disabled="cannotClearGames"
|
||||
@click.stop="clearGames"
|
||||
>Удалить все скачанные игры</n-button>
|
||||
</template>
|
||||
<div class="materials-list">
|
||||
<p v-if="!state.materials.length">В игре нет материалов.</p>
|
||||
<n-table
|
||||
@@ -459,6 +469,7 @@ const gameOptions = computed(() => {
|
||||
</n-collapse>
|
||||
<p v-if="!state.authenticated">Войдите на сервер, чтобы выбрать игру.</p>
|
||||
<n-button
|
||||
v-if="!selectedKey"
|
||||
class="clear-games"
|
||||
data-testid="clear-games"
|
||||
text
|
||||
@@ -484,7 +495,7 @@ const gameOptions = computed(() => {
|
||||
|
||||
<style scoped>
|
||||
.clear-games {
|
||||
align-self: flex-start;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.materials-list {
|
||||
|
||||
@@ -288,53 +288,6 @@ onUnmounted(() => {
|
||||
</n-tag>
|
||||
</template>
|
||||
<div class="screen-grid">
|
||||
<n-space vertical>
|
||||
<n-alert v-if="host.notice" type="warning">{{ host.notice }}</n-alert>
|
||||
<n-select
|
||||
v-model:value="display"
|
||||
:options="host.displays.map((d) => ({ label: d.label, value: d.id }))"
|
||||
placeholder="Выберите внешний дисплей"
|
||||
:input-props="{ 'aria-label': 'Дисплей показа' }"
|
||||
/>
|
||||
<label for="audio-output-input">Вывод звука</label>
|
||||
<n-select
|
||||
id="audio-output"
|
||||
:value="host.audioOutputId ?? ''"
|
||||
:options="[
|
||||
{ label: 'По умолчанию в системе', value: '' },
|
||||
...(host.audioOutputs ?? []).map((device) => ({ label: device.label, value: device.id })),
|
||||
]"
|
||||
:disabled="isBusy || isCheckingSound"
|
||||
:input-props="{ id: 'audio-output-input', 'aria-label': 'Вывод звука' }"
|
||||
@update:value="selectAudioOutput"
|
||||
/>
|
||||
<n-alert v-if="host.audioOutputNotice" type="warning" role="status">{{ host.audioOutputNotice }}</n-alert>
|
||||
<n-space align="center">
|
||||
<n-button
|
||||
data-testid="check-sound"
|
||||
:disabled="isBusy || isCheckingSound"
|
||||
:loading="isCheckingSound"
|
||||
@click="checkSound"
|
||||
>Проверка звука</n-button>
|
||||
<n-button
|
||||
v-if="state"
|
||||
square
|
||||
:type="state.muted ? 'error' : 'default'"
|
||||
:ghost="state.muted"
|
||||
:disabled="isBusy"
|
||||
:aria-label="state.muted ? 'Включить весь звук' : 'Выключить весь звук'"
|
||||
:title="state.muted ? 'Включить весь звук' : 'Выключить весь звук'"
|
||||
:aria-pressed="state.muted"
|
||||
@click="send({ action: 'mute', enabled: !state.muted })"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M11 5 6 9H3v6h3l5 4z" />
|
||||
<path v-if="state.muted" d="m16 9 6 6m0-6-6 6" />
|
||||
<path v-else d="M15 8a6 6 0 0 1 0 8M18 5a10 10 0 0 1 0 14" />
|
||||
</svg>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
<div v-if="state" class="screen-preview">
|
||||
<img
|
||||
v-if="previewURL"
|
||||
@@ -353,18 +306,66 @@ onUnmounted(() => {
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button
|
||||
v-if="!state?.outputOpen"
|
||||
:disabled="display === null || isBusy"
|
||||
@click="run(() => bridge!.show(display!))"
|
||||
>Открыть показ</n-button>
|
||||
<n-button v-else :disabled="isBusy" @click="run(() => bridge!.closeOutput())"
|
||||
>Закрыть показ</n-button>
|
||||
<n-space vertical>
|
||||
<n-alert v-if="host.notice" type="warning">{{ host.notice }}</n-alert>
|
||||
<label for="display-output-input">Вывод изображения</label>
|
||||
<n-select
|
||||
v-model:value="display"
|
||||
:options="host.displays.map((d) => ({ label: d.label, value: d.id }))"
|
||||
placeholder="Выберите внешний дисплей"
|
||||
:input-props="{ id: 'display-output-input', 'aria-label': 'Дисплей показа' }"
|
||||
/>
|
||||
<label for="audio-output-input">Вывод звука</label>
|
||||
<n-select
|
||||
id="audio-output"
|
||||
:value="host.audioOutputId ?? ''"
|
||||
:options="[
|
||||
{ label: 'По умолчанию в системе', value: '' },
|
||||
...(host.audioOutputs ?? []).map((device) => ({ label: device.label, value: device.id })),
|
||||
]"
|
||||
:disabled="isBusy || isCheckingSound"
|
||||
:input-props="{ id: 'audio-output-input', 'aria-label': 'Вывод звука' }"
|
||||
@update:value="selectAudioOutput"
|
||||
/>
|
||||
<n-alert v-if="host.audioOutputNotice" type="warning" role="status">{{ host.audioOutputNotice }}</n-alert>
|
||||
<div class="screen-actions">
|
||||
<n-space align="center">
|
||||
<n-button
|
||||
data-testid="check-sound"
|
||||
:disabled="isBusy || isCheckingSound"
|
||||
:loading="isCheckingSound"
|
||||
@click="checkSound"
|
||||
>Проверка звука</n-button>
|
||||
<n-button
|
||||
v-if="state"
|
||||
square
|
||||
:type="state.muted ? 'error' : 'default'"
|
||||
:ghost="state.muted"
|
||||
:disabled="isBusy"
|
||||
:aria-label="state.muted ? 'Включить весь звук' : 'Выключить весь звук'"
|
||||
:title="state.muted ? 'Включить весь звук' : 'Выключить весь звук'"
|
||||
:aria-pressed="state.muted"
|
||||
@click="send({ action: 'mute', enabled: !state.muted })"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M11 5 6 9H3v6h3l5 4z" />
|
||||
<path v-if="state.muted" d="m16 9 6 6m0-6-6 6" />
|
||||
<path v-else d="M15 8a6 6 0 0 1 0 8M18 5a10 10 0 0 1 0 14" />
|
||||
</svg>
|
||||
</n-button>
|
||||
</n-space>
|
||||
<div class="output-toggle">
|
||||
<n-button
|
||||
v-if="!state?.outputOpen"
|
||||
:disabled="display === null || isBusy"
|
||||
@click="run(() => bridge!.show(display!))"
|
||||
>Открыть показ</n-button>
|
||||
<n-button v-else :disabled="isBusy" @click="run(() => bridge!.closeOutput())"
|
||||
>Закрыть показ</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
</template>
|
||||
</div>
|
||||
</n-card>
|
||||
<template v-if="state">
|
||||
<section class="presentation-grid">
|
||||
@@ -515,9 +516,15 @@ onUnmounted(() => {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.screen-preview {
|
||||
justify-self: center;
|
||||
width: 85%;
|
||||
.screen-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.output-toggle {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.preview {
|
||||
|
||||
@@ -45,10 +45,22 @@ function run(command: GameCommand) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card title="Игра" data-testid="game-control">
|
||||
<n-card data-testid="game-control">
|
||||
<template #header>
|
||||
<div class="game-heading">
|
||||
<span>Игра</span>
|
||||
<n-tag role="status" data-testid="game-state" :type="state.gameError ? 'warning' : status?.type ?? 'default'">
|
||||
{{ !selectedKey ? 'Игра не выбрана' : status?.label ?? (!state.authenticated ? 'Требуется вход' : state.gameError ? 'Нет данных' : 'Загрузка…') }}
|
||||
</n-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<n-tag role="status" data-testid="game-state" :type="state.gameError ? 'warning' : status?.type ?? 'default'">
|
||||
{{ !selectedKey ? 'Игра не выбрана' : status?.label ?? (!state.authenticated ? 'Требуется вход' : state.gameError ? 'Нет данных' : 'Загрузка…') }}
|
||||
<n-tag
|
||||
role="status"
|
||||
data-testid="game-connection-status"
|
||||
:type="state.connection === 'connected' ? 'success' : 'error'"
|
||||
>
|
||||
{{ state.connection === 'connected' ? 'Подключено' : 'Не подключено' }}
|
||||
</n-tag>
|
||||
</template>
|
||||
<div class="game-grid">
|
||||
@@ -80,6 +92,13 @@ function run(command: GameCommand) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.game-heading {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.game-grid {
|
||||
align-items: start;
|
||||
display: grid;
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
NProgress,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
useDialog,
|
||||
} from 'naive-ui'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
@@ -229,25 +228,31 @@ function remove(trackId?: number) {
|
||||
</template>
|
||||
</n-space>
|
||||
<div v-else class="music-plan-view" data-testid="music-plan-view">
|
||||
<n-select
|
||||
:value="channel.playlistId"
|
||||
:options="localPlaylists.map((p) => ({ label: p.name, value: p.id }))"
|
||||
:disabled="isBusy"
|
||||
placeholder="Выберите плейлист"
|
||||
:input-props="{ 'aria-label': 'Плейлист' }"
|
||||
@update:value="
|
||||
emit('command', { action: 'playlist', channel: 'music', playlistId: $event })
|
||||
"
|
||||
/>
|
||||
<label class="music-loop"
|
||||
>Повторять плейлист
|
||||
<n-switch
|
||||
:value="channel.loop"
|
||||
<div class="playlist-controls">
|
||||
<n-select
|
||||
:value="channel.playlistId"
|
||||
:options="localPlaylists.map((p) => ({ label: p.name, value: p.id }))"
|
||||
:disabled="isBusy"
|
||||
aria-label="Повторять плейлист"
|
||||
@update:value="emit('command', { action: 'loop', channel: 'music', enabled: $event })"
|
||||
placeholder="Выберите плейлист"
|
||||
:input-props="{ 'aria-label': 'Плейлист' }"
|
||||
@update:value="
|
||||
emit('command', { action: 'playlist', channel: 'music', playlistId: $event })
|
||||
"
|
||||
/>
|
||||
</label>
|
||||
<n-button
|
||||
square
|
||||
:type="channel.loop ? 'primary' : 'default'"
|
||||
:disabled="isBusy"
|
||||
:aria-pressed="channel.loop"
|
||||
aria-label="Повторять плейлист"
|
||||
title="Повторять плейлист"
|
||||
@click="emit('command', { action: 'loop', channel: 'music', enabled: !channel.loop })"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="m17 2 4 4-4 4M3 11V8a2 2 0 0 1 2-2h16M7 22l-4-4 4-4m14-1v3a2 2 0 0 1-2 2H3" />
|
||||
</svg>
|
||||
</n-button>
|
||||
</div>
|
||||
<p v-if="channel.source" class="current-track" data-testid="current-music-track">{{ channel.source.name }}</p>
|
||||
<div class="track-actions">
|
||||
<n-space align="center">
|
||||
@@ -395,6 +400,13 @@ function remove(trackId?: number) {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.playlist-controls {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.track-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
@@ -451,7 +463,6 @@ function remove(trackId?: number) {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.music-loop,
|
||||
.track-volume {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NSpace, NTag } from 'naive-ui'
|
||||
import { NButton, NSpace } from 'naive-ui'
|
||||
import type { OnlineIntent, OnlineState } from '../types/online'
|
||||
defineProps<{ state: OnlineState; isBusy: boolean }>()
|
||||
const emit = defineEmits<{ action: [intent: OnlineIntent] }>()
|
||||
@@ -9,16 +9,7 @@ function run(intent: OnlineIntent) {
|
||||
</script>
|
||||
<template>
|
||||
<section class="screen-connection" aria-label="Экран игры">
|
||||
<div class="screen-heading">
|
||||
<span>Экран игры</span>
|
||||
<n-tag
|
||||
role="status"
|
||||
data-testid="game-connection-status"
|
||||
:type="state.connection === 'connected' ? 'success' : 'error'"
|
||||
>
|
||||
{{ state.connection === 'connected' ? 'Подключено' : 'Не подключено' }}
|
||||
</n-tag>
|
||||
</div>
|
||||
<span>Экран игры</span>
|
||||
<slot />
|
||||
<n-space v-if="state.screenId" align="center">
|
||||
<span>Экран №{{ state.screenId }}</span>
|
||||
@@ -58,11 +49,4 @@ function run(intent: OnlineIntent) {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.screen-heading {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user