summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib
diff options
context:
space:
mode:
authorWolfgang Müller2025-02-20 19:33:55 +0100
committerWolfgang Müller2025-02-20 19:54:14 +0100
commit361f506cd3677f61d2203ff91fab70ba3a1c5851 (patch)
tree6703aa5084c37832b263863321ff3667f03bb31a /frontend/src/lib
parent35f8a1a24f803be917d5982cd271f4352133b62a (diff)
downloadhircine-361f506cd3677f61d2203ff91fab70ba3a1c5851.tar.gz
frontend: Introduce ComicCard and ArchiveCard
Instead of repeatedly supplying Card content in all the places it is required, it makes more sense to create dedicated ComicCard and ArchiveCard components. These wrap around Card itself and can be used in a more straightforward and consistent fashion. Whilst we are here, simplify and streamline the display of Comic and Archive metadata by introducing a Card footer. The footer is used for information on page count, release date, and archive size.
Diffstat (limited to 'frontend/src/lib')
-rw-r--r--frontend/src/lib/components/ArchiveCard.svelte39
-rw-r--r--frontend/src/lib/components/Card.svelte39
-rw-r--r--frontend/src/lib/components/ComicCard.svelte75
-rw-r--r--frontend/src/lib/pills/ComicPills.svelte37
-rw-r--r--frontend/src/lib/pills/FooterPill.svelte15
-rw-r--r--frontend/src/lib/tabs/ArchiveDetails.svelte7
-rw-r--r--frontend/src/lib/tabs/ArchiveEdit.svelte8
7 files changed, 150 insertions, 70 deletions
diff --git a/frontend/src/lib/components/ArchiveCard.svelte b/frontend/src/lib/components/ArchiveCard.svelte
new file mode 100644
index 0000000..c9d283b
--- /dev/null
+++ b/frontend/src/lib/components/ArchiveCard.svelte
@@ -0,0 +1,39 @@
+<script lang="ts">
+ import type { ArchiveFragment } from '$gql/graphql';
+ import FooterPill from '$lib/pills/FooterPill.svelte';
+ import { filesize } from 'filesize';
+ import { type Snippet } from 'svelte';
+ import Card from './Card.svelte';
+
+ interface Props {
+ archive: ArchiveFragment;
+ overlay?: Snippet;
+ onclick?: (event: MouseEvent) => void;
+ }
+
+ let { archive, overlay, onclick }: Props = $props();
+
+ let details = $derived({
+ title: archive.name,
+ cover: archive.cover
+ });
+ let href = $derived(`/archives/${archive.id.toString()}`);
+</script>
+
+<Card {details} {href} {onclick} {overlay}>
+ {#snippet footer()}
+ <div class="flex flex-wrap gap-1">
+ <FooterPill text={`${archive.pageCount} pages`}>
+ {#snippet icon()}
+ <span class="icon-[material-symbols--description] mr-0.5 text-sm"></span>
+ {/snippet}
+ </FooterPill>
+ <div class="flex grow"></div>
+ <FooterPill text={filesize(archive.size, { base: 2 })}>
+ {#snippet icon()}
+ <span class="icon-[material-symbols--hard-drive] mr-0.5 text-sm"></span>
+ {/snippet}
+ </FooterPill>
+ </div>
+ {/snippet}
+</Card>
diff --git a/frontend/src/lib/components/Card.svelte b/frontend/src/lib/components/Card.svelte
index b8e8ecf..8cfe34d 100644
--- a/frontend/src/lib/components/Card.svelte
+++ b/frontend/src/lib/components/Card.svelte
@@ -1,5 +1,8 @@
-<script lang="ts" module>
- import type { ComicFragment, ImageFragment } from '$gql/graphql';
+<script lang="ts">
+ import type { ImageFragment } from '$gql/graphql';
+ import { src } from '$lib/Utils';
+ import Star from '$lib/icons/Star.svelte';
+ import type { Snippet } from 'svelte';
interface CardDetails {
title: string;
@@ -8,24 +11,6 @@
cover?: ImageFragment;
}
- export function comicCard(comic: ComicFragment) {
- return {
- href: `/comics/${comic.id.toString()}`,
- details: {
- title: comic.title,
- subtitle: comic.originalTitle,
- favourite: comic.favourite,
- cover: comic.cover
- }
- };
- }
-</script>
-
-<script lang="ts">
- import { src } from '$lib/Utils';
- import Star from '$lib/icons/Star.svelte';
- import type { Snippet } from 'svelte';
-
interface Props {
href: string;
details: CardDetails;
@@ -33,6 +18,7 @@
coverOnly?: boolean;
overlay?: Snippet;
children?: Snippet;
+ footer?: Snippet;
onclick?: (event: MouseEvent) => void;
}
@@ -43,6 +29,7 @@
coverOnly = false,
overlay,
children,
+ footer,
onclick
}: Props = $props();
</script>
@@ -66,8 +53,8 @@
/>
{/if}
{#if !coverOnly}
- <article class="flex h-full flex-col gap-2 p-2">
- <header>
+ <article class="p flex h-full flex-col p-2 pb-1">
+ <header class="mb-2">
<h2 class="self-center text-sm font-medium [grid-area:title]" title={details.title}>
{details.title}
</h2>
@@ -86,9 +73,15 @@
{/if}
</header>
- <section class="max-h-full grow overflow-auto border-t border-slate-800/80 pt-2 text-xs">
+ <section class="max-h-full grow overflow-auto border-y border-slate-800/80 pt-2 text-xs">
{@render children?.()}
</section>
+
+ {#if footer}
+ <div class="mt-1 text-xs">
+ {@render footer()}
+ </div>
+ {/if}
</article>
{/if}
</a>
diff --git a/frontend/src/lib/components/ComicCard.svelte b/frontend/src/lib/components/ComicCard.svelte
new file mode 100644
index 0000000..cb73e97
--- /dev/null
+++ b/frontend/src/lib/components/ComicCard.svelte
@@ -0,0 +1,75 @@
+<script lang="ts">
+ import type { ComicFragment } from '$gql/graphql';
+ import AssociationPill from '$lib/pills/AssociationPill.svelte';
+ import FooterPill from '$lib/pills/FooterPill.svelte';
+ import TagPill from '$lib/pills/TagPill.svelte';
+ import { type Snippet } from 'svelte';
+ import Card from './Card.svelte';
+
+ interface Props {
+ comic: ComicFragment;
+ overlay?: Snippet;
+ compact?: boolean;
+ coverOnly?: boolean;
+ onclick?: (event: MouseEvent) => void;
+ }
+
+ let { comic, overlay, compact, coverOnly, onclick }: Props = $props();
+
+ let details = $derived({
+ title: comic.title,
+ subtitle: comic.originalTitle,
+ favourite: comic.favourite,
+ cover: comic.cover
+ });
+ let href = $derived(`/comics/${comic.id.toString()}`);
+</script>
+
+<Card {details} {href} {compact} {onclick} {overlay} {coverOnly}>
+ <div class="flex flex-col gap-1">
+ {#if comic.artists.length || comic.circles.length}
+ <div class="flex flex-wrap gap-1">
+ {#each comic.artists as { name } (name)}
+ <AssociationPill {name} type="artist" />
+ {/each}
+ {#each comic.circles as { name } (name)}
+ <AssociationPill {name} type="circle" />
+ {/each}
+ </div>
+ {/if}
+ {#if comic.characters.length || comic.worlds.length}
+ <div class="flex flex-wrap gap-1">
+ {#each comic.worlds as { name } (name)}
+ <AssociationPill {name} type="world" />
+ {/each}
+ {#each comic.characters as { name } (name)}
+ <AssociationPill {name} type="character" />
+ {/each}
+ </div>
+ {/if}
+ {#if comic.tags.length}
+ <div class="flex flex-wrap gap-1">
+ {#each comic.tags as { name, description } (name)}
+ <TagPill {name} {description} />
+ {/each}
+ </div>
+ {/if}
+ </div>
+ {#snippet footer()}
+ <div class="flex flex-wrap gap-1">
+ <FooterPill text={`${comic.pageCount} pages`}>
+ {#snippet icon()}
+ <span class="icon-[material-symbols--description] mr-0.5 text-sm"></span>
+ {/snippet}
+ </FooterPill>
+ <div class="flex grow"></div>
+ {#if comic.date}
+ <FooterPill text={comic.date}>
+ {#snippet icon()}
+ <span class="icon-[material-symbols--calendar-today] mr-0.5 text-sm"></span>
+ {/snippet}
+ </FooterPill>
+ {/if}
+ </div>
+ {/snippet}
+</Card>
diff --git a/frontend/src/lib/pills/ComicPills.svelte b/frontend/src/lib/pills/ComicPills.svelte
deleted file mode 100644
index 45c42fd..0000000
--- a/frontend/src/lib/pills/ComicPills.svelte
+++ /dev/null
@@ -1,37 +0,0 @@
-<script lang="ts">
- import type { ComicFragment } from '$gql/graphql';
- import AssociationPill from '$lib/pills/AssociationPill.svelte';
- import TagPill from '$lib/pills/TagPill.svelte';
-
- let { comic }: { comic: ComicFragment } = $props();
-</script>
-
-<div class="flex flex-col gap-1">
- {#if comic.artists.length || comic.circles.length}
- <div class="flex flex-wrap gap-1">
- {#each comic.artists as { name } (name)}
- <AssociationPill {name} type="artist" />
- {/each}
- {#each comic.circles as { name } (name)}
- <AssociationPill {name} type="circle" />
- {/each}
- </div>
- {/if}
- {#if comic.characters.length || comic.worlds.length}
- <div class="flex flex-wrap gap-1">
- {#each comic.worlds as { name } (name)}
- <AssociationPill {name} type="world" />
- {/each}
- {#each comic.characters as { name } (name)}
- <AssociationPill {name} type="character" />
- {/each}
- </div>
- {/if}
- {#if comic.tags.length}
- <div class="flex flex-wrap gap-1">
- {#each comic.tags as { name, description } (name)}
- <TagPill {name} {description} />
- {/each}
- </div>
- {/if}
-</div>
diff --git a/frontend/src/lib/pills/FooterPill.svelte b/frontend/src/lib/pills/FooterPill.svelte
new file mode 100644
index 0000000..3da1811
--- /dev/null
+++ b/frontend/src/lib/pills/FooterPill.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+ import type { Snippet } from 'svelte';
+
+ interface Props {
+ text: string;
+ icon?: Snippet;
+ }
+
+ let { text, icon }: Props = $props();
+</script>
+
+<div class="flex items-center rounded-sm p-0.5 text-zinc-300">
+ {@render icon?.()}
+ <span>{text}</span>
+</div>
diff --git a/frontend/src/lib/tabs/ArchiveDetails.svelte b/frontend/src/lib/tabs/ArchiveDetails.svelte
index b3d570f..1243162 100644
--- a/frontend/src/lib/tabs/ArchiveDetails.svelte
+++ b/frontend/src/lib/tabs/ArchiveDetails.svelte
@@ -1,8 +1,7 @@
<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';
@@ -40,9 +39,7 @@
<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>
+ <ComicCard compact {comic} />
{/each}
</div>
</div>
diff --git a/frontend/src/lib/tabs/ArchiveEdit.svelte b/frontend/src/lib/tabs/ArchiveEdit.svelte
index 83a492b..2ed0523 100644
--- a/frontend/src/lib/tabs/ArchiveEdit.svelte
+++ b/frontend/src/lib/tabs/ArchiveEdit.svelte
@@ -3,9 +3,8 @@
import { type FullArchiveFragment } from '$gql/graphql';
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';
@@ -57,12 +56,11 @@
<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)}>
+ <ComicCard compact {comic}>
{#snippet overlay()}
<AddOverlay id={comic.id} />
{/snippet}
- <ComicPills {comic} />
- </Card>
+ </ComicCard>
{/each}
</div>
</div>