add URL-based tab routing with pushState sync
This commit is contained in:
parent
5b0d41a020
commit
af27f0fbbc
3 changed files with 42 additions and 4 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, getContext, setContext, onDestroy } from 'svelte'
|
import { onMount, getContext, setContext, onDestroy } from 'svelte'
|
||||||
|
import { pushState } from '$app/navigation'
|
||||||
|
import { page } from '$app/state'
|
||||||
import type { Party, GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
import type { Party, GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
||||||
import { partyStore } from '$lib/stores/partyStore.svelte'
|
import { partyStore } from '$lib/stores/partyStore.svelte'
|
||||||
|
|
||||||
|
|
@ -65,9 +67,10 @@
|
||||||
party?: Party
|
party?: Party
|
||||||
canEdit?: boolean
|
canEdit?: boolean
|
||||||
authUserId?: string
|
authUserId?: string
|
||||||
|
initialTab?: GridType
|
||||||
}
|
}
|
||||||
|
|
||||||
let { party: initial, canEdit: canEditServer = false, authUserId }: Props = $props()
|
let { party: initial, canEdit: canEditServer = false, authUserId, initialTab }: Props = $props()
|
||||||
|
|
||||||
// Per-route local state using Svelte 5 runes
|
// Per-route local state using Svelte 5 runes
|
||||||
const defaultParty: Party = {
|
const defaultParty: Party = {
|
||||||
|
|
@ -95,7 +98,24 @@
|
||||||
partyStore.clear()
|
partyStore.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
let activeTab = $state<GridType>(GridType.Weapon)
|
let activeTab = $state<GridType>(initialTab ?? GridType.Weapon)
|
||||||
|
|
||||||
|
// Map URL segment to GridType for back/forward navigation
|
||||||
|
const tabMap: Record<string, GridType> = {
|
||||||
|
weapons: GridType.Weapon,
|
||||||
|
summons: GridType.Summon,
|
||||||
|
characters: GridType.Character
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync activeTab with URL when user navigates back/forward
|
||||||
|
$effect(() => {
|
||||||
|
const urlTab = page.params.tab as string | undefined
|
||||||
|
const expectedTab = urlTab ? (tabMap[urlTab] ?? GridType.Weapon) : GridType.Weapon
|
||||||
|
if (activeTab !== expectedTab) {
|
||||||
|
activeTab = expectedTab
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
let error = $state<string | null>(null)
|
let error = $state<string | null>(null)
|
||||||
let selectedSlot = $state<number>(0)
|
let selectedSlot = $state<number>(0)
|
||||||
|
|
@ -259,6 +279,12 @@
|
||||||
|
|
||||||
function handleTabChange(tab: GridType) {
|
function handleTabChange(tab: GridType) {
|
||||||
activeTab = tab
|
activeTab = tab
|
||||||
|
|
||||||
|
// Update URL with push state (adds to browser history)
|
||||||
|
const basePath = `/teams/${party.shortcode}`
|
||||||
|
const newPath = tab === GridType.Weapon ? basePath : `${basePath}/${tab}s`
|
||||||
|
pushState(newPath, {})
|
||||||
|
|
||||||
// Update selectedSlot to the first valid empty slot for this tab
|
// Update selectedSlot to the first valid empty slot for this tab
|
||||||
const nextEmpty = findNextEmptySlot(party, tab)
|
const nextEmpty = findNextEmptySlot(party, tab)
|
||||||
if (nextEmpty !== SLOT_NOT_FOUND) {
|
if (nextEmpty !== SLOT_NOT_FOUND) {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
||||||
party: party ? structuredClone(party) : null,
|
party: party ? structuredClone(party) : null,
|
||||||
canEdit: Boolean(canEdit),
|
canEdit: Boolean(canEdit),
|
||||||
partyFound,
|
partyFound,
|
||||||
authUserId: authUserId || null
|
authUserId: authUserId || null,
|
||||||
|
tab: params.tab || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,20 @@
|
||||||
import { createQuery } from '@tanstack/svelte-query'
|
import { createQuery } from '@tanstack/svelte-query'
|
||||||
import { partyQueries } from '$lib/api/queries/party.queries'
|
import { partyQueries } from '$lib/api/queries/party.queries'
|
||||||
import { withInitialData } from '$lib/query/ssr'
|
import { withInitialData } from '$lib/query/ssr'
|
||||||
|
import { GridType } from '$lib/types/enums'
|
||||||
|
|
||||||
let { data }: { data: PageData } = $props()
|
let { data }: { data: PageData } = $props()
|
||||||
|
|
||||||
|
// Map URL segment to GridType
|
||||||
|
const tabMap: Record<string, GridType> = {
|
||||||
|
weapons: GridType.Weapon,
|
||||||
|
summons: GridType.Summon,
|
||||||
|
characters: GridType.Character
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize from URL or default to Weapon
|
||||||
|
const initialTab = data.tab ? (tabMap[data.tab] ?? GridType.Weapon) : GridType.Weapon
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TanStack Query v6 SSR Integration Example
|
* TanStack Query v6 SSR Integration Example
|
||||||
*
|
*
|
||||||
|
|
@ -33,7 +44,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if party}
|
{#if party}
|
||||||
<Party party={party} canEdit={data.canEdit || false} authUserId={data.authUserId} />
|
<Party party={party} canEdit={data.canEdit || false} authUserId={data.authUserId} {initialTab} />
|
||||||
{:else}
|
{:else}
|
||||||
<div>
|
<div>
|
||||||
<h1>Party not found</h1>
|
<h1>Party not found</h1>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue