Applies ruff format, ruff check --fix and prettier, so CI can gate on all three. The surviving unused imports were all re-exports in __init__.py, now covered by a per-file ignore; the pdf.js viewer and the generated openapi types join the vendored code that eslint and prettier already skip.
101 lines
2.9 KiB
Svelte
101 lines
2.9 KiB
Svelte
<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>
|