obsidian-book-tracker/src/ui/components/BookshelfView.svelte

152 lines
3.6 KiB
Svelte

<script lang="ts">
import type BookTrackerPlugin from "@src/main";
import Book from "@ui/components/bookshelf/Book.svelte";
import Bookshelf from "@ui/components/bookshelf/Bookshelf.svelte";
import BookStack from "@ui/components/bookshelf/BookStack.svelte";
import {
getMetadataContext,
type FileMetadata,
} from "@ui/stores/metadata.svelte";
import { COLOR_NAMES, type ColorName } from "@utils/color";
import { randomElement, randomFloat } from "@utils/rand";
import { v4 as uuidv4 } from "uuid";
import memoize from "just-memoize";
import type { TFile } from "obsidian";
interface Props {
plugin: BookTrackerPlugin;
}
interface BookData {
id: string;
title: string;
subtitle?: string;
authors: string[];
width: number;
color: ColorName;
design: (typeof designs)[number];
orientation: "default" | "tilted" | "front";
file: TFile;
}
interface BookStackData {
id: string;
books: BookData[];
}
const { plugin }: Props = $props();
const metadataStore = getMetadataContext();
const designs = ["default", "dual-top-bands", "split-bands"] as const;
const randomDesign = () => randomElement(designs);
const randomColor = () => randomElement(COLOR_NAMES);
function randomOrientation() {
const n = randomFloat();
if (n < 0.55) {
return "default";
} else if (n < 0.8) {
return "tilted";
} else {
return "front";
}
}
const randomStackChance = () => randomFloat() > 0.9;
function randomStackSize() {
const n = randomFloat();
if (n < 0.15) {
return 5;
} else if (n < 0.3) {
return 4;
} else if (n < 0.5) {
return 3;
} else if (n < 0.8) {
return 2;
} else {
return 1;
}
}
const getBookData = memoize(
(meta: FileMetadata): BookData => {
const orientation = randomOrientation();
return {
id: meta.file.path,
title: meta.book.title,
subtitle:
meta.book.subtitle === "" ? undefined : meta.book.subtitle,
authors: meta.book.authors,
width: Math.min(Math.max(20, meta.book.pageCount / 10), 100),
color: randomColor(),
design: orientation === "front" ? "default" : randomDesign(),
orientation: randomOrientation(),
file: meta.file,
};
},
(metadata: FileMetadata) => metadata.file.path,
);
let books: (BookData | BookStackData)[] = $state([]);
$effect(() => {
let newBooks: (BookData | BookStackData)[] = [];
for (let i = 0; i < metadataStore.metadata.length; i++) {
if (randomStackChance()) {
const booksRemaining = metadataStore.metadata.length - i;
const stackSize = Math.min(booksRemaining, randomStackSize());
newBooks.push({
id: uuidv4(),
books: metadataStore.metadata
.slice(i, i + stackSize)
.map(getBookData),
});
i += stackSize - 1;
} else {
newBooks.push(getBookData(metadataStore.metadata[i]));
}
}
books = newBooks;
});
</script>
<Bookshelf>
{#each books as book (book.id)}
{#if "books" in book}
<BookStack>
{#each book.books as bookData (bookData.id)}
<Book
title={bookData.title}
subtitle={bookData.subtitle}
authors={bookData.authors}
width={bookData.width}
color={bookData.color}
design={bookData.design}
orientation="flat"
onClick={() =>
plugin.app.workspace
.getLeaf(false)
.openFile(bookData.file)}
/>
{/each}
</BookStack>
{:else}
<Book
title={book.title}
subtitle={book.subtitle}
authors={book.authors}
width={book.width}
color={book.color}
design={book.design}
orientation={book.orientation}
onClick={() =>
plugin.app.workspace.getLeaf(false).openFile(book.file)}
/>
{/if}
{/each}
</Bookshelf>