feat: add book cover component and detail-card list view
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index';
|
||||
import { buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
import { getBookOperationsState } from '$lib/state/bookOperations.svelte';
|
||||
import { getLibraryState } from '$lib/state/library.svelte';
|
||||
import { getBookshelfState } from '$lib/state/bookshelf.svelte';
|
||||
import { getFileType } from '$lib/utils';
|
||||
import {
|
||||
BookOpenCheck,
|
||||
BookOpenText,
|
||||
Download,
|
||||
EllipsisVertical,
|
||||
Pencil,
|
||||
Trash2
|
||||
} from '@lucide/svelte';
|
||||
import type { Book, BookFile } from '$lib/schema';
|
||||
|
||||
let { book, class: className = '' }: { book: Book; class?: string } = $props();
|
||||
|
||||
const bookOps = getBookOperationsState();
|
||||
const libraryState = getLibraryState();
|
||||
const bookshelfState = getBookshelfState();
|
||||
|
||||
function openInReader(file: BookFile) {
|
||||
const type = getFileType(file.filename);
|
||||
if (type === 'EPUB' || type === 'PDF')
|
||||
window.open(`/book/${book.id}/read/${type.toLowerCase()}/${file.id}`, '_blank', 'noopener');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--
|
||||
The per-book overflow menu, shared so the grid, list and table cannot drift
|
||||
apart. data-row-control keeps a click here from toggling row selection.
|
||||
-->
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger
|
||||
data-row-control
|
||||
aria-label="More actions for {book.title}"
|
||||
class="{buttonVariants({ variant: 'ghost', size: 'icon' })} {className}"
|
||||
>
|
||||
<EllipsisVertical class="size-4" />
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content align="end" data-row-control>
|
||||
<DropdownMenu.Group>
|
||||
{#if book.files.length > 0}
|
||||
<DropdownMenu.Item onclick={() => openInReader(book.files[0])}>
|
||||
<BookOpenText class="size-4" />
|
||||
Read
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
onclick={() => bookOps.downloadBookFile(book.id, book.files[0].id, book.files[0].filename)}
|
||||
>
|
||||
<Download class="size-4" />
|
||||
Download
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
{/if}
|
||||
|
||||
<DropdownMenu.Item
|
||||
onclick={() => {
|
||||
bookOps.bookToEdit = book;
|
||||
bookOps.editDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Pencil class="size-4" />
|
||||
Edit
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
onclick={async () => {
|
||||
if (!book.progress?.completed) await bookOps.markBooksAsComplete([book.id]);
|
||||
else await bookOps.markBooksAsIncomplete([book.id]);
|
||||
}}
|
||||
>
|
||||
<BookOpenCheck class="size-4" />
|
||||
{book.progress?.completed ? 'Mark as unfinished' : 'Mark as finished'}
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="text-destructive"
|
||||
onclick={() => {
|
||||
bookOps.deleteDialogTitle = `Delete "${book.title}"?`;
|
||||
bookOps.deleteFn = async (deleteFiles: boolean) => {
|
||||
await bookOps.deleteBooks([book.id], deleteFiles);
|
||||
libraryState.activeLibrary!.total!--;
|
||||
bookshelfState.deletedBooks([book]);
|
||||
};
|
||||
bookOps.deleteDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Trash2 class="size-4" />
|
||||
Delete
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
Reference in New Issue
Block a user