summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/tabs
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/lib/tabs')
-rw-r--r--frontend/src/lib/tabs/AddOverlay.svelte23
-rw-r--r--frontend/src/lib/tabs/ArchiveDelete.svelte25
-rw-r--r--frontend/src/lib/tabs/ArchiveDetails.svelte11
-rw-r--r--frontend/src/lib/tabs/ArchiveEdit.svelte32
-rw-r--r--frontend/src/lib/tabs/ComicDelete.svelte19
-rw-r--r--frontend/src/lib/tabs/ComicDetails.svelte40
-rw-r--r--frontend/src/lib/tabs/DetailsHeader.svelte6
-rw-r--r--frontend/src/lib/tabs/DetailsSection.svelte6
-rw-r--r--frontend/src/lib/tabs/Tab.svelte24
-rw-r--r--frontend/src/lib/tabs/Tabs.svelte50
10 files changed, 135 insertions, 101 deletions
diff --git a/frontend/src/lib/tabs/AddOverlay.svelte b/frontend/src/lib/tabs/AddOverlay.svelte
index b1c98bf..4d5ec49 100644
--- a/frontend/src/lib/tabs/AddOverlay.svelte
+++ b/frontend/src/lib/tabs/AddOverlay.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
- import { updateComics } from '$gql/Mutations';
import { UpdateMode } from '$gql/graphql';
- import { getSelectionContext } from '$lib/Selection';
+ import { updateComics } from '$gql/Mutations';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
import { toastFinally } from '$lib/Toasts';
import { fadeDefault } from '$lib/Transitions';
import { getContextClient } from '@urql/svelte';
@@ -10,27 +10,30 @@
const client = getContextClient();
const selection = getSelectionContext();
- export let id: number;
+ let { id }: { id: number } = $props();
+
+ function onclick(event: MouseEvent) {
+ event.preventDefault();
- function addPages() {
updateComics(client, {
ids: id,
- input: { pages: { ids: $selection.ids, options: { mode: UpdateMode.Add } } }
+ input: { pages: { ids: selection.ids, options: { mode: UpdateMode.Add } } }
})
- .then(() => ($selection = $selection.none()))
+ .then(() => selection.none())
.catch(toastFinally);
}
</script>
-{#if $selection.size > 0}
+{#if selection.size > 0}
<div class="absolute left-1 top-1" transition:fade={fadeDefault}>
<button
type="button"
- class="btn-blue rounded-full shadow-sm shadow-black"
+ class="btn-blue rounded-full shadow-xs shadow-black"
title="Add to this comic"
- on:click|preventDefault={addPages}
+ aria-label="Add to this comic"
+ {onclick}
>
- <span class="icon-base icon-[material-symbols--note-add]" />
+ <span class="icon-base icon-[material-symbols--note-add]"></span>
</button>
</div>
{/if}
diff --git a/frontend/src/lib/tabs/ArchiveDelete.svelte b/frontend/src/lib/tabs/ArchiveDelete.svelte
index b0e3c58..d2b2465 100644
--- a/frontend/src/lib/tabs/ArchiveDelete.svelte
+++ b/frontend/src/lib/tabs/ArchiveDelete.svelte
@@ -9,7 +9,7 @@
const client = getContextClient();
- export let archive: FullArchiveFragment;
+ let { archive }: { archive: FullArchiveFragment } = $props();
function deleteArchive() {
confirmDeletion('Archive', archive.name, () => {
@@ -21,22 +21,13 @@
</script>
<div class="flex flex-col gap-2">
- <div>
- <p>
- Deleting this archive will remove the
- <span class="cursor-help font-medium underline" title={archive.path}>archive file</span> on disk.
- </p>
- {#if archive.comics.length > 0}
- <p>The following comics will also be deleted:</p>
- <ul class="ml-8 list-disc">
- {#each archive.comics as comic}
- <li><a href="/comics/{comic.id}" class="underline">{comic.title}</a></li>
- {/each}
- </ul>
- {/if}
- <p class="mt-2 font-medium">This action is irrevocable.</p>
- </div>
+ <p>
+ Deleting this archive will remove all of its comics as well as the
+ <span class="cursor-help font-medium underline" title={archive.path}>archive file</span> on
+ disk.
+ <span class="font-medium">This action is irrevocable.</span>
+ </p>
<div class="flex">
- <DeleteButton prominent on:click={deleteArchive} />
+ <DeleteButton prominent onclick={deleteArchive} />
</div>
</div>
diff --git a/frontend/src/lib/tabs/ArchiveDetails.svelte b/frontend/src/lib/tabs/ArchiveDetails.svelte
index 9554557..c1ad68e 100644
--- a/frontend/src/lib/tabs/ArchiveDetails.svelte
+++ b/frontend/src/lib/tabs/ArchiveDetails.svelte
@@ -1,14 +1,13 @@
<script lang="ts">
import type { FullArchiveFragment } from '$gql/graphql';
import { formatListSize, joinText } from '$lib/Utils';
- import Card, { comicCard } from '$lib/components/Card.svelte';
- import ComicPills from '$lib/pills/ComicPills.svelte';
+ import ComicCard from '$lib/components/ComicCard.svelte';
import { formatDistance, formatISO9075 } from 'date-fns';
import { filesize } from 'filesize';
import Header from './DetailsHeader.svelte';
import Section from './DetailsSection.svelte';
- export let archive: FullArchiveFragment;
+ let { archive }: { archive: FullArchiveFragment } = $props();
const now = Date.now();
const modifiedDate = new Date(archive.mtime);
@@ -39,10 +38,8 @@
<div class="flex flex-col gap-1">
<h2 class="text-base font-medium">Comics</h2>
<div class="flex shrink-0 flex-col gap-4">
- {#each archive.comics as comic}
- <Card compact {...comicCard(comic)}>
- <ComicPills {comic} />
- </Card>
+ {#each archive.comics as comic (comic.id)}
+ <ComicCard compact {comic} />
{/each}
</div>
</div>
diff --git a/frontend/src/lib/tabs/ArchiveEdit.svelte b/frontend/src/lib/tabs/ArchiveEdit.svelte
index 80efaed..c6ea684 100644
--- a/frontend/src/lib/tabs/ArchiveEdit.svelte
+++ b/frontend/src/lib/tabs/ArchiveEdit.svelte
@@ -1,12 +1,11 @@
<script lang="ts">
import { addComic, updateArchives } from '$gql/Mutations';
import { type FullArchiveFragment } from '$gql/graphql';
- import { getSelectionContext } from '$lib/Selection';
import { toastFinally } from '$lib/Toasts';
import AddButton from '$lib/components/AddButton.svelte';
- import Card, { comicCard } from '$lib/components/Card.svelte';
+ import ComicCard from '$lib/components/ComicCard.svelte';
import OrganizedButton from '$lib/components/OrganizedButton.svelte';
- import ComicPills from '$lib/pills/ComicPills.svelte';
+ import { getSelectionContext } from '$lib/selection/Selection.svelte';
import SelectionControls from '$lib/toolbar/SelectionControls.svelte';
import { getContextClient } from '@urql/svelte';
import AddOverlay from './AddOverlay.svelte';
@@ -14,23 +13,23 @@
const client = getContextClient();
const selection = getSelectionContext();
- export let archive: FullArchiveFragment;
+ let { archive }: { archive: FullArchiveFragment } = $props();
function addNew() {
addComic(client, {
input: {
archive: { id: archive.id },
title: archive.name,
- pages: { ids: $selection.ids },
- cover: { id: archive.pages[$selection.indices.toSorted((a, b) => a - b)[0]].id }
+ pages: { ids: selection.ids },
+ cover: { id: archive.pages[selection.indices.toSorted((a, b) => a - b)[0]].id }
}
})
.then((mutatation) => {
const data = mutatation.addComic;
if (data.__typename === 'AddComicSuccess' && !data.archivePagesRemaining) {
- $selection = $selection.clear();
+ selection.clear();
} else {
- $selection = $selection.none();
+ selection.none();
}
})
.catch(toastFinally);
@@ -46,21 +45,22 @@
<div class="flex flex-col gap-4">
<div class="flex gap-2 text-sm">
<SelectionControls page>
- <AddButton title="Add Comic from selected" on:click={addNew} />
+ <AddButton title="Add Comic from selected" onclick={addNew} />
</SelectionControls>
- <div class="grow" />
- <OrganizedButton organized={archive.organized} on:click={toggleOrganized} />
+ <div class="grow"></div>
+ <OrganizedButton organized={archive.organized} onclick={toggleOrganized} />
</div>
{#if archive.comics.length > 0}
<div class="flex flex-col gap-1">
<h2 class="text-base font-medium">Comics</h2>
<div class="flex shrink-0 flex-col gap-4">
- {#each archive.comics as comic}
- <Card compact {...comicCard(comic)}>
- <AddOverlay slot="overlay" id={comic.id} />
- <ComicPills {comic} />
- </Card>
+ {#each archive.comics as comic (comic.id)}
+ <ComicCard compact {comic}>
+ {#snippet overlay()}
+ <AddOverlay id={comic.id} />
+ {/snippet}
+ </ComicCard>
{/each}
</div>
</div>
diff --git a/frontend/src/lib/tabs/ComicDelete.svelte b/frontend/src/lib/tabs/ComicDelete.svelte
index a10f6b2..93fa106 100644
--- a/frontend/src/lib/tabs/ComicDelete.svelte
+++ b/frontend/src/lib/tabs/ComicDelete.svelte
@@ -9,9 +9,9 @@
const client = getContextClient();
- export let comic: FullComicFragment;
+ let { comic }: { comic: FullComicFragment } = $props();
- function deleteComic() {
+ function onclick() {
confirmDeletion('Comic', comic.title, () => {
deleteComics(client, { ids: comic.id })
.then(() => goto('/comics/'))
@@ -21,14 +21,13 @@
</script>
<div class="flex flex-col gap-2">
- <div>
- <p>
- Deleting this comic will make all of its pages available again for allocation. All of its
- metadata will be lost.
- </p>
- <p class="mt-2 font-medium">This action is irrevocable.</p>
- </div>
+ <p>
+ Deleting this comic will make all of its pages available again for allocation. All of its
+ metadata will be lost.
+ <span class="font-medium">This action is irrevocable.</span>
+ </p>
+
<div class="flex">
- <DeleteButton prominent on:click={deleteComic} />
+ <DeleteButton prominent {onclick} />
</div>
</div>
diff --git a/frontend/src/lib/tabs/ComicDetails.svelte b/frontend/src/lib/tabs/ComicDetails.svelte
index 0a131af..3f9090e 100644
--- a/frontend/src/lib/tabs/ComicDetails.svelte
+++ b/frontend/src/lib/tabs/ComicDetails.svelte
@@ -1,15 +1,15 @@
<script lang="ts">
- import type { ComicFilter, FullComicFragment } from '$gql/graphql';
+ import { type ComicFilter, type FullComicFragment } from '$gql/graphql';
import { CategoryLabel, CensorshipLabel, LanguageLabel, RatingLabel } from '$lib/Enums';
import { href } from '$lib/Navigation';
import { formatListSize, joinText } from '$lib/Utils';
- import AssociationPill from '$lib/pills/AssociationPill.svelte';
+ import Pill from '$lib/pills/Pill.svelte';
import TagPill from '$lib/pills/TagPill.svelte';
import { formatDistance, formatISO9075 } from 'date-fns';
import Header from './DetailsHeader.svelte';
import Section from './DetailsSection.svelte';
- export let comic: FullComicFragment;
+ let { comic }: { comic: FullComicFragment } = $props();
const now = Date.now();
const updatedDate = new Date(comic.updatedAt);
@@ -27,13 +27,13 @@
<div class="flex flex-col gap-4 text-sm">
<Header {title}>
- {#if comic.url}
- <a href={comic.url} target="_blank" rel="noreferrer" class="btn-slate" title="Open URL">
- <span class="icon-base icon-[material-symbols--link]" />
- </a>
- {/if}
- <a href={`/archives/${comic.archive.id}`} class="btn-slate" title="Go to Archive">
- <span class="icon-base icon-[material-symbols--folder-zip]" />
+ <a
+ href={`/archives/${comic.archive.id}`}
+ class="btn-slate"
+ title="Go to Archive"
+ aria-label="Go to Archive"
+ >
+ <span class="icon-base icon-[material-symbols--folder-zip]"></span>
</a>
</Header>
@@ -73,11 +73,21 @@
</Section>
</div>
+ {#if comic.url}
+ <Section title="URL">
+ <a
+ class="ellipsis-nowrap transition-colors hover:text-white hover:underline"
+ rel="noreferrer"
+ href={comic.url}>{comic.url}</a
+ >
+ </Section>
+ {/if}
+
{#if comic.artists.length}
<Section title="Artists">
{#each comic.artists as { id, name } (id)}
<a href={filterFor('artists', id)}>
- <AssociationPill {name} type="artist" />
+ <Pill highlight {name} style="artist" />
</a>
{/each}
</Section>
@@ -86,7 +96,7 @@
<Section title="Circles">
{#each comic.circles as { id, name } (id)}
<a href={filterFor('circles', id)}>
- <AssociationPill {name} type="circle" />
+ <Pill highlight {name} style="circle" />
</a>
{/each}
</Section>
@@ -95,7 +105,7 @@
<Section title="Characters">
{#each comic.characters as { id, name } (id)}
<a href={filterFor('characters', id)}>
- <AssociationPill {name} type="character" />
+ <Pill highlight {name} style="character" />
</a>
{/each}
</Section>
@@ -104,7 +114,7 @@
<Section title="Worlds">
{#each comic.worlds as { id, name } (id)}
<a href={filterFor('worlds', id)}>
- <AssociationPill {name} type="world" />
+ <Pill highlight {name} style="world" />
</a>
{/each}
</Section>
@@ -113,7 +123,7 @@
<Section title="Tags">
{#each comic.tags as { id, name, description } (id)}
<a href={filterFor('tags', id)}>
- <TagPill {name} {description} />
+ <TagPill highlight {name} {description} />
</a>
{/each}
</Section>
diff --git a/frontend/src/lib/tabs/DetailsHeader.svelte b/frontend/src/lib/tabs/DetailsHeader.svelte
index f980f75..ee5fa23 100644
--- a/frontend/src/lib/tabs/DetailsHeader.svelte
+++ b/frontend/src/lib/tabs/DetailsHeader.svelte
@@ -1,5 +1,7 @@
<script lang="ts">
- export let title: string;
+ import type { Snippet } from 'svelte';
+
+ let { title, children }: { title: string; children?: Snippet } = $props();
</script>
<div class="flex items-center gap-2">
@@ -7,5 +9,5 @@
{title}
</h2>
<div class="grow"></div>
- <slot />
+ {@render children?.()}
</div>
diff --git a/frontend/src/lib/tabs/DetailsSection.svelte b/frontend/src/lib/tabs/DetailsSection.svelte
index 9a6ad51..5514aa3 100644
--- a/frontend/src/lib/tabs/DetailsSection.svelte
+++ b/frontend/src/lib/tabs/DetailsSection.svelte
@@ -1,10 +1,12 @@
<script lang="ts">
- export let title: string;
+ import type { Snippet } from 'svelte';
+
+ let { title, children }: { title: string; children?: Snippet } = $props();
</script>
<section class="flex flex-col gap-1">
<h2 class="text-base font-medium">{title}</h2>
<div class="flex flex-wrap gap-1 text-gray-300">
- <slot />
+ {@render children?.()}
</div>
</section>
diff --git a/frontend/src/lib/tabs/Tab.svelte b/frontend/src/lib/tabs/Tab.svelte
index 0a6be57..f8dc67c 100644
--- a/frontend/src/lib/tabs/Tab.svelte
+++ b/frontend/src/lib/tabs/Tab.svelte
@@ -1,14 +1,28 @@
<script lang="ts">
- import { getTabContext } from '$lib/Tabs';
import { fadeDefault } from '$lib/Transitions';
+ import type { Snippet } from 'svelte';
import { fade } from 'svelte/transition';
+ import { getTabContext } from './Tabs.svelte';
+
+ interface Props {
+ id: string;
+ title: string;
+ initial?: boolean;
+ children: Snippet;
+ }
+
+ let { id, title, initial = false, children }: Props = $props();
const context = getTabContext();
- export let id: string;
+
+ context.tabs = { ...context.tabs, [id]: { title } };
+ if (initial) {
+ context.current = id;
+ }
</script>
-{#if $context.current === id}
- <div class="h-full overflow-auto py-2 pe-3" in:fade={fadeDefault}>
- <slot />
+{#if context.current === id}
+ <div class="h-full overflow-auto py-2 pe-3 ps-1" in:fade={fadeDefault}>
+ {@render children?.()}
</div>
{/if}
diff --git a/frontend/src/lib/tabs/Tabs.svelte b/frontend/src/lib/tabs/Tabs.svelte
index 09cdbdd..59b3220 100644
--- a/frontend/src/lib/tabs/Tabs.svelte
+++ b/frontend/src/lib/tabs/Tabs.svelte
@@ -1,28 +1,50 @@
+<script lang="ts" module>
+ import { getContext, setContext } from 'svelte';
+
+ type Tab = string;
+ type Tabs = Record<Tab, { title: string }>;
+
+ class TabContext {
+ tabs: Tabs = $state({});
+ current: Tab = $state('');
+ }
+
+ export function getTabContext() {
+ return getContext<TabContext>('tabs');
+ }
+
+ function initTabContext() {
+ return setContext('tabs', new TabContext());
+ }
+</script>
+
<script lang="ts">
- import { getTabContext } from '$lib/Tabs';
import { fadeFast } from '$lib/Transitions';
+ import type { Snippet } from 'svelte';
import { fade } from 'svelte/transition';
- const context = getTabContext();
+ let { badges = {}, children }: { badges?: Record<Tab, boolean>; children?: Snippet } = $props();
+
+ const context = initTabContext();
</script>
<div class="flex h-full max-h-full flex-col">
<nav>
- <ul class="me-3 flex border-b-2 border-slate-700 text-sm">
- {#each Object.entries($context.tabs) as [id, { title, badge }]}
+ <ul class="ms-1 me-3 flex border-b-2 border-slate-700 text-sm">
+ {#each Object.entries(context.tabs) as [id, { title }] (id)}
<li class="-mb-0.5">
<button
type="button"
- class:active={$context.current === id}
- class="relative flex gap-1 p-1 px-3 hover:border-b-2 hover:border-slate-200"
- on:click={() => ($context.current = id)}
+ class:active={context.current === id}
+ class="relative flex gap-1 p-1 px-3 hover:border-b-2 hover:border-slate-200 focus-visible:border-b-2 focus-visible:!border-slate-200 focus-visible:outline-hidden [&.active]:border-b-2 [&.active]:border-indigo-500"
+ onclick={() => (context.current = id)}
>
- {#if badge}
+ {#if badges[id]}
<div
- class="absolute right-0 top-1 h-2 w-2 rounded-full bg-emerald-400"
+ class="absolute top-1 right-0 h-2 w-2 rounded-full bg-emerald-400"
title="There are pending changes"
transition:fade={fadeFast}
- />
+ ></div>
{/if}
<span>{title}</span>
</button>
@@ -30,11 +52,5 @@
{/each}
</ul>
</nav>
- <slot />
+ {@render children?.()}
</div>
-
-<style lang="postcss">
- button.active {
- @apply border-b-2 border-indigo-500;
- }
-</style>