wire up selection mode and bulk delete in collection pages
This commit is contained in:
parent
a56d8f1870
commit
856e5017ea
4 changed files with 299 additions and 55 deletions
|
|
@ -2,17 +2,53 @@
|
||||||
import type { LayoutData } from './$types'
|
import type { LayoutData } from './$types'
|
||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
|
import { setContext } from 'svelte'
|
||||||
|
import { DropdownMenu } from 'bits-ui'
|
||||||
import ProfileHeader from '$lib/components/profile/ProfileHeader.svelte'
|
import ProfileHeader from '$lib/components/profile/ProfileHeader.svelte'
|
||||||
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
|
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
|
||||||
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
|
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
|
import DropdownItem from '$lib/components/ui/dropdown/DropdownItem.svelte'
|
||||||
import AddToCollectionModal from '$lib/components/collection/AddToCollectionModal.svelte'
|
import AddToCollectionModal from '$lib/components/collection/AddToCollectionModal.svelte'
|
||||||
|
import BulkDeleteConfirmModal from '$lib/components/collection/BulkDeleteConfirmModal.svelte'
|
||||||
import { openAddArtifactSidebar } from '$lib/features/collection/openAddArtifactSidebar'
|
import { openAddArtifactSidebar } from '$lib/features/collection/openAddArtifactSidebar'
|
||||||
|
import {
|
||||||
|
createSelectionModeContext,
|
||||||
|
SELECTION_MODE_KEY,
|
||||||
|
LOADED_IDS_KEY,
|
||||||
|
type EntityType
|
||||||
|
} from '$lib/stores/selectionMode.svelte'
|
||||||
|
import {
|
||||||
|
useBulkRemoveCharactersFromCollection,
|
||||||
|
useBulkRemoveWeaponsFromCollection,
|
||||||
|
useBulkRemoveSummonsFromCollection
|
||||||
|
} from '$lib/api/mutations/collection.mutations'
|
||||||
|
import { useBulkDeleteCollectionArtifacts } from '$lib/api/mutations/artifact.mutations'
|
||||||
|
|
||||||
let { data, children }: { data: LayoutData; children: any } = $props()
|
let { data, children }: { data: LayoutData; children: any } = $props()
|
||||||
|
|
||||||
|
// Bulk delete mutations
|
||||||
|
const bulkDeleteCharacters = useBulkRemoveCharactersFromCollection()
|
||||||
|
const bulkDeleteWeapons = useBulkRemoveWeaponsFromCollection()
|
||||||
|
const bulkDeleteSummons = useBulkRemoveSummonsFromCollection()
|
||||||
|
const bulkDeleteArtifacts = useBulkDeleteCollectionArtifacts()
|
||||||
|
|
||||||
let addModalOpen = $state(false)
|
let addModalOpen = $state(false)
|
||||||
|
let confirmDeleteOpen = $state(false)
|
||||||
|
let isDeleting = $state(false)
|
||||||
|
|
||||||
|
// Selection mode context
|
||||||
|
const selectionMode = createSelectionModeContext()
|
||||||
|
setContext(SELECTION_MODE_KEY, selectionMode)
|
||||||
|
|
||||||
|
// Context for child pages to provide their loaded IDs
|
||||||
|
let loadedIds = $state<string[]>([])
|
||||||
|
setContext(LOADED_IDS_KEY, {
|
||||||
|
setIds: (ids: string[]) => {
|
||||||
|
loadedIds = ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Determine active entity type from URL path
|
// Determine active entity type from URL path
|
||||||
const activeEntityType = $derived.by(() => {
|
const activeEntityType = $derived.by(() => {
|
||||||
|
|
@ -43,12 +79,69 @@
|
||||||
const username = $derived(data.user?.username || $page.params.username)
|
const username = $derived(data.user?.username || $page.params.username)
|
||||||
|
|
||||||
function handleTabChange(value: string) {
|
function handleTabChange(value: string) {
|
||||||
|
// Exit selection mode when switching entity types
|
||||||
|
if (selectionMode.isActive) {
|
||||||
|
selectionMode.exit()
|
||||||
|
}
|
||||||
goto(`/${username}/collection/${value}`)
|
goto(`/${username}/collection/${value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddArtifact() {
|
function handleAddArtifact() {
|
||||||
openAddArtifactSidebar()
|
openAddArtifactSidebar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleEnterSelectionMode() {
|
||||||
|
selectionMode.enter(activeEntityType as EntityType)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelSelection() {
|
||||||
|
selectionMode.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectAll() {
|
||||||
|
selectionMode.selectAll(loadedIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteClick() {
|
||||||
|
if (selectionMode.selectedCount > 0) {
|
||||||
|
confirmDeleteOpen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConfirmDelete() {
|
||||||
|
isDeleting = true
|
||||||
|
const ids = Array.from(selectionMode.selectedIds)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call the appropriate bulk delete mutation based on entity type
|
||||||
|
switch (activeEntityType) {
|
||||||
|
case 'characters':
|
||||||
|
await bulkDeleteCharacters.mutateAsync(ids)
|
||||||
|
break
|
||||||
|
case 'weapons':
|
||||||
|
await bulkDeleteWeapons.mutateAsync(ids)
|
||||||
|
break
|
||||||
|
case 'summons':
|
||||||
|
await bulkDeleteSummons.mutateAsync(ids)
|
||||||
|
break
|
||||||
|
case 'artifacts':
|
||||||
|
await bulkDeleteArtifacts.mutateAsync(ids)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionMode.exit()
|
||||||
|
confirmDeleteOpen = false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete items:', error)
|
||||||
|
// Keep modal open on error so user can retry
|
||||||
|
} finally {
|
||||||
|
isDeleting = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelDelete() {
|
||||||
|
confirmDeleteOpen = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -72,38 +165,81 @@
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<!-- Entity type segmented control -->
|
<!-- Entity type segmented control -->
|
||||||
<nav class="entity-nav" aria-label="Collection type">
|
<nav class="entity-nav" aria-label="Collection type">
|
||||||
<SegmentedControl
|
{#if selectionMode.isActive}
|
||||||
value={activeEntityType}
|
<!-- Selection mode UI -->
|
||||||
onValueChange={handleTabChange}
|
<div class="selection-controls-left">
|
||||||
variant="blended"
|
<span class="selection-count">{selectionMode.selectedCount} selected</span>
|
||||||
size="small"
|
<button class="select-all-link" onclick={handleSelectAll}>Select all</button>
|
||||||
>
|
</div>
|
||||||
<Segment value="characters">Characters</Segment>
|
<div class="selection-controls-right">
|
||||||
<Segment value="weapons">Weapons</Segment>
|
<Button
|
||||||
<Segment value="summons">Summons</Segment>
|
variant="destructive"
|
||||||
<Segment value="artifacts">Artifacts</Segment>
|
size="small"
|
||||||
</SegmentedControl>
|
onclick={handleDeleteClick}
|
||||||
|
disabled={selectionMode.selectedCount === 0}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="small" onclick={handleCancelSelection}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- Normal UI -->
|
||||||
|
<SegmentedControl
|
||||||
|
value={activeEntityType}
|
||||||
|
onValueChange={handleTabChange}
|
||||||
|
variant="blended"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Segment value="characters">Characters</Segment>
|
||||||
|
<Segment value="weapons">Weapons</Segment>
|
||||||
|
<Segment value="summons">Summons</Segment>
|
||||||
|
<Segment value="artifacts">Artifacts</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
|
||||||
{#if data.isOwner && supportsAddModal}
|
{#if data.isOwner}
|
||||||
<Button
|
<div class="action-buttons">
|
||||||
variant="primary"
|
{#if supportsAddModal}
|
||||||
size="small"
|
<Button
|
||||||
onclick={() => (addModalOpen = true)}
|
variant="primary"
|
||||||
icon="plus"
|
size="small"
|
||||||
iconPosition="left"
|
onclick={() => (addModalOpen = true)}
|
||||||
>
|
icon="plus"
|
||||||
{addButtonText}
|
iconPosition="left"
|
||||||
</Button>
|
>
|
||||||
{:else if data.isOwner && isArtifacts}
|
{addButtonText}
|
||||||
<Button
|
</Button>
|
||||||
variant="primary"
|
{:else if isArtifacts}
|
||||||
size="small"
|
<Button
|
||||||
onclick={handleAddArtifact}
|
variant="primary"
|
||||||
icon="plus"
|
size="small"
|
||||||
iconPosition="left"
|
onclick={handleAddArtifact}
|
||||||
>
|
icon="plus"
|
||||||
Add artifact
|
iconPosition="left"
|
||||||
</Button>
|
>
|
||||||
|
Add artifact
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger class="more-menu-trigger">
|
||||||
|
<Icon name="ellipsis" size={16} />
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content class="dropdown-menu" sideOffset={5} align="end">
|
||||||
|
<DropdownItem>
|
||||||
|
<button onclick={handleEnterSelectionMode}>
|
||||||
|
<Icon name="check" size={14} />
|
||||||
|
<span>Select...</span>
|
||||||
|
</button>
|
||||||
|
</DropdownItem>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
@ -121,9 +257,19 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<BulkDeleteConfirmModal
|
||||||
|
bind:open={confirmDeleteOpen}
|
||||||
|
count={selectionMode.selectedCount}
|
||||||
|
entityType={activeEntityType}
|
||||||
|
deleting={isDeleting}
|
||||||
|
onConfirm={handleConfirmDelete}
|
||||||
|
onCancel={handleCancelDelete}
|
||||||
|
/>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use '$src/themes/spacing' as *;
|
@use '$src/themes/spacing' as *;
|
||||||
@use '$src/themes/layout' as *;
|
@use '$src/themes/layout' as *;
|
||||||
|
@use '$src/themes/typography' as *;
|
||||||
|
|
||||||
.collection {
|
.collection {
|
||||||
padding: $unit-2x 0;
|
padding: $unit-2x 0;
|
||||||
|
|
@ -147,4 +293,69 @@
|
||||||
padding: $unit-2x;
|
padding: $unit-2x;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Action buttons container
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// More menu trigger button
|
||||||
|
:global(.more-menu-trigger) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: $unit-4x;
|
||||||
|
height: $unit-4x;
|
||||||
|
border-radius: $item-corner;
|
||||||
|
border: none;
|
||||||
|
background: var(--button-contained-bg);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--button-contained-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--accent-color);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection mode controls
|
||||||
|
.selection-controls-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-count {
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $medium;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-link {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
font-size: $font-small;
|
||||||
|
font-weight: $medium;
|
||||||
|
color: var(--accent-color);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-controls-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $unit;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
import type { CollectionCharacter, CollectionSortKey } from '$lib/types/api/collection'
|
import type { CollectionCharacter, CollectionSortKey } from '$lib/types/api/collection'
|
||||||
|
import { getContext } from 'svelte'
|
||||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
||||||
import CollectionFilters, {
|
import CollectionFilters, {
|
||||||
|
|
@ -9,13 +10,19 @@
|
||||||
import CollectionCharacterPane from '$lib/components/collection/CollectionCharacterPane.svelte'
|
import CollectionCharacterPane from '$lib/components/collection/CollectionCharacterPane.svelte'
|
||||||
import CollectionCharacterCard from '$lib/components/collection/CollectionCharacterCard.svelte'
|
import CollectionCharacterCard from '$lib/components/collection/CollectionCharacterCard.svelte'
|
||||||
import CollectionCharacterRow from '$lib/components/collection/CollectionCharacterRow.svelte'
|
import CollectionCharacterRow from '$lib/components/collection/CollectionCharacterRow.svelte'
|
||||||
|
import SelectableCollectionCard from '$lib/components/collection/SelectableCollectionCard.svelte'
|
||||||
|
import SelectableCollectionRow from '$lib/components/collection/SelectableCollectionRow.svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import { IsInViewport } from 'runed'
|
import { IsInViewport } from 'runed'
|
||||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||||
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
||||||
|
import { LOADED_IDS_KEY, type LoadedIdsContext } from '$lib/stores/selectionMode.svelte'
|
||||||
|
|
||||||
const { data }: { data: PageData } = $props()
|
const { data }: { data: PageData } = $props()
|
||||||
|
|
||||||
|
// Get loaded IDs context from layout
|
||||||
|
const loadedIdsContext = getContext<LoadedIdsContext | undefined>(LOADED_IDS_KEY)
|
||||||
|
|
||||||
// Filter state
|
// Filter state
|
||||||
let elementFilters = $state<number[]>([])
|
let elementFilters = $state<number[]>([])
|
||||||
let rarityFilters = $state<number[]>([])
|
let rarityFilters = $state<number[]>([])
|
||||||
|
|
@ -54,6 +61,12 @@
|
||||||
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Provide loaded IDs to layout for "Select all"
|
||||||
|
$effect(() => {
|
||||||
|
const ids = allCharacters.map((c) => c.id)
|
||||||
|
loadedIdsContext?.setIds(ids)
|
||||||
|
})
|
||||||
|
|
||||||
// Infinite scroll
|
// Infinite scroll
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||||
rootMargin: '200px'
|
rootMargin: '200px'
|
||||||
|
|
@ -151,19 +164,17 @@
|
||||||
{:else if currentViewMode === 'grid'}
|
{:else if currentViewMode === 'grid'}
|
||||||
<div class="character-grid">
|
<div class="character-grid">
|
||||||
{#each allCharacters as character (character.id)}
|
{#each allCharacters as character (character.id)}
|
||||||
<CollectionCharacterCard
|
<SelectableCollectionCard id={character.id} onClick={() => openCharacterDetails(character)}>
|
||||||
{character}
|
<CollectionCharacterCard {character} />
|
||||||
onClick={() => openCharacterDetails(character)}
|
</SelectableCollectionCard>
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="character-list">
|
<div class="character-list">
|
||||||
{#each allCharacters as character (character.id)}
|
{#each allCharacters as character (character.id)}
|
||||||
<CollectionCharacterRow
|
<SelectableCollectionRow id={character.id} onClick={() => openCharacterDetails(character)}>
|
||||||
{character}
|
<CollectionCharacterRow {character} />
|
||||||
onClick={() => openCharacterDetails(character)}
|
</SelectableCollectionRow>
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
import type { CollectionSummon, CollectionSortKey } from '$lib/types/api/collection'
|
import type { CollectionSummon, CollectionSortKey } from '$lib/types/api/collection'
|
||||||
|
import { getContext } from 'svelte'
|
||||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
||||||
import CollectionFilters, {
|
import CollectionFilters, {
|
||||||
|
|
@ -9,13 +10,19 @@
|
||||||
import CollectionSummonPane from '$lib/components/collection/CollectionSummonPane.svelte'
|
import CollectionSummonPane from '$lib/components/collection/CollectionSummonPane.svelte'
|
||||||
import CollectionSummonCard from '$lib/components/collection/CollectionSummonCard.svelte'
|
import CollectionSummonCard from '$lib/components/collection/CollectionSummonCard.svelte'
|
||||||
import CollectionSummonRow from '$lib/components/collection/CollectionSummonRow.svelte'
|
import CollectionSummonRow from '$lib/components/collection/CollectionSummonRow.svelte'
|
||||||
|
import SelectableCollectionCard from '$lib/components/collection/SelectableCollectionCard.svelte'
|
||||||
|
import SelectableCollectionRow from '$lib/components/collection/SelectableCollectionRow.svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import { IsInViewport } from 'runed'
|
import { IsInViewport } from 'runed'
|
||||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||||
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
||||||
|
import { LOADED_IDS_KEY, type LoadedIdsContext } from '$lib/stores/selectionMode.svelte'
|
||||||
|
|
||||||
const { data }: { data: PageData } = $props()
|
const { data }: { data: PageData } = $props()
|
||||||
|
|
||||||
|
// Get loaded IDs context from layout
|
||||||
|
const loadedIdsContext = getContext<LoadedIdsContext | undefined>(LOADED_IDS_KEY)
|
||||||
|
|
||||||
// Filter state
|
// Filter state
|
||||||
let elementFilters = $state<number[]>([])
|
let elementFilters = $state<number[]>([])
|
||||||
let rarityFilters = $state<number[]>([])
|
let rarityFilters = $state<number[]>([])
|
||||||
|
|
@ -48,6 +55,12 @@
|
||||||
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Provide loaded IDs to layout for "Select all"
|
||||||
|
$effect(() => {
|
||||||
|
const ids = allSummons.map((s) => s.id)
|
||||||
|
loadedIdsContext?.setIds(ids)
|
||||||
|
})
|
||||||
|
|
||||||
// Infinite scroll
|
// Infinite scroll
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||||
rootMargin: '200px'
|
rootMargin: '200px'
|
||||||
|
|
@ -131,19 +144,17 @@
|
||||||
{:else if currentViewMode === 'grid'}
|
{:else if currentViewMode === 'grid'}
|
||||||
<div class="summon-grid">
|
<div class="summon-grid">
|
||||||
{#each allSummons as summon (summon.id)}
|
{#each allSummons as summon (summon.id)}
|
||||||
<CollectionSummonCard
|
<SelectableCollectionCard id={summon.id} onClick={() => openSummonDetails(summon)}>
|
||||||
{summon}
|
<CollectionSummonCard {summon} />
|
||||||
onClick={() => openSummonDetails(summon)}
|
</SelectableCollectionCard>
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="summon-list">
|
<div class="summon-list">
|
||||||
{#each allSummons as summon (summon.id)}
|
{#each allSummons as summon (summon.id)}
|
||||||
<CollectionSummonRow
|
<SelectableCollectionRow id={summon.id} onClick={() => openSummonDetails(summon)}>
|
||||||
{summon}
|
<CollectionSummonRow {summon} />
|
||||||
onClick={() => openSummonDetails(summon)}
|
</SelectableCollectionRow>
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
import type { CollectionWeapon, CollectionSortKey } from '$lib/types/api/collection'
|
import type { CollectionWeapon, CollectionSortKey } from '$lib/types/api/collection'
|
||||||
|
import { getContext } from 'svelte'
|
||||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
||||||
import CollectionFilters, {
|
import CollectionFilters, {
|
||||||
|
|
@ -9,13 +10,19 @@
|
||||||
import CollectionWeaponPane from '$lib/components/collection/CollectionWeaponPane.svelte'
|
import CollectionWeaponPane from '$lib/components/collection/CollectionWeaponPane.svelte'
|
||||||
import CollectionWeaponCard from '$lib/components/collection/CollectionWeaponCard.svelte'
|
import CollectionWeaponCard from '$lib/components/collection/CollectionWeaponCard.svelte'
|
||||||
import CollectionWeaponRow from '$lib/components/collection/CollectionWeaponRow.svelte'
|
import CollectionWeaponRow from '$lib/components/collection/CollectionWeaponRow.svelte'
|
||||||
|
import SelectableCollectionCard from '$lib/components/collection/SelectableCollectionCard.svelte'
|
||||||
|
import SelectableCollectionRow from '$lib/components/collection/SelectableCollectionRow.svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import { IsInViewport } from 'runed'
|
import { IsInViewport } from 'runed'
|
||||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||||
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
||||||
|
import { LOADED_IDS_KEY, type LoadedIdsContext } from '$lib/stores/selectionMode.svelte'
|
||||||
|
|
||||||
const { data }: { data: PageData } = $props()
|
const { data }: { data: PageData } = $props()
|
||||||
|
|
||||||
|
// Get loaded IDs context from layout
|
||||||
|
const loadedIdsContext = getContext<LoadedIdsContext | undefined>(LOADED_IDS_KEY)
|
||||||
|
|
||||||
// Filter state
|
// Filter state
|
||||||
let elementFilters = $state<number[]>([])
|
let elementFilters = $state<number[]>([])
|
||||||
let rarityFilters = $state<number[]>([])
|
let rarityFilters = $state<number[]>([])
|
||||||
|
|
@ -52,6 +59,12 @@
|
||||||
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
return collectionQuery.data.pages.flatMap((page) => page.results ?? [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Provide loaded IDs to layout for "Select all"
|
||||||
|
$effect(() => {
|
||||||
|
const ids = allWeapons.map((w) => w.id)
|
||||||
|
loadedIdsContext?.setIds(ids)
|
||||||
|
})
|
||||||
|
|
||||||
// Infinite scroll
|
// Infinite scroll
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||||
rootMargin: '200px'
|
rootMargin: '200px'
|
||||||
|
|
@ -139,19 +152,17 @@
|
||||||
{:else if currentViewMode === 'grid'}
|
{:else if currentViewMode === 'grid'}
|
||||||
<div class="weapon-grid">
|
<div class="weapon-grid">
|
||||||
{#each allWeapons as weapon (weapon.id)}
|
{#each allWeapons as weapon (weapon.id)}
|
||||||
<CollectionWeaponCard
|
<SelectableCollectionCard id={weapon.id} onClick={() => openWeaponDetails(weapon)}>
|
||||||
{weapon}
|
<CollectionWeaponCard {weapon} />
|
||||||
onClick={() => openWeaponDetails(weapon)}
|
</SelectableCollectionCard>
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="weapon-list">
|
<div class="weapon-list">
|
||||||
{#each allWeapons as weapon (weapon.id)}
|
{#each allWeapons as weapon (weapon.id)}
|
||||||
<CollectionWeaponRow
|
<SelectableCollectionRow id={weapon.id} onClick={() => openWeaponDetails(weapon)}>
|
||||||
{weapon}
|
<CollectionWeaponRow {weapon} />
|
||||||
onClick={() => openWeaponDetails(weapon)}
|
</SelectableCollectionRow>
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue