diff --git a/src/lib/api/adapters/__tests__/entity.adapter.test.ts b/src/lib/api/adapters/__tests__/entity.adapter.test.ts index 95029209..5c9e56aa 100644 --- a/src/lib/api/adapters/__tests__/entity.adapter.test.ts +++ b/src/lib/api/adapters/__tests__/entity.adapter.test.ts @@ -29,7 +29,7 @@ describe('EntityAdapter', () => { name: { en: 'Dark Opus', ja: 'ダークオーパス' }, hasWeaponKeys: true, hasAwakening: true, - augmentType: 'none', + augmentType: 'no_augment', extra: false, elementChangeable: false }, diff --git a/src/lib/api/adapters/__tests__/grid.adapter.test.ts b/src/lib/api/adapters/__tests__/grid.adapter.test.ts index 1c39aed7..3b12d270 100644 --- a/src/lib/api/adapters/__tests__/grid.adapter.test.ts +++ b/src/lib/api/adapters/__tests__/grid.adapter.test.ts @@ -29,7 +29,7 @@ describe('GridAdapter', () => { name: { en: 'Dark Opus', ja: 'ダークオーパス' }, hasWeaponKeys: true, hasAwakening: true, - augmentType: 'none', + augmentType: 'no_augment', extra: false, elementChangeable: false }, diff --git a/src/lib/components/collection/WeaponEditPane.svelte b/src/lib/components/collection/WeaponEditPane.svelte index 0ffa714c..1e91e251 100644 --- a/src/lib/components/collection/WeaponEditPane.svelte +++ b/src/lib/components/collection/WeaponEditPane.svelte @@ -114,7 +114,7 @@ const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0) // Augment type from series determines AX skills vs befoulment - const augmentType = $derived(series?.augmentType ?? 'none') + const augmentType = $derived(series?.augmentType ?? 'no_augment') const hasAxSkills = $derived(augmentType === 'ax') const hasBefoulment = $derived(augmentType === 'befoulment') const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0) diff --git a/src/lib/components/sidebar/EditWeaponSidebar.svelte b/src/lib/components/sidebar/EditWeaponSidebar.svelte index 08b9b351..f78a582a 100644 --- a/src/lib/components/sidebar/EditWeaponSidebar.svelte +++ b/src/lib/components/sidebar/EditWeaponSidebar.svelte @@ -82,7 +82,7 @@ const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0) // Augment type from series determines AX skills vs befoulment - const augmentType = $derived(series?.augmentType ?? 'none') + const augmentType = $derived(series?.augmentType ?? 'no_augment') const hasAxSkills = $derived(augmentType === 'ax') const hasBefoulment = $derived(augmentType === 'befoulment') const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0) diff --git a/src/lib/types/api/weaponSeries.ts b/src/lib/types/api/weaponSeries.ts index fc301b99..16021637 100644 --- a/src/lib/types/api/weaponSeries.ts +++ b/src/lib/types/api/weaponSeries.ts @@ -20,7 +20,7 @@ export interface WeaponSeriesRef { name: { en: string; ja: string } hasWeaponKeys: boolean hasAwakening: boolean - /** Type of augment this series supports: "ax", "befoulment", or "none" */ + /** Type of augment this series supports: "ax", "befoulment", or "no_augment" */ augmentType: AugmentType extra: boolean elementChangeable: boolean @@ -40,7 +40,7 @@ export interface WeaponSeries { elementChangeable: boolean hasWeaponKeys: boolean hasAwakening: boolean - /** Type of augment this series supports: "ax", "befoulment", or "none" */ + /** Type of augment this series supports: "ax", "befoulment", or "no_augment" */ augmentType: AugmentType // Only included in :full view (show endpoint) weaponCount?: number diff --git a/src/lib/types/api/weaponStatModifier.ts b/src/lib/types/api/weaponStatModifier.ts index a6dd6547..56f3348a 100644 --- a/src/lib/types/api/weaponStatModifier.ts +++ b/src/lib/types/api/weaponStatModifier.ts @@ -11,7 +11,7 @@ * Augment type enum for weapon series. * Determines whether a weapon series supports AX skills, befoulments, or neither. */ -export type AugmentType = 'none' | 'ax' | 'befoulment' +export type AugmentType = 'no_augment' | 'ax' | 'befoulment' /** * WeaponStatModifier from the API. diff --git a/src/lib/utils/augmentType.ts b/src/lib/utils/augmentType.ts new file mode 100644 index 00000000..545dda45 --- /dev/null +++ b/src/lib/utils/augmentType.ts @@ -0,0 +1,42 @@ +import type { AugmentType } from '$lib/types/api/weaponStatModifier' + +interface AugmentTypeData { + en: string + ja: string +} + +export const AUGMENT_TYPES: Record = { + no_augment: { en: 'None', ja: 'なし' }, + ax: { en: 'AX Skills', ja: 'AXスキル' }, + befoulment: { en: 'Befoulment', ja: '禍スキル' } +} + +export const AUGMENT_TYPE_LABELS: Record = { + no_augment: 'None', + ax: 'AX Skills', + befoulment: 'Befoulment' +} + +export function getAugmentTypeLabel(type?: AugmentType): string { + if (!type) return '—' + return AUGMENT_TYPE_LABELS[type] || '—' +} + +export function getAugmentTypeName(type?: AugmentType, locale: 'en' | 'ja' = 'en'): string { + if (!type) return '—' + const data = AUGMENT_TYPES[type] + if (!data) return '—' + return data[locale] +} + +export function getAugmentTypeOptions(): Array<{ value: AugmentType; label: string }> { + return Object.entries(AUGMENT_TYPE_LABELS).map(([value, label]) => ({ + value: value as AugmentType, + label + })) +} + +export function getAugmentTypeClass(type?: AugmentType): string { + if (!type || type === 'no_augment') return '' + return `augment-${type}` +} diff --git a/src/lib/utils/modificationDetector.ts b/src/lib/utils/modificationDetector.ts index 975fbee5..e5e22dc2 100644 --- a/src/lib/utils/modificationDetector.ts +++ b/src/lib/utils/modificationDetector.ts @@ -116,8 +116,8 @@ export function canWeaponBeModified(gridWeapon: GridWeapon | undefined): boolean const hasWeaponKeys = seriesHasWeaponKeys(weapon.series) // AX skills or Befoulment - check augmentType from series - const augmentType = weapon.series?.augmentType ?? 'none' - const hasAugments = augmentType !== 'none' + const augmentType = weapon.series?.augmentType ?? 'no_augment' + const hasAugments = augmentType !== 'no_augment' // Awakening (maxAwakeningLevel > 0 means it can have awakening) const hasAwakening = (weapon.maxAwakeningLevel ?? 0) > 0 diff --git a/src/routes/(app)/database/series/+page.svelte b/src/routes/(app)/database/series/+page.svelte deleted file mode 100644 index e233afa9..00000000 --- a/src/routes/(app)/database/series/+page.svelte +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - -
-
- - - {#if activeQuery.isPending} -
Loading {activeType} series...
- {:else if activeQuery.error} -
Failed to load {activeType} series
- {:else if sortedData.length > 0} -
- - - - - - - - {#if hasFlags} - - {/if} - - - - {#each sortedData as series (series.id)} - - - - - - {#if hasFlags && 'extra' in series} - - {/if} - - {/each} - -
OrderName (EN)Name (JA)SlugFlags
{series.order}{series.name.en}{series.name.ja}{series.slug} - {#if series.extra}Extra{/if} - {#if series.elementChangeable}Element{/if} - {#if series.hasWeaponKeys}Keys{/if} - {#if series.hasAwakening}Awaken{/if} - {#if series.hasAxSkills}AX{/if} -
-
- {:else} -
No {activeType} series found
- {/if} -
-
- - diff --git a/src/routes/(app)/database/series/weapons/[slug]/+page.server.ts b/src/routes/(app)/database/series/weapons/[slug]/+page.server.ts new file mode 100644 index 00000000..6f9dd75f --- /dev/null +++ b/src/routes/(app)/database/series/weapons/[slug]/+page.server.ts @@ -0,0 +1,28 @@ +import type { PageServerLoad } from './$types' +import { entityAdapter } from '$lib/api/adapters/entity.adapter' +import { error } from '@sveltejs/kit' + +export const load: PageServerLoad = async ({ params, parent }) => { + const parentData = await parent() + + try { + const series = await entityAdapter.getWeaponSeries(params.slug) + + if (!series) { + throw error(404, 'Weapon series not found') + } + + return { + series, + role: parentData.role + } + } catch (err) { + console.error('Failed to load weapon series:', err) + + if (err instanceof Error && 'status' in err && err.status === 404) { + throw error(404, 'Weapon series not found') + } + + throw error(500, 'Failed to load weapon series') + } +} diff --git a/src/routes/(app)/database/series/weapons/[slug]/+page.svelte b/src/routes/(app)/database/series/weapons/[slug]/+page.svelte new file mode 100644 index 00000000..2cb0c6cf --- /dev/null +++ b/src/routes/(app)/database/series/weapons/[slug]/+page.svelte @@ -0,0 +1,110 @@ + + + + + + +
+ + {#snippet rightAction()} + {#if canEdit && editUrl} + + {/if} + {/snippet} + + + {#if series} +
+ + + + + + + + + + + + + + + + {#if series.weaponCount !== undefined} + + + + {/if} +
+ {:else} +
+

Series Not Found

+

The weapon series you're looking for could not be found.

+ +
+ {/if} +
+ + diff --git a/src/routes/(app)/database/series/weapons/[slug]/edit/+page.server.ts b/src/routes/(app)/database/series/weapons/[slug]/edit/+page.server.ts new file mode 100644 index 00000000..92e1ca84 --- /dev/null +++ b/src/routes/(app)/database/series/weapons/[slug]/edit/+page.server.ts @@ -0,0 +1,33 @@ +import type { PageServerLoad } from './$types' +import { entityAdapter } from '$lib/api/adapters/entity.adapter' +import { error, redirect } from '@sveltejs/kit' + +export const load: PageServerLoad = async ({ params, parent }) => { + const parentData = await parent() + + // Role check - must be editor level (>= 7) to edit + if (!parentData.role || parentData.role < 7) { + throw redirect(303, `/database/series/weapons/${params.slug}`) + } + + try { + const series = await entityAdapter.getWeaponSeries(params.slug) + + if (!series) { + throw error(404, 'Weapon series not found') + } + + return { + series, + role: parentData.role + } + } catch (err) { + console.error('Failed to load weapon series:', err) + + if (err instanceof Error && 'status' in err && err.status === 404) { + throw error(404, 'Weapon series not found') + } + + throw error(500, 'Failed to load weapon series') + } +} diff --git a/src/routes/(app)/database/series/weapons/[slug]/edit/+page.svelte b/src/routes/(app)/database/series/weapons/[slug]/edit/+page.svelte new file mode 100644 index 00000000..ba9ecb33 --- /dev/null +++ b/src/routes/(app)/database/series/weapons/[slug]/edit/+page.svelte @@ -0,0 +1,252 @@ + + + + + + +
+ + {#snippet rightAction()} + + {/snippet} + + + {#if series} +
+ {#if saveError} +
{saveError}
+ {/if} + + + + + + + + + + + + + + + +
+ {:else} +
+

Series Not Found

+

The weapon series you're looking for could not be found.

+ +
+ {/if} +
+ + diff --git a/src/routes/(app)/database/weapons/+page.svelte b/src/routes/(app)/database/weapons/+page.svelte index 2e6b05a2..99996067 100644 --- a/src/routes/(app)/database/weapons/+page.svelte +++ b/src/routes/(app)/database/weapons/+page.svelte @@ -4,6 +4,7 @@ import PageMeta from '$lib/components/PageMeta.svelte' import * as m from '$lib/paraglide/messages' import { goto } from '$app/navigation' + import { page } from '$app/stores' import { createQuery } from '@tanstack/svelte-query' import { entityQueries } from '$lib/api/queries/entity.queries' import DatabaseGridWithProvider from '$lib/components/database/DatabaseGridWithProvider.svelte' @@ -17,8 +18,19 @@ import LastUpdatedCell from '$lib/components/database/cells/LastUpdatedCell.svelte' import { getRarityLabel } from '$lib/utils/rarity' - // View mode state - let viewMode = $state<'weapons' | 'series'>('weapons') + // View mode state - read initial value from URL + const initialView = $page.url.searchParams.get('view') + let viewMode = $state<'weapons' | 'series'>(initialView === 'series' ? 'series' : 'weapons') + + // Sync viewMode changes to URL + $effect(() => { + const currentView = $page.url.searchParams.get('view') + if (viewMode === 'series' && currentView !== 'series') { + goto('?view=series', { replaceState: true, noScroll: true }) + } else if (viewMode === 'weapons' && currentView === 'series') { + goto('/database/weapons', { replaceState: true, noScroll: true }) + } + }) // Query for weapon series const weaponSeriesQuery = createQuery(() => entityQueries.weaponSeriesList()) @@ -30,8 +42,8 @@ }) // Navigate to series detail - function handleSeriesClick(seriesId: string) { - goto(`/database/series/${seriesId}`) + function handleSeriesClick(slug: string) { + goto(`/database/series/weapons/${slug}`) } // Column configuration for weapons @@ -130,7 +142,7 @@ {#each sortedSeries as series (series.id)} - handleSeriesClick(series.id)} class="clickable"> + handleSeriesClick(series.slug)} class="clickable"> {series.order} {series.name.en} @@ -188,8 +200,7 @@ .controls { display: flex; align-items: center; - padding: spacing.$unit; - border-bottom: 1px solid #e5e5e5; + padding: spacing.$unit-2x; gap: spacing.$unit; } @@ -216,7 +227,7 @@ th, td { - padding: spacing.$unit spacing.$unit-2x; + padding: spacing.$unit-2x spacing.$unit-2x; text-align: left; border-bottom: 1px solid #e5e5e5; } @@ -262,7 +273,7 @@ } .series-name { - font-weight: typography.$bold; + font-weight: typography.$normal; } .no-flags { diff --git a/src/stories/mocks/weapons.ts b/src/stories/mocks/weapons.ts index e44cdd47..12077290 100644 --- a/src/stories/mocks/weapons.ts +++ b/src/stories/mocks/weapons.ts @@ -19,7 +19,7 @@ const mockOpusSeries = { name: { en: 'Opus', ja: 'オプス' }, hasWeaponKeys: true, hasAwakening: true, - augmentType: 'none' as const, + augmentType: 'no_augment' as const, extra: false, elementChangeable: false }; @@ -30,7 +30,7 @@ const mockDraconicSeries = { name: { en: 'Draconic', ja: 'ドラゴニック' }, hasWeaponKeys: true, hasAwakening: false, - augmentType: 'none' as const, + augmentType: 'no_augment' as const, extra: false, elementChangeable: false };