feat(game-ui): 토너먼트 단계별 메인 메뉴를 전환한다
This commit is contained in:
@@ -4,9 +4,9 @@ import { legacyNationTextColor } from '../../utils/legacyNationColor';
|
||||
import MainNavigationLink from './MainNavigationLink.vue';
|
||||
import {
|
||||
buildGlobalNavigation,
|
||||
buildNationNavigation,
|
||||
isNationNavigationEnabled,
|
||||
isNavigationConfigured,
|
||||
nationNavigation,
|
||||
quickNavigation,
|
||||
type MainNavigationLink as MainNavigationLinkItem,
|
||||
type MainNavigationEntry,
|
||||
@@ -18,6 +18,7 @@ import { useMenuPopup } from './useMenuPopup';
|
||||
const props = defineProps<{
|
||||
access: NationNavigationAccess;
|
||||
tournamentStage: number;
|
||||
tournamentType: number | null;
|
||||
nationColor: string;
|
||||
npcMode: number;
|
||||
realtimeEnabled: boolean;
|
||||
@@ -35,6 +36,7 @@ const emit = defineEmits<{
|
||||
|
||||
const { setRoot, openId, close, toggle } = useMenuPopup();
|
||||
const globalEntries = computed(() => buildGlobalNavigation(props.npcMode, props.entries));
|
||||
const nationEntries = computed(() => buildNationNavigation(props.tournamentStage, props.tournamentType));
|
||||
const nationMenuColor = computed(() => props.nationColor || '#000000');
|
||||
const nationMenuTextColor = computed(() => legacyNationTextColor(nationMenuColor.value));
|
||||
const isActive = (link: MainNavigationLinkItem) =>
|
||||
@@ -150,7 +152,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
<span class="dropup-caret" aria-hidden="true"></span>
|
||||
</button>
|
||||
<ul v-if="openId === 'nation'" id="mobile-nation-menu" class="bottom-popup" role="menu">
|
||||
<template v-for="entry in nationNavigation" :key="entry.id">
|
||||
<template v-for="entry in nationEntries" :key="entry.id">
|
||||
<li v-if="entry.kind === 'link'" role="none">
|
||||
<MainNavigationLink
|
||||
:link="entry"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import MainNavigationLink from './MainNavigationLink.vue';
|
||||
import {
|
||||
buildNationNavigation,
|
||||
isNationNavigationEnabled,
|
||||
nationNavigation,
|
||||
type MainNavigationLink as MainNavigationLinkItem,
|
||||
type NationNavigationAccess,
|
||||
} from './mainNavigation';
|
||||
@@ -12,10 +13,12 @@ import { legacyNationTextColor } from '../../utils/legacyNationColor';
|
||||
const props = defineProps<{
|
||||
access: NationNavigationAccess;
|
||||
tournamentStage: number;
|
||||
tournamentType: number | null;
|
||||
nationColor: string;
|
||||
}>();
|
||||
|
||||
const { setRoot, openId, close, toggle } = useMenuPopup();
|
||||
const entries = computed(() => buildNationNavigation(props.tournamentStage, props.tournamentType));
|
||||
const isActive = (link: MainNavigationLinkItem) =>
|
||||
link.highlightStage === props.tournamentStage ||
|
||||
link.highlightStages?.some((stage) => stage === props.tournamentStage) === true;
|
||||
@@ -29,7 +32,7 @@ const isActive = (link: MainNavigationLinkItem) =>
|
||||
:class="{ 'dark-label': legacyNationTextColor(nationColor) === '#000000' }"
|
||||
aria-label="국가 메뉴"
|
||||
>
|
||||
<template v-for="entry in nationNavigation" :key="entry.id">
|
||||
<template v-for="entry in entries" :key="entry.id">
|
||||
<MainNavigationLink
|
||||
v-if="entry.kind === 'link'"
|
||||
:link="entry"
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
RuntimeNavigationLink,
|
||||
} from '@sammo-ts/common/navigation/menuConfig';
|
||||
import defaultNavigationJson from '../../../../../resources/navigation.json';
|
||||
import { resolveTournamentMainPresentation } from '../../utils/tournamentNavigation';
|
||||
|
||||
export type NationAccessRule =
|
||||
'always' | 'meeting' | 'secret' | 'nation-member' | 'nation-established' | 'nation-secret';
|
||||
@@ -11,8 +12,8 @@ export type NationAccessRule =
|
||||
export type MainNavigationLink = RuntimeNavigationLink & {
|
||||
compactLabel?: string;
|
||||
access?: NationAccessRule;
|
||||
highlightStage?: 1 | 6;
|
||||
highlightStages?: readonly [1, 6];
|
||||
highlightStage?: number;
|
||||
highlightStages?: readonly number[];
|
||||
unavailableReason?: string;
|
||||
};
|
||||
|
||||
@@ -155,7 +156,6 @@ export const nationNavigation: MainNavigationEntry[] = [
|
||||
to: '/tournament',
|
||||
newTab: true,
|
||||
access: 'always',
|
||||
highlightStages: [1, 6],
|
||||
},
|
||||
items: [
|
||||
{
|
||||
@@ -246,6 +246,39 @@ export const nationNavigation: MainNavigationEntry[] = [
|
||||
{ kind: 'link', id: 'my-settings', label: '화면 설정', to: '/my-settings', access: 'always' },
|
||||
];
|
||||
|
||||
export const buildNationNavigation = (
|
||||
tournamentStage: number,
|
||||
tournamentType: number | null,
|
||||
source: MainNavigationEntry[] = nationNavigation
|
||||
): MainNavigationEntry[] =>
|
||||
source.map((entry) => {
|
||||
if (entry.kind !== 'split' || entry.id !== 'tournament-betting') return entry;
|
||||
|
||||
const presentation = resolveTournamentMainPresentation(tournamentStage, tournamentType);
|
||||
const main: MainNavigationLink = {
|
||||
...entry.main,
|
||||
label: presentation.label,
|
||||
compactLabel: presentation.compactLabel,
|
||||
to: presentation.to,
|
||||
highlightStage: presentation.active ? tournamentStage : undefined,
|
||||
highlightStages: undefined,
|
||||
};
|
||||
const items = entry.items.map((item) => {
|
||||
if (item.kind !== 'link') return item;
|
||||
if (item.id === 'tournament-menu') {
|
||||
return {
|
||||
...item,
|
||||
highlightStage: presentation.active && !presentation.bettingActive ? tournamentStage : undefined,
|
||||
};
|
||||
}
|
||||
if (item.id === 'betting') {
|
||||
return { ...item, highlightStage: presentation.bettingActive ? tournamentStage : undefined };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return { ...entry, main, items };
|
||||
});
|
||||
|
||||
export const quickNavigation: Array<QuickNavigationItem | MainNavigationDivider> = [
|
||||
{ kind: 'divider', id: 'quick-nation-heading', label: '국가 정보' },
|
||||
{ id: 'policy', label: '방침', tab: 'map', selector: '[data-main-target="policy"]' },
|
||||
@@ -265,7 +298,8 @@ export const quickNavigation: Array<QuickNavigationItem | MainNavigationDivider>
|
||||
{ id: 'diplomacy-message', label: '외교', tab: 'messages', selector: '[data-message-type="diplomacy"]' },
|
||||
];
|
||||
|
||||
export const isNavigationConfigured = (link: MainNavigationLink): boolean => Boolean(link.to || link.href || link.action);
|
||||
export const isNavigationConfigured = (link: MainNavigationLink): boolean =>
|
||||
Boolean(link.to || link.href || link.action);
|
||||
|
||||
export const isNationNavigationEnabled = (link: MainNavigationLink, access: NationNavigationAccess): boolean => {
|
||||
const rule = link.access ?? 'always';
|
||||
|
||||
@@ -49,6 +49,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
type RecentRecord = Awaited<ReturnType<typeof trpc.general.getRecentRecords.query>>['global'][number];
|
||||
type FrontStatus = Awaited<ReturnType<typeof trpc.general.getFrontStatus.query>>;
|
||||
type TournamentState = Awaited<ReturnType<typeof trpc.tournament.getState.query>>;
|
||||
type TournamentType = NonNullable<TournamentState>['type'];
|
||||
type ContextBundleDelta = Awaited<ReturnType<typeof trpc.dashboard.getContextBundleDelta.query>>;
|
||||
type DashboardReadModelPatch = {
|
||||
contextSnapshot?: GeneralContext;
|
||||
@@ -76,6 +77,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
worldHistory?: RecentRecord[];
|
||||
frontStatus?: FrontStatus | null;
|
||||
tournamentStage?: number;
|
||||
tournamentType?: TournamentType | null;
|
||||
};
|
||||
type DashboardTabMessage =
|
||||
{ kind: 'patch'; patch: DashboardReadModelPatch } | { kind: 'status'; status: 'idle' | 'connected' };
|
||||
@@ -117,6 +119,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
const worldHistory = ref<RecentRecord[]>([]);
|
||||
const frontStatus = ref<FrontStatus | null>(null);
|
||||
const tournamentStage = ref(0);
|
||||
const tournamentType = ref<TournamentType | null>(null);
|
||||
const surveyNotice = ref<NonNullable<FrontStatus['latestVote']> | null>(null);
|
||||
let lastGeneralRecordId = 0;
|
||||
let lastWorldHistoryId = 0;
|
||||
@@ -440,6 +443,9 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
if (patch.tournamentStage !== undefined) {
|
||||
tournamentStage.value = patch.tournamentStage;
|
||||
}
|
||||
if (patch.tournamentType !== undefined) {
|
||||
tournamentType.value = patch.tournamentType;
|
||||
}
|
||||
if (patch.contextRevision !== undefined) {
|
||||
contextRevision = patch.contextRevision;
|
||||
contextSourceRevision = patch.contextSourceRevision ?? null;
|
||||
@@ -487,6 +493,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
patch.worldHistory = toRaw(worldHistory.value);
|
||||
patch.frontStatus = toRaw(frontStatus.value);
|
||||
patch.tournamentStage = tournamentStage.value;
|
||||
patch.tournamentType = tournamentType.value;
|
||||
return patch;
|
||||
};
|
||||
|
||||
@@ -637,6 +644,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
}
|
||||
if (tournamentState !== undefined) {
|
||||
tournamentStage.value = tournamentState?.stage ?? 0;
|
||||
tournamentType.value = tournamentState?.type ?? null;
|
||||
}
|
||||
if (initializedMailboxGeneralId !== id) {
|
||||
targetMailbox.value = MESSAGE_MAILBOX_NATIONAL_BASE + context.general.nationId;
|
||||
@@ -765,7 +773,10 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
patch.worldHistory = nextWorldHistory;
|
||||
}
|
||||
if (nextFrontStatus) patch.frontStatus = nextFrontStatus;
|
||||
if (tournamentState !== undefined) patch.tournamentStage = tournamentState?.stage ?? 0;
|
||||
if (tournamentState !== undefined) {
|
||||
patch.tournamentStage = tournamentState?.stage ?? 0;
|
||||
patch.tournamentType = tournamentState?.type ?? null;
|
||||
}
|
||||
applyDashboardPatch(patch);
|
||||
publishDashboardPatch(patch);
|
||||
} catch (err) {
|
||||
@@ -1290,6 +1301,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
worldHistory,
|
||||
frontStatus,
|
||||
tournamentStage,
|
||||
tournamentType,
|
||||
surveyNotice,
|
||||
messageDraftText,
|
||||
targetMailbox,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
export interface TournamentMainPresentation {
|
||||
label: string;
|
||||
compactLabel: string;
|
||||
to: '/tournament' | '/betting';
|
||||
active: boolean;
|
||||
bettingActive: boolean;
|
||||
}
|
||||
|
||||
const tournamentLabels = [
|
||||
{ label: '전 력 전', compactLabel: '전력전' },
|
||||
{ label: '통 솔 전', compactLabel: '통솔전' },
|
||||
{ label: '일 기 토', compactLabel: '일기토' },
|
||||
{ label: '설 전', compactLabel: '설전' },
|
||||
] as const;
|
||||
|
||||
export const resolveTournamentMainPresentation = (
|
||||
tournamentStage: number,
|
||||
tournamentType: number | null
|
||||
): TournamentMainPresentation => {
|
||||
const active = tournamentStage > 0 || tournamentType !== null;
|
||||
const bettingActive = tournamentStage === 6;
|
||||
const tournamentLabel = tournamentType === null ? undefined : tournamentLabels[tournamentType];
|
||||
return {
|
||||
label: bettingActive ? '베 팅 장' : (tournamentLabel?.label ?? '토 너 먼 트'),
|
||||
compactLabel: bettingActive ? '베팅장' : (tournamentLabel?.compactLabel ?? '토너먼트'),
|
||||
to: bettingActive ? '/betting' : '/tournament',
|
||||
active,
|
||||
bettingActive,
|
||||
};
|
||||
};
|
||||
@@ -83,6 +83,7 @@ const {
|
||||
worldHistory,
|
||||
frontStatus,
|
||||
tournamentStage,
|
||||
tournamentType,
|
||||
surveyNotice,
|
||||
messageDraftText,
|
||||
targetMailbox,
|
||||
@@ -301,6 +302,7 @@ watch(
|
||||
class="nation-menu-middle"
|
||||
:access="nationAccess"
|
||||
:tournament-stage="tournamentStage"
|
||||
:tournament-type="tournamentType"
|
||||
:nation-color="nationColor"
|
||||
/>
|
||||
</div>
|
||||
@@ -480,6 +482,7 @@ watch(
|
||||
class="nation-menu-middle"
|
||||
:access="nationAccess"
|
||||
:tournament-stage="tournamentStage"
|
||||
:tournament-type="tournamentType"
|
||||
:nation-color="nationColor"
|
||||
/>
|
||||
<section class="record-zone">
|
||||
@@ -570,6 +573,7 @@ watch(
|
||||
v-if="isMobile"
|
||||
:access="nationAccess"
|
||||
:tournament-stage="tournamentStage"
|
||||
:tournament-type="tournamentType"
|
||||
:nation-color="nationColor"
|
||||
:npc-mode="npcMode"
|
||||
:realtime-enabled="realtimeEnabled"
|
||||
|
||||
Reference in New Issue
Block a user