diff --git a/src/components/GamePage.vue b/src/components/GamePage.vue index 51ad121..a4d4fcd 100644 --- a/src/components/GamePage.vue +++ b/src/components/GamePage.vue @@ -19,7 +19,7 @@ import { type UploadFileInfo, } from 'naive-ui' import { useMessage } from 'naive-ui' -import { computed, ref } from 'vue' +import { computed, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import { getAuthClient } from '@/api/auth_client' @@ -46,11 +46,13 @@ import type { } from '@/api/generated/crabs/evening_detective_server' import GameMapPanel from '@/components/GameMapPanel.vue' import GameScreensPanel from '@/components/GameScreensPanel.vue' +import GameStatePanel from '@/components/GameStatePanel.vue' import GameTeamsPanel from '@/components/GameTeamsPanel.vue' import HeaderMenu from '@/components/HeaderMenu.vue' import TicketCheckModal from '@/components/TicketCheckModal.vue' import UiBlock from '@/components/UiBlock.vue' import { useDeviceContext } from '@/composables/useDeviceContext' +import { useGameState } from '@/composables/useGameState' import { useSimplePolling } from '@/composables/useSimplePolling' import { useAuthStore } from '@/stores/auth' import { useUIStore } from '@/stores/ui' @@ -65,23 +67,30 @@ const isDownloadingQR = ref(false) const router = useRouter() const route = useRoute() const gameId = route.params.id -useDeviceContext(() => ({ organizerGameID: Number(gameId) })) -type GameTab = 'settings' | 'teams' | 'screens' | 'game' | 'payment' | 'answers' + +type GameTab = 'settings' | 'teams' | 'screens' | 'game' | 'payment' | 'answers' | 'state' function isGameTab(value: string): value is GameTab { - return ['settings', 'teams', 'screens', 'game', 'payment', 'answers'].includes(value) + return ['settings', 'teams', 'screens', 'game', 'payment', 'answers', 'state'].includes(value) } const queryTab = typeof route.query.tab === 'string' ? route.query.tab : '' const activeTab = ref(isGameTab(queryTab) ? queryTab : 'game') async function setActiveTab(value: string) { - if (!isGameTab(value)) return + if (!isGameTab(value) || (value === 'state' ? !canViewState.value : !canManageGame.value)) return activeTab.value = value await router.replace({ query: { ...route.query, tab: value } }) if (value === 'answers') await loadGameAnswers() } +function onStateTabKeydown(event: KeyboardEvent) { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + void setActiveTab('state') + } +} + const message = useMessage() const settingsDraft = ref({ name: '', description: '', format: 'online', location: '' }) @@ -119,6 +128,55 @@ const game = ref({ }) const authStore = useAuthStore() const uiStore = useUIStore() +const canManageGame = computed(() => authStore.hasAnyRole(['admin', 'organizer', 'author'])) +const canViewState = computed( + () => + uiStore.environment === 'venue' && + authStore.hasAnyRole(['admin', 'organizer', 'developer']), +) +const isStateOnly = computed(() => canViewState.value && !canManageGame.value) +useDeviceContext(() => (canManageGame.value ? { organizerGameID: Number(gameId) } : {})) +const { devices, serverTime, isLoading, hasError, summary, reload } = useGameState( + () => Number(route.params.id), + () => canViewState.value && activeTab.value === 'state', +) +watch( + [() => route.query.tab, canViewState, canManageGame], + () => { + const tab = typeof route.query.tab === 'string' ? route.query.tab : '' + activeTab.value = isStateOnly.value + ? 'state' + : isGameTab(tab) && (tab !== 'state' || canViewState.value) + ? tab + : 'game' + if (isStateOnly.value && tab !== 'state') { + void router.replace({ query: { ...route.query, tab: 'state' } }) + } + }, + { immediate: true }, +) +watch( + isStateOnly, + (value) => { + if (value) { + game.value = { + ID: undefined, + name: undefined, + description: undefined, + startAt: undefined, + status: undefined, + format: undefined, + location: undefined, + scenario: undefined, + teams: undefined, + export: undefined, + } + void getGame(Number(gameId)) + } + }, + { immediate: true }, +) + const canExport = computed( () => uiStore.environment === 'prod' && authStore.hasAnyRole(['admin', 'organizer', 'developer']), ) @@ -161,7 +219,9 @@ const screenVolume = ref(100) const screenPosition = ref(0) async function getGame(id: number) { + const wasManaging = canManageGame.value const res = await client.GetGame({ ID: id }) + if (wasManaging !== canManageGame.value) return game.value = res.game! if (!settingsDraftInitialized.value) { settingsDraft.value = { @@ -199,7 +259,7 @@ async function getGame(id: number) { return 0 }) } -getGame(Number(gameId)) +if (canManageGame.value) getGame(Number(gameId)) async function getGameScreens() { const res = await client.GetGameScreens({ gameID: Number(gameId) }) @@ -215,7 +275,7 @@ async function getGameScreens() { previewTeams.value = [] } } -getGameScreens() +if (canManageGame.value) getGameScreens() async function getGameMusic() { try { @@ -226,7 +286,7 @@ async function getGameMusic() { message.error(err instanceof Error ? err.message : 'Не удалось получить музыку игры') } } -getGameMusic() +if (canManageGame.value) getGameMusic() function isScreenPlanEditing() { return screenPlanMode.value === 'edit' @@ -248,7 +308,7 @@ async function getScreenPlan() { screenPlan.value = res.plan screenPlanDraft.value = res.plan?.steps?.map((step) => ({ ...step })) || [] } -getScreenPlan() +if (canManageGame.value) getScreenPlan() function addScreenPlanStep() { screenPlanDraft.value.push({ @@ -375,7 +435,7 @@ async function getGameMap() { if (mapSaving.value || mapDragging.value) return mapItems.value = res.items || [] } -getGameMap() +if (canManageGame.value) getGameMap() async function saveMapItem(item: MapItem, hidden = item.hidden) { mapSaving.value = true @@ -749,7 +809,7 @@ async function issueTickets() { } useSimplePolling(() => { - if (isApplyingResults.value) return + if (!canManageGame.value || isApplyingResults.value) return getGame(Number(gameId)) getGameScreens() getGameMusic() @@ -857,7 +917,7 @@ async function saveAllScores() { await loadGameAnswers() } -if (activeTab.value === 'answers') { +if (canManageGame.value && activeTab.value === 'answers') { loadGameAnswers() } @@ -904,7 +964,9 @@ useSimplePolling(() => {