[FA-misc] Various UI updates
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<script lang="ts" module>
|
||||
import type { NovelQuery, NovelStatus, Language } from '$lib/graphql/__generated__/graphql';
|
||||
import { TagType } from '$lib/graphql/__generated__/graphql';
|
||||
import { SystemTags } from '$lib/constants/systemTags';
|
||||
|
||||
export type NovelNode = NonNullable<NonNullable<NovelQuery['novels']>['nodes']>[number];
|
||||
|
||||
@@ -80,6 +82,8 @@
|
||||
};
|
||||
let viewerOpen = $state(false);
|
||||
let viewerIndex = $state(0);
|
||||
let activeTab = $state('chapters');
|
||||
let galleryLoaded = $state(false);
|
||||
|
||||
const DESCRIPTION_PREVIEW_LENGTH = 300;
|
||||
|
||||
@@ -110,6 +114,10 @@
|
||||
|
||||
const chapterCount = $derived(novel?.chapters?.length ?? 0);
|
||||
|
||||
// Filter out system tags for display, check for NSFW
|
||||
const displayTags = $derived(novel?.tags?.filter((tag) => tag.tagType !== TagType.System) ?? []);
|
||||
const isNsfw = $derived(novel?.tags?.some((tag) => tag.key === SystemTags.Nsfw) ?? false);
|
||||
|
||||
const canRefresh = $derived(() => {
|
||||
if (status === 'COMPLETED') return false;
|
||||
if (!lastUpdated) return true;
|
||||
@@ -146,6 +154,14 @@
|
||||
});
|
||||
|
||||
const currentImage = $derived(galleryImages[viewerIndex]);
|
||||
const imageCount = $derived(galleryImages.length);
|
||||
|
||||
// Load gallery images when tab is first activated
|
||||
$effect(() => {
|
||||
if (activeTab === 'gallery' && !galleryLoaded) {
|
||||
galleryLoaded = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Image viewer functions
|
||||
function openImageViewer(index: number) {
|
||||
@@ -321,6 +337,9 @@
|
||||
<!-- Badges -->
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
<Badge class={statusColor}>{statusLabel}</Badge>
|
||||
{#if isNsfw}
|
||||
<Badge class="bg-red-600 text-white">NSFW</Badge>
|
||||
{/if}
|
||||
<Badge variant="outline">{languageLabel}</Badge>
|
||||
{#if $isAuthenticated}
|
||||
<TooltipProvider>
|
||||
@@ -390,9 +409,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
{#if novel.tags && novel.tags.length > 0}
|
||||
{#if displayTags.length > 0}
|
||||
<div class="flex flex-wrap gap-1.5 pt-1">
|
||||
{#each novel.tags as tag (tag.key)}
|
||||
{#each displayTags as tag (tag.key)}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
href="/novels?tags={tag.key}"
|
||||
@@ -435,20 +454,20 @@
|
||||
|
||||
<!-- Tabbed Content -->
|
||||
<Card>
|
||||
<Tabs value="chapters" class="w-full">
|
||||
<Tabs bind:value={activeTab} class="w-full">
|
||||
<CardHeader class="pb-0">
|
||||
<TabsList class="grid w-full grid-cols-3 bg-muted/50 p-1 rounded-lg">
|
||||
<TabsTrigger
|
||||
value="chapters"
|
||||
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all"
|
||||
>
|
||||
Chapters
|
||||
Chapters ({chapterCount})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="gallery"
|
||||
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all"
|
||||
>
|
||||
Gallery
|
||||
Gallery ({imageCount})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="bookmarks"
|
||||
@@ -498,7 +517,7 @@
|
||||
<p class="text-muted-foreground text-sm py-4 text-center">
|
||||
No images available.
|
||||
</p>
|
||||
{:else}
|
||||
{:else if galleryLoaded}
|
||||
<div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2">
|
||||
{#each galleryImages as image, index (image.src)}
|
||||
<button
|
||||
@@ -506,13 +525,20 @@
|
||||
onclick={() => openImageViewer(index)}
|
||||
class="relative aspect-square overflow-hidden rounded-md bg-muted/50 hover:ring-2 ring-primary transition-all"
|
||||
>
|
||||
<img src={image.src} alt={image.alt} class="h-full w-full object-cover" />
|
||||
<img src={image.src} alt={image.alt} class="h-full w-full object-cover" loading="lazy" />
|
||||
{#if image.isCover}
|
||||
<Badge class="absolute top-1 left-1 text-xs">Cover</Badge>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div
|
||||
class="border-primary h-8 w-8 animate-spin rounded-full border-2 border-t-transparent"
|
||||
aria-label="Loading gallery"
|
||||
></div>
|
||||
</div>
|
||||
{/if}
|
||||
</TabsContent>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user