summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/toolbar
diff options
context:
space:
mode:
authorWolfgang Müller2025-02-13 17:52:16 +0100
committerWolfgang Müller2025-02-13 17:52:16 +0100
commitdc4db405d2991d3ec6a114f3b08d3fccd057d3ee (patch)
tree2c620c9af2062ba09fa591f8b3ed961664adab58 /frontend/src/lib/toolbar
parent4df870d793123be95c8af031a340a39b5b8402ac (diff)
downloadhircine-dc4db405d2991d3ec6a114f3b08d3fccd057d3ee.tar.gz
frontend: Migrate to Svelte 5
Diffstat (limited to 'frontend/src/lib/toolbar')
-rw-r--r--frontend/src/lib/toolbar/DeleteSelection.svelte24
-rw-r--r--frontend/src/lib/toolbar/EditSelection.svelte22
-rw-r--r--frontend/src/lib/toolbar/FilterBookmarked.svelte15
-rw-r--r--frontend/src/lib/toolbar/FilterFavourites.svelte14
-rw-r--r--frontend/src/lib/toolbar/FilterOrganized.svelte18
-rw-r--r--frontend/src/lib/toolbar/MarkBookmark.svelte18
-rw-r--r--frontend/src/lib/toolbar/MarkFavourite.svelte18
-rw-r--r--frontend/src/lib/toolbar/MarkOrganized.svelte18
-rw-r--r--frontend/src/lib/toolbar/MarkSelection.svelte37
-rw-r--r--frontend/src/lib/toolbar/Search.svelte14
-rw-r--r--frontend/src/lib/toolbar/SelectItems.svelte21
-rw-r--r--frontend/src/lib/toolbar/SelectSort.svelte58
-rw-r--r--frontend/src/lib/toolbar/SelectionControls.svelte61
-rw-r--r--frontend/src/lib/toolbar/ToggleAdvancedFilters.svelte33
-rw-r--r--frontend/src/lib/toolbar/Toolbar.svelte38
15 files changed, 214 insertions, 195 deletions
diff --git a/frontend/src/lib/toolbar/DeleteSelection.svelte b/frontend/src/lib/toolbar/DeleteSelection.svelte
index 7459a87..7b37313 100644
--- a/frontend/src/lib/toolbar/DeleteSelection.svelte
+++ b/frontend/src/lib/toolbar/DeleteSelection.svelte
@@ -1,26 +1,28 @@
<script lang="ts">
import type { DeleteMutation } from '$gql/Mutations';
- import { getSelectionContext } from '$lib/Selection';
+ import DeleteButton from '$lib/components/DeleteButton.svelte';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
import { toastFinally } from '$lib/Toasts';
import { confirmDeletion } from '$lib/Utils';
- import DeleteButton from '$lib/components/DeleteButton.svelte';
import { getContextClient } from '@urql/svelte';
const client = getContextClient();
- const selection = getSelectionContext();
- export let mutation: DeleteMutation;
- export let warning: string | undefined = undefined;
+ interface Props {
+ mutation: DeleteMutation;
+ warning?: string;
+ }
+
+ let { mutation, warning = undefined }: Props = $props();
+ let selection = getSelectionContext();
- function remove() {
+ function onclick() {
const mutate = () => {
- mutation(client, { ids: $selection.ids })
- .then(() => ($selection = $selection.clear()))
- .catch(toastFinally);
+ mutation(client, { ids: selection.ids }).then(selection.clear).catch(toastFinally);
};
- confirmDeletion($selection.typename, $selection.names, mutate, warning);
+ confirmDeletion(selection.typename, selection.names, mutate, warning);
}
</script>
-<DeleteButton on:click={remove} />
+<DeleteButton {onclick} />
diff --git a/frontend/src/lib/toolbar/EditSelection.svelte b/frontend/src/lib/toolbar/EditSelection.svelte
index 50e6656..1803ed4 100644
--- a/frontend/src/lib/toolbar/EditSelection.svelte
+++ b/frontend/src/lib/toolbar/EditSelection.svelte
@@ -1,20 +1,19 @@
<script lang="ts">
- import { getSelectionContext } from '$lib/Selection';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
import { accelerator } from '$lib/Shortcuts';
- import type { SvelteComponent } from 'svelte';
- import { openModal } from 'svelte-modals';
+ import { toastFinally } from '$lib/Toasts';
+ import { modals, type ModalComponent, type ModalProps } from 'svelte-modals';
const selection = getSelectionContext();
- export let dialog: typeof SvelteComponent<{
- isOpen: boolean;
+ interface DialogProps extends ModalProps {
ids: number[];
- }>;
+ }
+
+ let { dialog }: { dialog: ModalComponent<DialogProps> } = $props();
function edit() {
- openModal(dialog, {
- ids: $selection.ids
- });
+ modals.open(dialog, { ids: selection.ids }).catch(toastFinally);
}
</script>
@@ -22,8 +21,9 @@
type="button"
class="btn-slate hover:bg-blue-700"
title="Edit selection"
- on:click={edit}
+ aria-label="Edit selection"
+ onclick={edit}
use:accelerator={'e'}
>
- <span class="icon-base icon-[material-symbols--edit]" />
+ <span class="icon-base icon-[material-symbols--edit]"></span>
</button>
diff --git a/frontend/src/lib/toolbar/FilterBookmarked.svelte b/frontend/src/lib/toolbar/FilterBookmarked.svelte
index bcbe295..76403ec 100644
--- a/frontend/src/lib/toolbar/FilterBookmarked.svelte
+++ b/frontend/src/lib/toolbar/FilterBookmarked.svelte
@@ -1,15 +1,16 @@
<script lang="ts">
- import { page } from '$app/stores';
- import { ComicFilterContext, cycleBooleanFilter, getFilterContext } from '$lib/Filter';
+ import { page } from '$app/state';
+ import { cycleBooleanFilter, type ComicFilterContext } from '$lib/Filter.svelte';
+
import { accelerator } from '$lib/Shortcuts';
import Bookmark from '$lib/icons/Bookmark.svelte';
- const filter = getFilterContext<ComicFilterContext>();
- $: bookmarked = $filter.include.controls.bookmarked.value;
+ let { filter }: { filter: ComicFilterContext } = $props();
+ let bookmarked = $derived(filter.include.bookmarked.value);
const toggle = () => {
- $filter.include.controls.bookmarked.value = cycleBooleanFilter(bookmarked, false);
- $filter.apply($page.url.searchParams);
+ filter.include.bookmarked.value = cycleBooleanFilter(bookmarked, false);
+ filter.apply(page.url.searchParams);
};
</script>
@@ -17,7 +18,7 @@
class:toggled={bookmarked}
class="btn-slate"
title="Filter bookmarked"
- on:click={toggle}
+ onclick={toggle}
use:accelerator={'b'}
>
<Bookmark {bookmarked} />
diff --git a/frontend/src/lib/toolbar/FilterFavourites.svelte b/frontend/src/lib/toolbar/FilterFavourites.svelte
index 6591cef..5e9beb7 100644
--- a/frontend/src/lib/toolbar/FilterFavourites.svelte
+++ b/frontend/src/lib/toolbar/FilterFavourites.svelte
@@ -1,15 +1,15 @@
<script lang="ts">
- import { page } from '$app/stores';
- import { ComicFilterContext, cycleBooleanFilter, getFilterContext } from '$lib/Filter';
+ import { page } from '$app/state';
+ import { ComicFilterContext, cycleBooleanFilter } from '$lib/Filter.svelte';
import { accelerator } from '$lib/Shortcuts';
import Star from '$lib/icons/Star.svelte';
- const filter = getFilterContext<ComicFilterContext>();
- $: favourite = $filter.include.controls.favourite.value;
+ let { filter }: { filter: ComicFilterContext } = $props();
+ let favourite = $derived(filter.include.favourite.value);
const toggle = () => {
- $filter.include.controls.favourite.value = cycleBooleanFilter(favourite, false);
- $filter.apply($page.url.searchParams);
+ filter.include.favourite.value = cycleBooleanFilter(favourite, false);
+ filter.apply(page.url.searchParams);
};
</script>
@@ -17,7 +17,7 @@
class:toggled={favourite}
class="btn-slate"
title="Filter favourites"
- on:click={toggle}
+ onclick={toggle}
use:accelerator={'f'}
>
<Star {favourite} />
diff --git a/frontend/src/lib/toolbar/FilterOrganized.svelte b/frontend/src/lib/toolbar/FilterOrganized.svelte
index 754e663..0f95e5f 100644
--- a/frontend/src/lib/toolbar/FilterOrganized.svelte
+++ b/frontend/src/lib/toolbar/FilterOrganized.svelte
@@ -1,20 +1,20 @@
<script lang="ts">
- import { page } from '$app/stores';
+ import { page } from '$app/state';
import {
ArchiveFilterContext,
- ComicFilterContext,
cycleBooleanFilter,
- getFilterContext
- } from '$lib/Filter';
+ type ComicFilterContext
+ } from '$lib/Filter.svelte';
+
import { accelerator } from '$lib/Shortcuts';
import Organized from '$lib/icons/Organized.svelte';
- const filter = getFilterContext<ArchiveFilterContext | ComicFilterContext>();
- $: organized = $filter.include.controls.organized.value;
+ let { filter }: { filter: ComicFilterContext | ArchiveFilterContext } = $props();
+ let organized = $derived(filter.include.organized.value);
const toggle = () => {
- $filter.include.controls.organized.value = cycleBooleanFilter(organized);
- $filter.apply($page.url.searchParams);
+ filter.include.organized.value = cycleBooleanFilter(organized);
+ filter.apply(page.url.searchParams);
};
</script>
@@ -23,7 +23,7 @@
class:toggled={organized !== undefined}
class="btn-slate"
title="Filter organized"
- on:click={toggle}
+ onclick={toggle}
use:accelerator={'o'}
>
<Organized tristate {organized} />
diff --git a/frontend/src/lib/toolbar/MarkBookmark.svelte b/frontend/src/lib/toolbar/MarkBookmark.svelte
index 792b84f..776ddd8 100644
--- a/frontend/src/lib/toolbar/MarkBookmark.svelte
+++ b/frontend/src/lib/toolbar/MarkBookmark.svelte
@@ -1,27 +1,25 @@
<script lang="ts">
- import { getSelectionContext } from '$lib/Selection';
- import { toastFinally } from '$lib/Toasts';
+ import type { MutationWith } from '$gql/Utils';
import Bookmark from '$lib/icons/Bookmark.svelte';
- import { Client, getContextClient } from '@urql/svelte';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
const client = getContextClient();
const selection = getSelectionContext();
- export let mutation: (
- client: Client,
- args: { ids: number[]; input: { bookmarked: boolean } }
- ) => Promise<unknown>;
+ let { mutation }: { mutation: MutationWith<{ bookmarked: boolean }> } = $props();
function mutate(bookmarked: boolean) {
- mutation(client, { ids: $selection.ids, input: { bookmarked } }).catch(toastFinally);
+ mutation(client, { ids: selection.ids, input: { bookmarked } }).catch(toastFinally);
}
</script>
-<button type="button" class="btn-slate flex justify-start gap-1" on:click={() => mutate(true)}>
+<button type="button" class="btn-slate flex justify-start gap-1" onclick={() => mutate(true)}>
<Bookmark bookmarked={true} />
<span>Bookmark</span>
</button>
-<button type="button" class="btn-slate flex justify-start gap-1" on:click={() => mutate(false)}>
+<button type="button" class="btn-slate flex justify-start gap-1" onclick={() => mutate(false)}>
<Bookmark bookmarked={false} />
<span>Unbookmark</span>
</button>
diff --git a/frontend/src/lib/toolbar/MarkFavourite.svelte b/frontend/src/lib/toolbar/MarkFavourite.svelte
index 42eaa39..1af5d60 100644
--- a/frontend/src/lib/toolbar/MarkFavourite.svelte
+++ b/frontend/src/lib/toolbar/MarkFavourite.svelte
@@ -1,27 +1,25 @@
<script lang="ts">
- import { getSelectionContext } from '$lib/Selection';
- import { toastFinally } from '$lib/Toasts';
+ import type { MutationWith } from '$gql/Utils';
import Star from '$lib/icons/Star.svelte';
- import { Client, getContextClient } from '@urql/svelte';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
const client = getContextClient();
const selection = getSelectionContext();
- export let mutation: (
- client: Client,
- args: { ids: number[]; input: { favourite: boolean } }
- ) => Promise<unknown>;
+ let { mutation }: { mutation: MutationWith<{ favourite: boolean }> } = $props();
function mutate(favourite: boolean) {
- mutation(client, { ids: $selection.ids, input: { favourite } }).catch(toastFinally);
+ mutation(client, { ids: selection.ids, input: { favourite } }).catch(toastFinally);
}
</script>
-<button type="button" class="btn-slate justify-start gap-1" on:click={() => mutate(true)}>
+<button type="button" class="btn-slate justify-start gap-1" onclick={() => mutate(true)}>
<Star favourite={true} />
<span>Favourite</span>
</button>
-<button type="button" class="btn-slate justify-start gap-1" on:click={() => mutate(false)}>
+<button type="button" class="btn-slate justify-start gap-1" onclick={() => mutate(false)}>
<Star favourite={false} />
<span>Unfavourite</span>
</button>
diff --git a/frontend/src/lib/toolbar/MarkOrganized.svelte b/frontend/src/lib/toolbar/MarkOrganized.svelte
index 4dc3a83..63c8622 100644
--- a/frontend/src/lib/toolbar/MarkOrganized.svelte
+++ b/frontend/src/lib/toolbar/MarkOrganized.svelte
@@ -1,27 +1,25 @@
<script lang="ts">
- import { getSelectionContext } from '$lib/Selection';
- import { toastFinally } from '$lib/Toasts';
+ import type { MutationWith } from '$gql/Utils';
import Organized from '$lib/icons/Organized.svelte';
- import { Client, getContextClient } from '@urql/svelte';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
const client = getContextClient();
const selection = getSelectionContext();
- export let mutation: (
- client: Client,
- args: { ids: number[]; input: { organized: boolean } }
- ) => Promise<unknown>;
+ let { mutation }: { mutation: MutationWith<{ organized: boolean }> } = $props();
function mutate(organized: boolean) {
- mutation(client, { ids: $selection.ids, input: { organized } }).catch(toastFinally);
+ mutation(client, { ids: selection.ids, input: { organized } }).catch(toastFinally);
}
</script>
-<button type="button" class="btn-slate flex justify-start gap-1" on:click={() => mutate(true)}>
+<button type="button" class="btn-slate flex justify-start gap-1" onclick={() => mutate(true)}>
<Organized tristate organized={true} />
<span>Organized</span>
</button>
-<button type="button" class="btn-slate flex justify-start gap-1" on:click={() => mutate(false)}>
+<button type="button" class="btn-slate flex justify-start gap-1" onclick={() => mutate(false)}>
<Organized dim tristate organized={false} />
<span>Unorganized</span>
</button>
diff --git a/frontend/src/lib/toolbar/MarkSelection.svelte b/frontend/src/lib/toolbar/MarkSelection.svelte
index 27eb2c7..1af36ca 100644
--- a/frontend/src/lib/toolbar/MarkSelection.svelte
+++ b/frontend/src/lib/toolbar/MarkSelection.svelte
@@ -1,24 +1,23 @@
<script lang="ts">
import Dropdown from '$lib/components/Dropdown.svelte';
+ import type { Snippet } from 'svelte';
- let visible = false;
- let button: HTMLElement;
+ let { children }: { children: Snippet } = $props();
</script>
-<div class="relative">
- <button
- type="button"
- class="btn-slate rounded-inherit relative hover:bg-blue-700 [&:not(:only-child)]:bg-blue-700"
- title="Set flag..."
- bind:this={button}
- on:click={() => (visible = !visible)}
- >
- <span class="icon-base icon-[material-symbols--flag] pointer-events-none" />
- </button>
-
- <Dropdown parent={button} bind:visible>
- <div class="grid grid-cols-[min-content_min-content] gap-1">
- <slot />
- </div>
- </Dropdown>
-</div>
+<Dropdown>
+ {#snippet button(onclick)}
+ <button
+ type="button"
+ class="btn-slate rounded-inherit relative hover:bg-blue-700 [&:not(:only-child)]:bg-blue-700"
+ title="Set flag..."
+ aria-label="Set flag..."
+ {onclick}
+ >
+ <span class="icon-base icon-[material-symbols--flag] pointer-events-none"></span>
+ </button>
+ {/snippet}
+ <div class="grid grid-cols-[min-content_min-content] gap-1">
+ {@render children?.()}
+ </div>
+</Dropdown>
diff --git a/frontend/src/lib/toolbar/Search.svelte b/frontend/src/lib/toolbar/Search.svelte
index f033258..4806971 100644
--- a/frontend/src/lib/toolbar/Search.svelte
+++ b/frontend/src/lib/toolbar/Search.svelte
@@ -1,13 +1,15 @@
<script lang="ts">
- import { page } from '$app/stores';
+ import { page } from '$app/state';
import { debounce } from '$lib/Actions';
- import { BasicFilterContext, getFilterContext } from '$lib/Filter';
import { accelerator } from '$lib/Shortcuts';
- const filter = getFilterContext<BasicFilterContext>();
+ interface Props {
+ name: string;
+ field: string;
+ filter: { apply: (params: URLSearchParams) => void };
+ }
- export let name: string;
- export let field: string;
+ let { name, field = $bindable(), filter }: Props = $props();
</script>
<input
@@ -16,6 +18,6 @@
class="btn-slate w-min"
placeholder="Search {name}..."
bind:value={field}
- use:debounce={{ callback: () => $filter.apply($page.url.searchParams) }}
+ use:debounce={{ callback: () => filter.apply(page.url.searchParams) }}
use:accelerator={'F'}
/>
diff --git a/frontend/src/lib/toolbar/SelectItems.svelte b/frontend/src/lib/toolbar/SelectItems.svelte
index 7ff339e..68a0652 100644
--- a/frontend/src/lib/toolbar/SelectItems.svelte
+++ b/frontend/src/lib/toolbar/SelectItems.svelte
@@ -1,18 +1,19 @@
<script lang="ts">
- import { page } from '$app/stores';
- import { getPaginationContext } from '$lib/Pagination';
+ import { page } from '$app/state';
+ import { navigate, type PaginationData } from '$lib/Navigation';
- const pagination = getPaginationContext();
+ let { pagination }: { pagination: PaginationData } = $props();
- $: values = new Set([24, 48, 72, 90, 120, 150, 180, $pagination.items].sort((a, b) => a - b));
+ let values = $derived(
+ new Set([24, 48, 72, 90, 120, 150, 180, pagination.items].sort((a, b) => a - b))
+ );
+
+ function onchange(e: Event & { currentTarget: EventTarget & HTMLSelectElement }) {
+ navigate({ pagination: { items: +e.currentTarget.value } }, page.url.searchParams);
+ }
</script>
-<select
- class="btn-slate"
- bind:value={$pagination.items}
- on:change={() => $pagination.apply($page.url.searchParams)}
- title="Limit displayed items to..."
->
+<select class="btn-slate" value={pagination.items} {onchange} title="Limit displayed items to...">
{#each values as value}
<option {value}>{value}</option>
{/each}
diff --git a/frontend/src/lib/toolbar/SelectSort.svelte b/frontend/src/lib/toolbar/SelectSort.svelte
index fdcb057..0e59df6 100644
--- a/frontend/src/lib/toolbar/SelectSort.svelte
+++ b/frontend/src/lib/toolbar/SelectSort.svelte
@@ -1,60 +1,68 @@
<script lang="ts">
- import { page } from '$app/stores';
+ import { page } from '$app/state';
import { SortDirection } from '$gql/graphql';
-
- import { getSortContext } from '$lib/Sort';
+ import { navigate, type SortData } from '$lib/Navigation';
import { slideXFast } from '$lib/Transitions';
import { getRandomInt } from '$lib/Utils';
import { slide } from 'svelte/transition';
- const sort = getSortContext();
+ let { sort, labels }: { sort: SortData<string>; labels: Record<string, string> } = $props();
+
+ function apply(sort: SortData<string>) {
+ navigate({ sort }, page.url.searchParams);
+ }
function toggle() {
- if ($sort.direction === SortDirection.Ascending) {
- $sort.direction = SortDirection.Descending;
+ if (sort.direction === SortDirection.Ascending) {
+ apply({ ...sort, direction: SortDirection.Descending });
} else {
- $sort.direction = SortDirection.Ascending;
+ apply({ ...sort, direction: SortDirection.Ascending });
}
+ }
- apply();
+ function newSeed() {
+ return getRandomInt(0, 1000000000);
}
- function apply() {
- if ($sort.on === 'RANDOM' && $sort.seed === undefined) {
- $sort.seed = getRandomInt(0, 1000000000);
- }
- $sort.apply($page.url.searchParams);
+ function shuffle() {
+ apply({ ...sort, seed: newSeed() });
}
- function reshuffle() {
- $sort.seed = undefined;
- apply();
+ function onchange(e: Event & { currentTarget: EventTarget & HTMLSelectElement }) {
+ let seed: number | undefined = undefined;
+
+ if (e.currentTarget.value === 'RANDOM') {
+ seed = newSeed();
+ }
+
+ apply({ ...sort, on: e.currentTarget.value, seed });
}
</script>
<div class="rounded-group flex flex-row">
- <select class="btn-slate" bind:value={$sort.on} on:change={apply} title="Sort on...">
- {#each Object.entries($sort.labels) as [value, label]}
+ <select class="btn-slate" value={sort.on} {onchange} title="Sort on...">
+ {#each Object.entries(labels) as [value, label]}
<option {value}>{label}</option>
{/each}
</select>
- <button type="button" class="btn-slate" title="Toggle sort direction" on:click={toggle}>
- {#if $sort.direction === SortDirection.Ascending}
- <span class="icon-base icon-[material-symbols--sort] -scale-y-100" />
+ <button type="button" class="btn-slate" title="Toggle sort direction" onclick={toggle}>
+ {#if sort.direction === SortDirection.Ascending}
+ <span class="icon-base icon-[material-symbols--sort] -scale-y-100"></span>
{:else}
- <span class="icon-base icon-[material-symbols--sort]" />
+ <span class="icon-base icon-[material-symbols--sort]"></span>
{/if}
</button>
- {#if $sort.on === 'RANDOM'}
+ {#if sort.on === 'RANDOM'}
<button
type="button"
class="btn-slate"
title="Reshuffle"
- on:click={reshuffle}
+ aria-label="Reshuffle"
+ onclick={shuffle}
transition:slide={slideXFast}
>
<div class="flex">
- <span class="icon-base icon-[material-symbols--shuffle]" />
+ <span class="icon-base icon-[material-symbols--shuffle]"></span>
</div>
</button>
{/if}
diff --git a/frontend/src/lib/toolbar/SelectionControls.svelte b/frontend/src/lib/toolbar/SelectionControls.svelte
index 4d309df..f0026c8 100644
--- a/frontend/src/lib/toolbar/SelectionControls.svelte
+++ b/frontend/src/lib/toolbar/SelectionControls.svelte
@@ -1,57 +1,64 @@
<script lang="ts">
- import { getSelectionContext } from '$lib/Selection';
+ import Badge from '$lib/components/Badge.svelte';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
import { accelerator } from '$lib/Shortcuts';
import { fadeDefault, slideXFast } from '$lib/Transitions';
- import Badge from '$lib/components/Badge.svelte';
- import { onDestroy } from 'svelte';
+ import { onDestroy, type Snippet } from 'svelte';
import { fade, slide } from 'svelte/transition';
- const selection = getSelectionContext();
-
- export let page = false;
-
- const toggle = () => ($selection = $selection.toggle());
- const all = () => ($selection = $selection.all());
- const none = () => ($selection = $selection.none());
+ let { page = false, children }: { page?: boolean; children?: Snippet } = $props();
+ let selection = getSelectionContext();
- onDestroy(() => ($selection = $selection.clear()));
+ onDestroy(selection.clear);
</script>
<div class="rounded-group flex">
<button
type="button"
class="btn-slate relative"
- class:toggled={$selection.active}
- title={`${$selection.active ? 'Exit' : 'Enter'} ${page ? 'page ' : ' '}selection mode`}
- on:click={toggle}
+ class:toggled={selection.active}
+ title={`${selection.active ? 'Exit' : 'Enter'} ${page ? 'page ' : ' '}selection mode`}
+ onclick={selection.toggle}
use:accelerator={'s'}
>
- {#if $selection.active}
+ {#if selection.active}
{#if page}
- <span class="icon-base icon-[material-symbols--edit-document]" />
+ <span class="icon-base icon-[material-symbols--edit-document]"></span>
{:else}
- <span class="icon-base icon-[material-symbols--remove-selection]" />
+ <span class="icon-base icon-[material-symbols--remove-selection]"></span>
{/if}
{:else if page}
- <span class="icon-base icon-[material-symbols--edit-document-outline]" />
+ <span class="icon-base icon-[material-symbols--edit-document-outline]"></span>
{:else}
- <span class="icon-base icon-[material-symbols--select]" />
+ <span class="icon-base icon-[material-symbols--select]"></span>
{/if}
- <Badge number={$selection.size} />
+ <Badge number={selection.size} />
</button>
- {#if $selection.active}
+ {#if selection.active}
<div class="rounded-group-end flex" transition:slide={slideXFast}>
- <button type="button" class="btn-slate" title="Select all" on:click={all}>
- <span class="icon-base icon-[material-symbols--select-all]" />
+ <button
+ type="button"
+ class="btn-slate"
+ title="Select all"
+ aria-label="Select all"
+ onclick={selection.all}
+ >
+ <span class="icon-base icon-[material-symbols--select-all]"></span>
</button>
- <button type="button" class="btn-slate" title="Select none" on:click={none}>
- <span class="icon-base icon-[material-symbols--deselect]" />
+ <button
+ type="button"
+ class="btn-slate"
+ title="Select none"
+ aria-label="Select all"
+ onclick={selection.none}
+ >
+ <span class="icon-base icon-[material-symbols--deselect]"></span>
</button>
</div>
{/if}
</div>
-{#if $selection.size > 0}
+{#if selection.size > 0}
<div class="rounded-group flex" transition:fade={fadeDefault}>
- <slot />
+ {@render children?.()}
</div>
{/if}
diff --git a/frontend/src/lib/toolbar/ToggleAdvancedFilters.svelte b/frontend/src/lib/toolbar/ToggleAdvancedFilters.svelte
index 2e7869f..ee07902 100644
--- a/frontend/src/lib/toolbar/ToggleAdvancedFilters.svelte
+++ b/frontend/src/lib/toolbar/ToggleAdvancedFilters.svelte
@@ -1,39 +1,42 @@
<script lang="ts">
- import { page } from '$app/stores';
- import { getFilterContext } from '$lib/Filter';
+ import { page } from '$app/state';
import { navigate } from '$lib/Navigation';
import { slideXFast } from '$lib/Transitions';
import Badge from '$lib/components/Badge.svelte';
import { slide } from 'svelte/transition';
- import { getToolbarContext } from './Toolbar.svelte';
+ import type { ToolbarState } from './Toolbar.svelte';
- const toolbar = getToolbarContext();
- const filter = getFilterContext();
+ interface Props extends ToolbarState {
+ filterSize: number;
+ }
+
+ let { expanded, toggle, filterSize }: Props = $props();
</script>
<div class="rounded-group flex">
<button
- class:toggled={$toolbar.expand}
+ class:toggled={expanded}
class="btn-slate relative"
- title={`${$toolbar.expand ? 'Hide' : 'Show'} filters`}
- on:click={() => ($toolbar.expand = !$toolbar.expand)}
+ title={`${expanded ? 'Hide' : 'Show'} filters`}
+ onclick={toggle}
>
- {#if $toolbar.expand}
- <span class="icon-base icon-[material-symbols--filter-alt]" />
+ {#if expanded}
+ <span class="icon-base icon-[material-symbols--filter-alt]"></span>
{:else}
- <span class="icon-base icon-[material-symbols--filter-alt-outline]" />
+ <span class="icon-base icon-[material-symbols--filter-alt-outline]"></span>
{/if}
- <Badge number={$filter.include.size + $filter.exclude.size} />
+ <Badge number={filterSize} />
</button>
- {#if $filter.include.size + $filter.exclude.size > 0}
+ {#if filterSize > 0}
<button
class="btn-slate relative hover:bg-rose-700"
- on:click={() => navigate({ filter: {} }, $page.url.searchParams)}
+ onclick={() => navigate({ filter: {} }, page.url.searchParams)}
transition:slide={slideXFast}
title="Reset filters"
+ aria-label="Reset filters"
>
<div class="flex">
- <span class="icon-base icon-[material-symbols--filter-alt-off]" />
+ <span class="icon-base icon-[material-symbols--filter-alt-off]"></span>
</div>
</button>
{/if}
diff --git a/frontend/src/lib/toolbar/Toolbar.svelte b/frontend/src/lib/toolbar/Toolbar.svelte
index e87d731..03cd892 100644
--- a/frontend/src/lib/toolbar/Toolbar.svelte
+++ b/frontend/src/lib/toolbar/Toolbar.svelte
@@ -1,23 +1,25 @@
-<script lang="ts" context="module">
- import { writable, type Writable } from 'svelte/store';
+<script lang="ts">
+ import { type Snippet } from 'svelte';
- interface ToolbarContext {
- expand: boolean;
+ export interface ToolbarState {
+ expanded: boolean;
+ toggle: () => void;
}
- function initToolbarContext() {
- return setContext<Writable<ToolbarContext>>('toolbar', writable({ expand: false }));
+ interface Props {
+ start?: Snippet<[ToolbarState]>;
+ center?: Snippet<[ToolbarState]>;
+ end?: Snippet<[ToolbarState]>;
+ expansion?: Snippet;
}
- export function getToolbarContext() {
- return getContext<Writable<ToolbarContext>>('toolbar');
- }
-</script>
+ let { start, center, end, expansion }: Props = $props();
-<script lang="ts">
- import { getContext, setContext } from 'svelte';
+ let expanded = $state(false);
- const toolbar = initToolbarContext();
+ function toggle() {
+ expanded = !expanded;
+ }
</script>
<div class="flex flex-col">
@@ -25,18 +27,18 @@
class="flex flex-row flex-wrap gap-4 text-sm xl:grid xl:grid-flow-col xl:grid-cols-[1fr_2fr_1fr]"
>
<div class="flex flex-row justify-start gap-2">
- <slot name="start" />
+ {@render start?.({ expanded, toggle })}
</div>
<div class="flex flex-row flex-wrap justify-start gap-2 xl:flex-nowrap xl:justify-center">
- <slot name="center" />
+ {@render center?.({ expanded, toggle })}
</div>
<div class="flex flex-row justify-end gap-2">
- <slot name="end" />
+ {@render end?.({ expanded, toggle })}
</div>
</div>
- {#if $toolbar.expand}
+ {#if expanded}
<div class="mt-4">
- <slot />
+ {@render expansion?.()}
</div>
{/if}
</div>