Drops the three tabs for a wide dialog: cover and files in a fixed rail, all fourteen metadata fields grouped beside them, Save in the footer. Adds file management, which the Files tab never had — it could only upload, never list or remove. Files now show size and format with download and delete, the latter asking whether to remove it from disk too.
69 lines
2.7 KiB
Svelte
69 lines
2.7 KiB
Svelte
<script lang="ts">
|
|
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
|
import { Button } from '$lib/components/ui/button/index.js';
|
|
import { type Book } from '$lib/schema';
|
|
import EditCover from './edit-cover.svelte';
|
|
import EditFiles from './edit-files.svelte';
|
|
import EditMetadata from './edit-metadata.svelte';
|
|
|
|
let { book, open = $bindable() }: { book?: Book; open: boolean } = $props();
|
|
|
|
/**
|
|
* The metadata form lives in a child, but Save belongs in the dialog footer —
|
|
* a submit button reaches it by id rather than the footer having to duplicate
|
|
* the submit logic.
|
|
*/
|
|
const METADATA_FORM_ID = 'edit-book-metadata';
|
|
</script>
|
|
|
|
<!--
|
|
This dialog is mounted once, in (root)/(library)/+layout.svelte, and `book`
|
|
changes underneath it as different books are edited. bookToEdit is never
|
|
cleared, so {#if book} stays true and the forms never unmount — they seed
|
|
local state from `book` on mount, so without this key you would open Edit on a
|
|
second book and see the first book's authors, tags and cover, then save them
|
|
onto the wrong record. Keying on the id remounts the forms per book.
|
|
-->
|
|
<Dialog.Root bind:open>
|
|
{#if book}
|
|
{#key book.id}
|
|
<Dialog.Content
|
|
class="flex h-[85vh] max-w-[min(72rem,95vw)] flex-col gap-0 overflow-hidden p-0 sm:max-w-[min(72rem,95vw)]"
|
|
>
|
|
<Dialog.Header class="shrink-0 space-y-0 border-b px-5 py-3 text-left">
|
|
<Dialog.Title class="truncate font-serif text-base font-normal">{book.title}</Dialog.Title
|
|
>
|
|
<Dialog.Description class="truncate text-xs">
|
|
{book.authors.map((author) => author.name).join(', ') || 'Unknown author'}
|
|
</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<!-- Rail and form scroll independently so the footer never moves -->
|
|
<div class="grid min-h-0 flex-1 grid-cols-1 md:grid-cols-[16rem_1fr]">
|
|
<aside
|
|
class="flex min-w-0 flex-col gap-5 overflow-y-auto border-b bg-sidebar p-4 md:border-r md:border-b-0"
|
|
>
|
|
<EditCover {book} />
|
|
<EditFiles {book} />
|
|
</aside>
|
|
|
|
<div class="min-h-0 overflow-y-auto p-5">
|
|
<EditMetadata {book} bind:open formId={METADATA_FORM_ID} />
|
|
</div>
|
|
</div>
|
|
|
|
<Dialog.Footer class="shrink-0 items-center gap-2 border-t px-5 py-3 sm:justify-between">
|
|
<!-- Cover and file changes hit the server as they happen, while the
|
|
fields wait for Save. Saying so is the cheapest way to stop
|
|
Cancel reading as "undo everything". -->
|
|
<p class="text-xs text-muted-foreground">Cover and file changes apply immediately</p>
|
|
<div class="flex gap-2">
|
|
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
|
|
<Button type="submit" form={METADATA_FORM_ID}>Save changes</Button>
|
|
</div>
|
|
</Dialog.Footer>
|
|
</Dialog.Content>
|
|
{/key}
|
|
{/if}
|
|
</Dialog.Root>
|