feat: switch to spa
This commit is contained in:
@@ -1,25 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTitle } from "@vueuse/core";
|
||||
import { gamesApiListEventGames } from "./api";
|
||||
import { gamesApiListEventGames, polylanSubmitterApiGetConfig } from "./api";
|
||||
import type { GamesApiListEventGamesResponse } from "./api/types.gen";
|
||||
|
||||
interface Props {
|
||||
eventPath: string;
|
||||
marketEnabled?: string | boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
marketEnabled: true,
|
||||
});
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
useTitle(computed(() => t("home.title")));
|
||||
|
||||
const games = ref<GamesApiListEventGamesResponse | undefined>();
|
||||
const loading = ref(true);
|
||||
const imageErrors = ref<Set<number>>(new Set());
|
||||
const marketEnabled = ref(false);
|
||||
|
||||
const getHeaderImage = (game: GamesApiListEventGamesResponse[number]) => {
|
||||
if (game.image) {
|
||||
@@ -35,24 +35,25 @@ const onImageError = (gameId: number) => {
|
||||
imageErrors.value.add(gameId);
|
||||
};
|
||||
|
||||
const navigate = (path: string) => {
|
||||
window.location.href = `/${props.eventPath}${path}`;
|
||||
const goToMarket = () => {
|
||||
router.push(`/${props.eventPath}/market`);
|
||||
};
|
||||
|
||||
const isMarketEnabled = () => {
|
||||
if (typeof props.marketEnabled === 'string') {
|
||||
return props.marketEnabled === 'true';
|
||||
}
|
||||
return Boolean(props.marketEnabled);
|
||||
const goToGame = (game: GamesApiListEventGamesResponse[number]) => {
|
||||
const path = game.custom_path ? `/${game.path}` : `/submissions/${game.path}`;
|
||||
router.push(`/${props.eventPath}${path}`);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// Fetch games enabled for this event
|
||||
const response = await gamesApiListEventGames({
|
||||
path: { event_path: props.eventPath },
|
||||
});
|
||||
if (response.data) {
|
||||
games.value = response.data;
|
||||
const [gamesResponse, configResponse] = await Promise.all([
|
||||
gamesApiListEventGames({ path: { event_path: props.eventPath } }),
|
||||
polylanSubmitterApiGetConfig(),
|
||||
]);
|
||||
if (gamesResponse.data) {
|
||||
games.value = gamesResponse.data;
|
||||
}
|
||||
if (configResponse.data) {
|
||||
marketEnabled.value = configResponse.data.market_enabled;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
@@ -77,7 +78,7 @@ onMounted(async () => {
|
||||
<!-- Cards Grid -->
|
||||
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- Market Card -->
|
||||
<div v-if="isMarketEnabled()" @click="navigate('/market')"
|
||||
<div v-if="marketEnabled" @click="goToMarket"
|
||||
class="card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden">
|
||||
<figure class="relative h-60 bg-gradient-to-br from-purple-600 to-blue-600 flex items-center justify-center">
|
||||
<i class="mdi mdi-chart-box text-6xl text-white opacity-80"></i>
|
||||
@@ -97,7 +98,7 @@ onMounted(async () => {
|
||||
|
||||
<!-- Game Cards -->
|
||||
<div v-for="game in games" :key="game.id"
|
||||
@click="navigate(game.custom_path ? `/${game.path}` : `/submissions/${game.path}`)"
|
||||
@click="goToGame(game)"
|
||||
class="card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden">
|
||||
<figure class="relative h-60 bg-base-300 overflow-hidden">
|
||||
<img v-if="getHeaderImage(game) && !imageErrors.has(game.id)" :src="getHeaderImage(game)!" :alt="game.name"
|
||||
@@ -111,7 +112,7 @@ onMounted(async () => {
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl">{{ game.name }}</h2>
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<button @click="navigate(game.path)" class="btn btn-primary">
|
||||
<button class="btn btn-primary">
|
||||
<i class="mdi mdi-arrow-right mr-2"></i>
|
||||
{{ t("home.submitResults") }}
|
||||
</button>
|
||||
|
||||
@@ -20,9 +20,6 @@ import { storeToRefs } from "pinia";
|
||||
|
||||
const props = defineProps<{
|
||||
eventPath: string;
|
||||
collectionTitle: string;
|
||||
collectionUrl: string;
|
||||
collectionDescription: string;
|
||||
}>();
|
||||
|
||||
provideEventPath(props.eventPath);
|
||||
@@ -191,9 +188,9 @@ const reloadPage = () => {
|
||||
<div class="mb-8">
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl">{{ props.collectionTitle }}</h2>
|
||||
<h2 class="card-title text-2xl">{{ collection?.title }}</h2>
|
||||
<p class="text-base-content/70">
|
||||
{{ props.collectionDescription }}
|
||||
{{ collection?.description }}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-4 mt-4">
|
||||
<button @click="openSubmissionModal" class="btn btn-primary" :disabled="isTournamentClosed">
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTitle } from "@vueuse/core";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { submissionsApiCreateSubmission, submissionsApiListUserSubmissions, submissionsApiCreateImageCrop, submissionsApiDeleteImageCrop, submissionsApiProcessFileOcr, submissionsApiProcessCropOcr } from "@/api";
|
||||
import { usePageHeaderStore } from "@/stores/pageHeader";
|
||||
import { gamesApiListEventGames, submissionsApiListAvailableScopes, submissionsApiCreateSubmission, submissionsApiListUserSubmissions, submissionsApiCreateImageCrop, submissionsApiDeleteImageCrop, submissionsApiProcessFileOcr, submissionsApiProcessCropOcr } from "@/api";
|
||||
import { useSubmissionsWebSocket } from "@/composables/useSubmissionsWebSocket";
|
||||
import { provideEventPath } from "@/composables/useEventPath";
|
||||
|
||||
interface Props {
|
||||
eventPath: string;
|
||||
gamePath: string;
|
||||
scopeId: string;
|
||||
scopeName?: string;
|
||||
gameName?: string;
|
||||
gamePath?: string;
|
||||
gameImage?: string;
|
||||
gameSteamId?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
scopeName: "Match",
|
||||
gameName: "Game",
|
||||
gamePath: "",
|
||||
gameImage: "",
|
||||
gameSteamId: "",
|
||||
});
|
||||
const props = defineProps<Props>();
|
||||
|
||||
provideEventPath(props.eventPath);
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
useTitle(computed(() => t("submissions.pageTitle", { game: props.gameName, scope: props.scopeName })));
|
||||
const pageHeaderStore = usePageHeaderStore();
|
||||
|
||||
const scopeName = ref("Match");
|
||||
const gameName = ref("Game");
|
||||
const gameImage = ref("");
|
||||
const gameSteamId = ref("");
|
||||
|
||||
useTitle(computed(() => t("submissions.pageTitle", { game: gameName.value, scope: scopeName.value })));
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
@@ -333,11 +333,11 @@ const processCropOcr = async (cropId: string) => {
|
||||
};
|
||||
|
||||
const getGameHeaderImage = () => {
|
||||
if (props.gameImage) {
|
||||
return props.gameImage;
|
||||
if (gameImage.value) {
|
||||
return gameImage.value;
|
||||
}
|
||||
if (props.gameSteamId) {
|
||||
return `https://cdn.akamai.steamstatic.com/steam/apps/${props.gameSteamId}/header.jpg`;
|
||||
if (gameSteamId.value) {
|
||||
return `https://cdn.akamai.steamstatic.com/steam/apps/${gameSteamId.value}/header.jpg`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -502,10 +502,34 @@ const handlePasteOnUploadArea = (event: ClipboardEvent) => {
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
userStore.loadUserData();
|
||||
loadUserSubmissions();
|
||||
|
||||
const [gamesResponse, scopesResponse] = await Promise.all([
|
||||
gamesApiListEventGames({ path: { event_path: props.eventPath } }),
|
||||
submissionsApiListAvailableScopes({
|
||||
path: { event_path: props.eventPath, game_path: props.gamePath },
|
||||
}),
|
||||
]);
|
||||
|
||||
const game = gamesResponse.data?.find((g) => g.path === props.gamePath);
|
||||
if (game) {
|
||||
gameName.value = game.name;
|
||||
gameImage.value = game.image ?? "";
|
||||
gameSteamId.value = game.steam_app_id ? String(game.steam_app_id) : "";
|
||||
}
|
||||
|
||||
const scope = scopesResponse.data?.find((s) => s.id === props.scopeId);
|
||||
if (!scope) {
|
||||
// Not in the list of scopes this user may access - bounce back to
|
||||
// the picker rather than rendering a page for data we can't show.
|
||||
router.replace({ name: "submissions-select", params: { eventPath: props.eventPath, gamePath: props.gamePath } });
|
||||
return;
|
||||
}
|
||||
scopeName.value = scope.name;
|
||||
pageHeaderStore.setTitle(t("submissions.pageTitle", { game: gameName.value, scope: scopeName.value }));
|
||||
|
||||
// Add paste listener (only processes if hovering upload area)
|
||||
window.addEventListener('paste', handlePasteOnUploadArea);
|
||||
});
|
||||
|
||||
@@ -1,36 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTitle } from "@vueuse/core";
|
||||
|
||||
interface ScopeOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
import { gamesApiListEventGames, submissionsApiListAvailableScopes } from "@/api";
|
||||
import type { ScopeOut } from "@/api/types.gen";
|
||||
|
||||
interface Props {
|
||||
eventPath: string;
|
||||
gamePath: string;
|
||||
gameName?: string;
|
||||
scopes?: ScopeOption[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
gameName: "Game",
|
||||
scopes: () => [],
|
||||
});
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
useTitle(computed(() => t("submissionsSelect.title")));
|
||||
|
||||
const goToScope = (scopeId: string) => {
|
||||
window.location.href = `/${props.eventPath}/submissions/${props.gamePath}/${scopeId}`;
|
||||
const gameName = ref("");
|
||||
const scopes = ref<ScopeOut[]>([]);
|
||||
const loading = ref(true);
|
||||
|
||||
const scopeRoute = (scope: ScopeOut) => ({
|
||||
name: "submissions",
|
||||
params: { eventPath: props.eventPath, gamePath: props.gamePath, scopeId: scope.id },
|
||||
});
|
||||
|
||||
const goToScope = (scope: ScopeOut) => {
|
||||
router.push(scopeRoute(scope));
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const [gamesResponse, scopesResponse] = await Promise.all([
|
||||
gamesApiListEventGames({ path: { event_path: props.eventPath } }),
|
||||
submissionsApiListAvailableScopes({
|
||||
path: { event_path: props.eventPath, game_path: props.gamePath },
|
||||
}),
|
||||
]);
|
||||
|
||||
gameName.value = gamesResponse.data?.find((game) => game.path === props.gamePath)?.name ?? "";
|
||||
|
||||
const availableScopes = scopesResponse.data ?? [];
|
||||
|
||||
// Mirrors the server-side behavior this replaced: skip the picker
|
||||
// straight to the only option, or bounce out if there's nothing to pick.
|
||||
if (availableScopes.length === 1) {
|
||||
router.replace(scopeRoute(availableScopes[0]));
|
||||
return;
|
||||
}
|
||||
if (availableScopes.length === 0) {
|
||||
router.replace({ name: "home", params: { eventPath: props.eventPath } });
|
||||
return;
|
||||
}
|
||||
|
||||
scopes.value = availableScopes;
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 bg-base-200 flex items-center justify-center p-4">
|
||||
<div class="card bg-base-100 shadow-lg w-full max-w-md">
|
||||
<div v-if="loading" class="flex justify-center py-20">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
<div v-else class="card bg-base-100 shadow-lg w-full max-w-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ t("submissionsSelect.title") }}</h2>
|
||||
<p class="text-base-content/70 mb-2">
|
||||
@@ -38,7 +71,7 @@ const goToScope = (scopeId: string) => {
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col gap-2 mt-2">
|
||||
<button v-for="scope in scopes" :key="scope.id" @click="goToScope(scope.id)"
|
||||
<button v-for="scope in scopes" :key="scope.id" @click="goToScope(scope)"
|
||||
class="btn btn-outline justify-start">
|
||||
<i class="mdi mdi-sword-cross mr-2"></i>
|
||||
{{ scope.name }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListEventGames, gamesApiListGames, marketApiCloseMarket, marketApiCreateBet, marketApiListMarkets, marketApiListUserBets, marketApiResolveMarket, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateImageCrop, submissionsApiCreateSubmission, submissionsApiDeleteImageCrop, submissionsApiGetSubmission, submissionsApiListMyScopes, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions, submissionsApiProcessCropOcr, submissionsApiProcessFileOcr } from './sdk.gen';
|
||||
export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, GameOut, GamesApiListEventGamesData, GamesApiListEventGamesResponse, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, ImageCropIn, ImageCropOut, Input, LeaderboardEntryOut, LeaderboardOut, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponse, MarketApiCreateBetResponses, MarketApiListMarketsData, MarketApiListMarketsResponse, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponse, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponse, MarketApiResolveMarketResponses, MarketListSchema, MarketOptionSchema, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResolveMarketSchema, ResultsOut, ScopeOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponse, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionError, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponse, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesError, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponse, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsError, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponse, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponse, SubmissionsApiProcessFileOcrResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserBetCreateSchema, UserBetSchema, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen';
|
||||
export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListEventGames, gamesApiListGames, marketApiCloseMarket, marketApiCreateBet, marketApiListMarkets, marketApiListUserBets, marketApiResolveMarket, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetConfig, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateImageCrop, submissionsApiCreateSubmission, submissionsApiDeleteImageCrop, submissionsApiGetSubmission, submissionsApiListAvailableScopes, submissionsApiListMyScopes, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions, submissionsApiProcessCropOcr, submissionsApiProcessFileOcr } from './sdk.gen';
|
||||
export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, ConfigOut, GameOut, GamesApiListEventGamesData, GamesApiListEventGamesResponse, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, ImageCropIn, ImageCropOut, Input, LeaderboardEntryOut, LeaderboardOut, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponse, MarketApiCreateBetResponses, MarketApiListMarketsData, MarketApiListMarketsResponse, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponse, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponse, MarketApiResolveMarketResponses, MarketListSchema, MarketOptionSchema, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetConfigData, PolylanSubmitterApiGetConfigResponse, PolylanSubmitterApiGetConfigResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResolveMarketSchema, ResultsOut, ScopeOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponse, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionError, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponse, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesError, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponse, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesError, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponse, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsError, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponse, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponse, SubmissionsApiProcessFileOcrResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserBetCreateSchema, UserBetSchema, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client';
|
||||
import { client } from './client.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, GamesApiListEventGamesData, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponses, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponses, MarketApiListMarketsData, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponses } from './types.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, GamesApiListEventGamesData, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponses, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponses, MarketApiListMarketsData, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetConfigData, PolylanSubmitterApiGetConfigResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponses } from './types.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
|
||||
/**
|
||||
@@ -25,6 +25,11 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
|
||||
*/
|
||||
export const polylanSubmitterApiHealthCheck = <ThrowOnError extends boolean = false>(options?: Options<PolylanSubmitterApiHealthCheckData, ThrowOnError>) => (options?.client ?? client).get<PolylanSubmitterApiHealthCheckResponses, unknown, ThrowOnError>({ url: '/api/health', ...options });
|
||||
|
||||
/**
|
||||
* Get Config
|
||||
*/
|
||||
export const polylanSubmitterApiGetConfig = <ThrowOnError extends boolean = false>(options?: Options<PolylanSubmitterApiGetConfigData, ThrowOnError>) => (options?.client ?? client).get<PolylanSubmitterApiGetConfigResponses, unknown, ThrowOnError>({ url: '/api/config', ...options });
|
||||
|
||||
/**
|
||||
* Clear Cache
|
||||
*
|
||||
@@ -201,6 +206,17 @@ export const gamesApiListEventGames = <ThrowOnError extends boolean = false>(opt
|
||||
*/
|
||||
export const submissionsApiListMyScopes = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListMyScopesData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListMyScopesResponses, SubmissionsApiListMyScopesErrors, ThrowOnError>({ url: '/api/events/{event_path}/submissions/scopes/mine', ...options });
|
||||
|
||||
/**
|
||||
* List Available Scopes
|
||||
*
|
||||
* Scopes the current user may submit to for this game - superusers see
|
||||
* every active scope, everyone else only the ones assigned to them. Used
|
||||
* by the submissions-select and submissions-detail pages, both to list
|
||||
* scopes and (for the detail page) to confirm the requested scope_id is
|
||||
* one the current user is actually allowed to see.
|
||||
*/
|
||||
export const submissionsApiListAvailableScopes = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListAvailableScopesData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListAvailableScopesResponses, SubmissionsApiListAvailableScopesErrors, ThrowOnError>({ url: '/api/events/{event_path}/submissions/{game_path}/available-scopes', ...options });
|
||||
|
||||
/**
|
||||
* Create Submission
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,16 @@ export type ClientOptions = {
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* ConfigOut
|
||||
*/
|
||||
export type ConfigOut = {
|
||||
/**
|
||||
* Market Enabled
|
||||
*/
|
||||
market_enabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* UserInfoOut
|
||||
*
|
||||
@@ -1125,6 +1135,22 @@ export type PolylanSubmitterApiHealthCheckResponses = {
|
||||
200: unknown;
|
||||
};
|
||||
|
||||
export type PolylanSubmitterApiGetConfigData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/config';
|
||||
};
|
||||
|
||||
export type PolylanSubmitterApiGetConfigResponses = {
|
||||
/**
|
||||
* OK
|
||||
*/
|
||||
200: ConfigOut;
|
||||
};
|
||||
|
||||
export type PolylanSubmitterApiGetConfigResponse = PolylanSubmitterApiGetConfigResponses[keyof PolylanSubmitterApiGetConfigResponses];
|
||||
|
||||
export type PolylanSubmitterApiClearCacheData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
@@ -1668,6 +1694,46 @@ export type SubmissionsApiListMyScopesResponses = {
|
||||
|
||||
export type SubmissionsApiListMyScopesResponse = SubmissionsApiListMyScopesResponses[keyof SubmissionsApiListMyScopesResponses];
|
||||
|
||||
export type SubmissionsApiListAvailableScopesData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Event Path
|
||||
*/
|
||||
event_path: string;
|
||||
/**
|
||||
* Game Path
|
||||
*/
|
||||
game_path: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/events/{event_path}/submissions/{game_path}/available-scopes';
|
||||
};
|
||||
|
||||
export type SubmissionsApiListAvailableScopesErrors = {
|
||||
/**
|
||||
* Response
|
||||
*
|
||||
* Forbidden
|
||||
*/
|
||||
403: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type SubmissionsApiListAvailableScopesError = SubmissionsApiListAvailableScopesErrors[keyof SubmissionsApiListAvailableScopesErrors];
|
||||
|
||||
export type SubmissionsApiListAvailableScopesResponses = {
|
||||
/**
|
||||
* Response
|
||||
*
|
||||
* OK
|
||||
*/
|
||||
200: Array<ScopeOut>;
|
||||
};
|
||||
|
||||
export type SubmissionsApiListAvailableScopesResponse = SubmissionsApiListAvailableScopesResponses[keyof SubmissionsApiListAvailableScopesResponses];
|
||||
|
||||
export type SubmissionsApiCreateSubmissionData = {
|
||||
/**
|
||||
* MultiPartBodyParams
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Per-page data Django injects via `{{ bootstrap|json_script:"bootstrap-data" }}`
|
||||
// in templates/app.html. Read once at boot, before the router/app are created.
|
||||
export const bootstrap: Record<string, unknown> = (() => {
|
||||
const el = document.getElementById("bootstrap-data");
|
||||
if (!el?.textContent) return {};
|
||||
try {
|
||||
return JSON.parse(el.textContent);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
})();
|
||||
@@ -1,30 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { usePageHeaderStore } from "@/stores/pageHeader";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
const pageHeaderStore = usePageHeaderStore();
|
||||
const { title: dynamicTitle } = storeToRefs(pageHeaderStore);
|
||||
|
||||
const title = computed(() => {
|
||||
if (dynamicTitle.value) return dynamicTitle.value;
|
||||
const key = route.meta.titleKey;
|
||||
if (!key) return t("common.appName");
|
||||
return t(key, {
|
||||
game: (bootstrap.gameName as string) ?? "",
|
||||
scope: (bootstrap.scopeName as string) ?? "",
|
||||
});
|
||||
return key ? t(key) : t("common.appName");
|
||||
});
|
||||
|
||||
const backPath = computed(() => route.meta.getBackPath?.(route));
|
||||
|
||||
const goBack = () => {
|
||||
if (backPath.value) {
|
||||
window.location.href = backPath.value;
|
||||
const backRoute = route.meta.getBackRoute?.(route);
|
||||
if (backRoute) {
|
||||
router.push(backRoute);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -32,7 +31,7 @@ const goBack = () => {
|
||||
<template>
|
||||
<div class="navbar bg-base-100 shadow-lg">
|
||||
<div class="container min-w-3/4 mx-auto w-full flex items-center gap-4">
|
||||
<button v-if="backPath" @click="goBack" class="btn btn-primary btn-sm">
|
||||
<button v-if="route.meta.getBackRoute" @click="goBack" class="btn btn-primary btn-sm">
|
||||
<i class="mdi mdi-arrow-left"></i>
|
||||
{{ t("common.back") }}
|
||||
</button>
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
import { createRouter, createWebHistory, type RouteLocationNormalizedLoaded } from "vue-router";
|
||||
import { createRouter, createWebHistory, type RouteLocationNormalizedLoaded, type RouteLocationRaw } from "vue-router";
|
||||
import Home from "@/Home.vue";
|
||||
import Market from "@/Market.vue";
|
||||
import Noita from "@/Noita.vue";
|
||||
import OpusMagnum from "@/OpusMagnum.vue";
|
||||
import Submissions from "@/Submissions.vue";
|
||||
import SubmissionsSelect from "@/SubmissionsSelect.vue";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { usePageHeaderStore } from "@/stores/pageHeader";
|
||||
|
||||
declare module "vue-router" {
|
||||
interface RouteMeta {
|
||||
// Static label for AppHeader's title bar. Pages whose title depends on
|
||||
// fetched data (e.g. submissions) set one at runtime via
|
||||
// usePageHeaderStore() instead and leave this out.
|
||||
titleKey?: string;
|
||||
getBackPath?: (route: RouteLocationNormalizedLoaded) => string;
|
||||
getBackRoute?: (route: RouteLocationNormalizedLoaded) => RouteLocationRaw;
|
||||
}
|
||||
}
|
||||
|
||||
// Route params (eventPath, gamePath, scopeId, ...) come from the URL;
|
||||
// page-specific extras Django can't express as URL segments (collection
|
||||
// title, scope name, ...) come from the bootstrap JSON island instead.
|
||||
const withBootstrap = (route: RouteLocationNormalizedLoaded) => ({
|
||||
...route.params,
|
||||
...bootstrap,
|
||||
const eventHome = (route: RouteLocationNormalizedLoaded): RouteLocationRaw => ({
|
||||
name: "home",
|
||||
params: { eventPath: route.params.eventPath },
|
||||
});
|
||||
|
||||
const eventHome = (route: RouteLocationNormalizedLoaded) => `/${route.params.eventPath}/`;
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory("/"),
|
||||
routes: [
|
||||
@@ -31,45 +29,55 @@ export const router = createRouter({
|
||||
path: "/:eventPath/",
|
||||
name: "home",
|
||||
component: Home,
|
||||
props: withBootstrap,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/:eventPath/market",
|
||||
name: "market",
|
||||
component: Market,
|
||||
props: withBootstrap,
|
||||
meta: { titleKey: "market.header", getBackPath: eventHome },
|
||||
props: true,
|
||||
meta: { titleKey: "market.header", getBackRoute: eventHome },
|
||||
},
|
||||
{
|
||||
path: "/:eventPath/opus-magnum",
|
||||
name: "opus-magnum",
|
||||
component: OpusMagnum,
|
||||
props: withBootstrap,
|
||||
meta: { titleKey: "opusMagnum.header", getBackPath: eventHome },
|
||||
props: true,
|
||||
meta: { titleKey: "opusMagnum.header", getBackRoute: eventHome },
|
||||
},
|
||||
{
|
||||
path: "/:eventPath/noita",
|
||||
name: "noita",
|
||||
component: Noita,
|
||||
props: withBootstrap,
|
||||
meta: { titleKey: "noita.header", getBackPath: eventHome },
|
||||
props: true,
|
||||
meta: { titleKey: "noita.header", getBackRoute: eventHome },
|
||||
},
|
||||
{
|
||||
path: "/:eventPath/submissions/:gamePath",
|
||||
name: "submissions-select",
|
||||
component: SubmissionsSelect,
|
||||
props: withBootstrap,
|
||||
meta: { titleKey: "submissionsSelect.title", getBackPath: eventHome },
|
||||
props: true,
|
||||
meta: { titleKey: "submissionsSelect.title", getBackRoute: eventHome },
|
||||
},
|
||||
{
|
||||
path: "/:eventPath/submissions/:gamePath/:scopeId",
|
||||
name: "submissions",
|
||||
component: Submissions,
|
||||
props: withBootstrap,
|
||||
props: true,
|
||||
meta: {
|
||||
titleKey: "submissions.pageTitle",
|
||||
getBackPath: (route) => `/${route.params.eventPath}/submissions/${route.params.gamePath}`,
|
||||
// No static titleKey: Submissions.vue sets a dynamic
|
||||
// "{game} Submissions — {scope}" title once it has fetched both.
|
||||
getBackRoute: (route) => ({
|
||||
name: "submissions-select",
|
||||
params: { eventPath: route.params.eventPath, gamePath: route.params.gamePath },
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Every route change starts from a clean header title; pages that need a
|
||||
// dynamic one (see submissions above) set it again once their data lands.
|
||||
router.beforeEach(() => {
|
||||
usePageHeaderStore().clear();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
// Lets a routed page hand AppHeader a title built from data only that page
|
||||
// has fetched (e.g. "Noita Submissions — Semifinal", assembled from an API
|
||||
// call keyed by the scopeId route param). Cleared on every navigation so a
|
||||
// stale title never lingers while the next page is still loading.
|
||||
export const usePageHeaderStore = defineStore("pageHeader", () => {
|
||||
const title = ref("");
|
||||
|
||||
const setTitle = (value: string) => {
|
||||
title.value = value;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
title.value = "";
|
||||
};
|
||||
|
||||
return { title, setTitle, clear };
|
||||
});
|
||||
Reference in New Issue
Block a user