From 52660f3fb1daebe3c481014ec38e64b1f867af7c Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Wed, 3 Dec 2025 16:17:15 -0800 Subject: [PATCH] add collection artifact feature (cards, rows, pane, route) - CollectionArtifactCard for grid view - CollectionArtifactRow for list view - CollectionArtifactPane for sidebar details - artifacts collection page with filters and infinite scroll - getArtifactImage util - update collection layout for artifacts tab --- src/lib/api/adapters/artifact.adapter.ts | 3 +- .../collection/CollectionArtifactCard.svelte | 147 ++++++++ .../collection/CollectionArtifactPane.svelte | 157 +++++++++ .../collection/CollectionArtifactRow.svelte | 244 ++++++++++++++ src/lib/utils/images.ts | 10 + .../[username]/collection/+layout.svelte | 14 +- .../collection/artifacts/+page.server.ts | 12 + .../collection/artifacts/+page.svelte | 317 ++++++++++++++++++ 8 files changed, 899 insertions(+), 5 deletions(-) create mode 100644 src/lib/components/collection/CollectionArtifactCard.svelte create mode 100644 src/lib/components/collection/CollectionArtifactPane.svelte create mode 100644 src/lib/components/collection/CollectionArtifactRow.svelte create mode 100644 src/routes/(app)/[username]/collection/artifacts/+page.server.ts create mode 100644 src/routes/(app)/[username]/collection/artifacts/+page.svelte diff --git a/src/lib/api/adapters/artifact.adapter.ts b/src/lib/api/adapters/artifact.adapter.ts index 728f445e..4049b04d 100644 --- a/src/lib/api/adapters/artifact.adapter.ts +++ b/src/lib/api/adapters/artifact.adapter.ts @@ -40,9 +40,10 @@ export interface ArtifactListParams { export interface CollectionArtifactListParams { page?: number limit?: number - element?: number + element?: number | number[] artifactId?: string proficiency?: number + rarity?: 'standard' | 'quirk' } /** diff --git a/src/lib/components/collection/CollectionArtifactCard.svelte b/src/lib/components/collection/CollectionArtifactCard.svelte new file mode 100644 index 00000000..bfaf6ab9 --- /dev/null +++ b/src/lib/components/collection/CollectionArtifactCard.svelte @@ -0,0 +1,147 @@ + + + + + + + diff --git a/src/lib/components/collection/CollectionArtifactPane.svelte b/src/lib/components/collection/CollectionArtifactPane.svelte new file mode 100644 index 00000000..2b216a0c --- /dev/null +++ b/src/lib/components/collection/CollectionArtifactPane.svelte @@ -0,0 +1,157 @@ + + + + +
+ +
+
+ {displayName} +
+

{displayName}

+ {#if artifact.nickname} +

"{artifact.nickname}"

+ {/if} +
+ + +
+ +
+ + + {#if isOwner} + + {/if} +
+ + diff --git a/src/lib/components/collection/CollectionArtifactRow.svelte b/src/lib/components/collection/CollectionArtifactRow.svelte new file mode 100644 index 00000000..6dad37a8 --- /dev/null +++ b/src/lib/components/collection/CollectionArtifactRow.svelte @@ -0,0 +1,244 @@ + + + + + + + diff --git a/src/lib/utils/images.ts b/src/lib/utils/images.ts index 5cedadcd..4da704ed 100644 --- a/src/lib/utils/images.ts +++ b/src/lib/utils/images.ts @@ -364,6 +364,16 @@ export function getElementIcon(element: number): string { return `${getBasePath()}/elements/element-${name}.png` } +// ===== Artifact Images ===== + +/** + * Get artifact image URL + */ +export function getArtifactImage(granblueId: string | number | null | undefined): string { + if (!granblueId) return '/images/placeholders/placeholder-weapon-grid.png' + return `${getBasePath()}/artifacts/${granblueId}.png` +} + // ===== Other Game Images ===== /** diff --git a/src/routes/(app)/[username]/collection/+layout.svelte b/src/routes/(app)/[username]/collection/+layout.svelte index 236d29cd..916f4b31 100644 --- a/src/routes/(app)/[username]/collection/+layout.svelte +++ b/src/routes/(app)/[username]/collection/+layout.svelte @@ -18,16 +18,21 @@ const path = $page.url.pathname if (path.includes('/weapons')) return 'weapons' if (path.includes('/summons')) return 'summons' + if (path.includes('/artifacts')) return 'artifacts' return 'characters' }) - // Map entity type to singular form for modal - const modalEntityType = $derived.by(() => { + // Map entity type to singular form for modal (only for supported types) + const modalEntityType = $derived.by((): 'character' | 'weapon' | 'summon' | undefined => { if (activeEntityType === 'weapons') return 'weapon' if (activeEntityType === 'summons') return 'summon' + if (activeEntityType === 'artifacts') return undefined // Artifacts use different flow return 'character' }) + // Whether the current entity type supports the add modal + const supportsAddModal = $derived(activeEntityType !== 'artifacts') + // Dynamic button text const addButtonText = $derived(`Add ${activeEntityType}`) @@ -62,9 +67,10 @@ Characters Weapons Summons + Artifacts - {#if data.isOwner} + {#if data.isOwner && supportsAddModal} + + + + + +
+ {#if isLoading} +
+ +

Loading collection...

+
+ {:else if isEmpty} +
+ {#if data.isOwner} + +

Your artifact collection is empty

+

Artifacts will appear here once added

+ {:else} + +

This collection is empty or private

+ {/if} +
+ {:else if currentViewMode === 'grid'} +
+ {#each allArtifacts as artifact (artifact.id)} + openArtifactDetails(artifact)} + /> + {/each} +
+ {:else} +
+ {#each allArtifacts as artifact (artifact.id)} + openArtifactDetails(artifact)} + /> + {/each} +
+ {/if} + + {#if !isLoading && !isEmpty} + {#if showSentinel} +
+ {/if} + + {#if collectionQuery.isFetchingNextPage} +
+ + Loading more... +
+ {/if} + + {#if !collectionQuery.hasNextPage && allArtifacts.length > 0} +
+

+ {allArtifacts.length} artifact{allArtifacts.length === 1 ? '' : 's'} in {data.isOwner + ? 'your' + : 'this'} collection +

+
+ {/if} + {/if} +
+ + +