Put actual books on bookshelf view

This commit is contained in:
Evan Fiordeliso 2025-07-05 10:42:25 -04:00
parent 658713fbec
commit b9f146f922
23 changed files with 265 additions and 208 deletions

View File

@ -10,7 +10,7 @@ import { EditorCheckCommand } from "./Command";
import type { BookTrackerPluginSettings } from "@ui/settings";
import { RatingModal } from "@ui/modals";
import type { ReadingLog } from "@utils/ReadingLog";
import { READ_STATE } from "@src/const";
import { STATUS_READ } from "@src/const";
import { mkdirRecursive, dirname } from "@utils/fs";
export class LogReadingFinishedCommand extends EditorCheckCommand {
@ -70,7 +70,7 @@ export class LogReadingFinishedCommand extends EditorCheckCommand {
const endDate = moment().format("YYYY-MM-DD");
this.app.fileManager.processFrontMatter(file, (frontMatter) => {
frontMatter[this.settings.statusProperty] = READ_STATE;
frontMatter[this.settings.statusProperty] = STATUS_READ;
frontMatter[this.settings.endDateProperty] = endDate;
frontMatter[this.settings.ratingProperty] = ratings.rating;
if (this.settings.spiceProperty !== "") {

View File

@ -7,7 +7,7 @@ import {
} from "obsidian";
import { EditorCheckCommand } from "./Command";
import type { BookTrackerPluginSettings } from "@ui/settings";
import { IN_PROGRESS_STATE } from "@src/const";
import { STATUS_IN_PROGRESS } from "@src/const";
export class LogReadingStartedCommand extends EditorCheckCommand {
constructor(
@ -38,7 +38,7 @@ export class LogReadingStartedCommand extends EditorCheckCommand {
const startDate = moment().format("YYYY-MM-DD");
this.app.fileManager.processFrontMatter(file, (frontMatter) => {
frontMatter[this.settings.statusProperty] = IN_PROGRESS_STATE;
frontMatter[this.settings.statusProperty] = STATUS_IN_PROGRESS;
frontMatter[this.settings.startDateProperty] = startDate;
});

View File

@ -8,7 +8,7 @@ import {
import { EditorCheckCommand } from "./Command";
import type { BookTrackerPluginSettings } from "@ui/settings";
import type { ReadingLog } from "@utils/ReadingLog";
import { TO_BE_READ_STATE } from "@src/const";
import { STATUS_TO_BE_READ } from "@src/const";
export class ResetReadingStatusCommand extends EditorCheckCommand {
constructor(
@ -38,7 +38,7 @@ export class ResetReadingStatusCommand extends EditorCheckCommand {
const file = ctx.file!;
this.app.fileManager.processFrontMatter(file, (frontMatter) => {
frontMatter[this.settings.statusProperty] = TO_BE_READ_STATE;
frontMatter[this.settings.statusProperty] = STATUS_TO_BE_READ;
frontMatter[this.settings.startDateProperty] = "";
frontMatter[this.settings.endDateProperty] = "";
});

View File

@ -1,6 +1,6 @@
export const TO_BE_READ_STATE = "To Be Read";
export const IN_PROGRESS_STATE = "Currently Reading";
export const READ_STATE = "Read";
export const STATUS_TO_BE_READ = "To Be Read";
export const STATUS_IN_PROGRESS = "Currently Reading";
export const STATUS_READ = "Read";
export const CONTENT_TYPE_EXTENSIONS: Record<string, string> = {
"image/jpeg": "jpg",

View File

@ -1,4 +1,8 @@
import type { IN_PROGRESS_STATE, READ_STATE, TO_BE_READ_STATE } from "./const";
import type {
STATUS_IN_PROGRESS,
STATUS_READ,
STATUS_TO_BE_READ,
} from "./const";
export interface Author {
name: string;
@ -24,8 +28,8 @@ export interface Book {
isbn13: string;
}
export type ToBeReadState = typeof TO_BE_READ_STATE;
export type InProgressState = typeof IN_PROGRESS_STATE;
export type ReadState = typeof READ_STATE;
export type ToBeReadState = typeof STATUS_TO_BE_READ;
export type InProgressState = typeof STATUS_IN_PROGRESS;
export type ReadState = typeof STATUS_READ;
export type ReadingState = ToBeReadState | InProgressState | ReadState;

View File

@ -2,6 +2,7 @@ import { registerCodeBlockRenderer } from ".";
import { SvelteCodeBlockRenderer } from "./SvelteCodeBlockRenderer";
import BookshelfCodeBlockView from "./BookshelfCodeBlockView.svelte";
import type BookTrackerPlugin from "@src/main";
import z from "zod/v4";
export function registerBookshelfCodeBlockProcessor(
plugin: BookTrackerPlugin
@ -13,6 +14,12 @@ export function registerBookshelfCodeBlockProcessor(
);
}
export const BookshelfSettingsSchema = z.object({
titleProperty: z.string(),
subtitleProperty: z.optional(z.string()),
authorsProperty: z.string(),
});
export class BookshelfCodeBlockRenderer extends SvelteCodeBlockRenderer<
typeof BookshelfCodeBlockView
> {

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { READ_STATE } from "@src/const";
import { STATUS_TO_BE_READ } from "@src/const";
import type BookTrackerPlugin from "@src/main";
import type { ReadingState } from "@src/types";
import Book from "@ui/components/bookshelf/Book.svelte";
@ -9,26 +9,42 @@
import {
createMetadata,
setMetadataContext,
type FileMetadata,
} from "@ui/stores/metadata.svelte";
import {
createSettings,
setSettingsContext,
} from "@ui/stores/settings.svelte";
import { COLOR_NAMES } from "@utils/color";
import { randomElement } from "@utils/rand";
import { COLOR_NAMES, type ColorName } from "@utils/color";
import { randomElement, randomFloat, randomInt } from "@utils/rand";
import { onDestroy } from "svelte";
import { BookshelfSettingsSchema } from "./BookshelfCodeBlock";
import { parseYaml, TFile } from "obsidian";
interface Props {
plugin: BookTrackerPlugin;
source: string;
}
const { plugin }: Props = $props();
interface BookData {
title: string;
subtitle?: string;
author: string;
width: number;
color: ColorName;
design: (typeof designs)[number];
orientation: undefined | "tilted" | "on-display";
file: TFile;
}
const { plugin, source }: Props = $props();
const settings = $derived(BookshelfSettingsSchema.parse(parseYaml(source)));
const settingsStore = createSettings(plugin);
setSettingsContext(settingsStore);
let stateFilter: ReadingState = $state(READ_STATE);
let stateFilter: ReadingState = $state(STATUS_TO_BE_READ);
const metadataStore = createMetadata(plugin, stateFilter);
setMetadataContext(metadataStore);
@ -40,155 +56,113 @@
"split-bands",
] as const;
function randomDesign() {
return randomElement(designs);
const randomDesign = () => randomElement(designs);
const randomColor = () => randomElement(COLOR_NAMES);
function randomOrientation() {
const n = randomFloat();
if (n < 0.55) {
return undefined;
} else if (n < 0.8) {
return "tilted";
} else {
return "on-display";
}
}
const randomStackChance = () => randomFloat() > 0.9;
function randomStackSize() {
const n = randomFloat();
if (n < 0.2) {
return 5;
} else if (n < 0.5) {
return 4;
} else if (n < 0.8) {
return 3;
} else if (n < 0.98) {
return 2;
} else {
return 1;
}
}
const calculateWidth = (pageCount: number) =>
Math.max(10, Math.min(1000, 200 + pageCount * 10));
function getBookData(metadata: FileMetadata): BookData {
return {
title: metadata.frontmatter[settings.titleProperty],
subtitle: settings.subtitleProperty
? metadata.frontmatter[settings.subtitleProperty]
: undefined,
author: metadata.frontmatter[settings.authorsProperty].join(", "),
width: calculateWidth(
metadata.frontmatter[settingsStore.settings.pageCountProperty],
),
color: randomColor(),
design: randomDesign(),
orientation: randomOrientation(),
file: metadata.file,
};
}
function randomColor() {
return randomElement(COLOR_NAMES);
}
const books = $derived.by(() => {
let books: (BookData | BookData[])[] = [];
for (let i = 0; i < metadataStore.metadata.length; i++) {
if (randomStackChance()) {
const booksRemaining = metadataStore.metadata.length - i;
const stackSize = randomInt(
1,
Math.min(booksRemaining, randomStackSize()),
);
books.push(
metadataStore.metadata
.slice(i, i + stackSize)
.map(getBookData),
);
i += stackSize - 1;
} else {
books.push(getBookData(metadataStore.metadata[i]));
}
}
return books;
});
onDestroy(() => metadataStore.destroy());
</script>
<Bookshelf>
<Book
title="Hello World"
width={1120}
color={randomColor()}
design={randomDesign()}
/>
<Book
title="White Space"
width={700}
color={randomColor()}
design={randomDesign()}
/>
<Book
title="The Art of Computer Programming Vol 1"
width={1156}
color={randomColor()}
design={randomDesign()}
/>
<Book
title="Cascading Style Sheets"
subtitle="Guide to Design"
width={560}
color={randomColor()}
design={randomDesign()}
orientation="tilted"
/>
<Book
title="HTML5"
subtitle="Welcome to the Web"
width={1350}
color={randomColor()}
design={randomDesign()}
/>
<BookStack totalChildren={2}>
<BookStackElement
title="Coding for Dummies"
subtitle="JS tutorial"
color={randomColor()}
design={randomDesign()}
/>
<BookStackElement
title="Coding for Dummies"
subtitle="C# tutorial"
color={randomColor()}
design={randomDesign()}
/>
</BookStack>
<Book
title="CoffeeScript"
subtitle="The JS Alternative"
author="The Dev Guy"
color={randomColor()}
design={randomDesign()}
orientation="on-display"
/>
<Book
title="Cheat Sheet"
subtitle="Guide to Design"
width={870}
color={randomColor()}
design={randomDesign()}
/>
<Book
title="Psychology of Colors"
width={540}
color={randomColor()}
design={randomDesign()}
/>
<Book
title="TypeScript"
subtitle="Intro JS to type checking"
width={1130}
color={randomColor()}
design={randomDesign()}
/>
<Book
title="Testing"
width={10}
color={randomColor()}
design={randomDesign()}
/>
<Book
title="JavaScript"
subtitle="The Definitive Guide"
author="David Flanagan"
color={randomColor()}
design={randomDesign()}
orientation="on-display"
/>
<Book
title="Pragmatic Programmer"
color={randomColor()}
design={randomDesign()}
/>
<Book title="White Space" color={randomColor()} design={randomDesign()} />
<Book
title="W3 Schools"
subtitle="The best around"
color={randomColor()}
design={randomDesign()}
orientation="tilted"
/>
<Book
title="UI/UX"
subtitle="Guide to Mobile Development"
author="John Doe"
color={randomColor()}
design={randomDesign()}
orientation="on-display"
/>
<Book
title="Clean Code"
color={randomColor()}
design={randomDesign()}
orientation="tilted"
/>
<Book title="Docs for Devs" color={randomColor()} design={randomDesign()} />
<BookStack totalChildren={4}>
<BookStackElement
title="The Art of Computer Programming Vol 1"
color={randomColor()}
design={randomDesign()}
/>
<BookStackElement
title="The Art of Computer Programming Vol 2"
color={randomColor()}
design={randomDesign()}
/>
<BookStackElement
title="The Art of Computer Programming Vol 3"
color={randomColor()}
design={randomDesign()}
/>
<BookStackElement
title="The Art of Computer Programming Vol 4a"
color={randomColor()}
design={randomDesign()}
/>
</BookStack>
{#each books as book}
{#if Array.isArray(book)}
<BookStack totalChildren={book.length}>
{#each book as bookData}
<BookStackElement
title={bookData.title}
subtitle={bookData.subtitle}
color={bookData.color}
design={bookData.design}
onClick={() =>
plugin.app.workspace.openLinkText(
bookData.file.path,
"",
true,
)}
/>
{/each}
</BookStack>
{:else}
<Book
title={book.title}
subtitle={book.subtitle}
author={book.author}
width={book.width}
color={book.color}
design={book.design}
orientation={book.orientation}
onClick={() =>
plugin.app.workspace.openLinkText(book.file.path, "", true)}
/>
{/if}
{/each}
</Bookshelf>

View File

@ -6,6 +6,7 @@
import { createReadingLog } from "@ui/stores/reading-log.svelte";
import { ALL_TIME } from "@ui/stores/date-filter.svelte";
import { onDestroy } from "svelte";
import { getLinkpath } from "obsidian";
interface Props {
plugin: BookTrackerPlugin;
@ -14,9 +15,7 @@
const { plugin }: Props = $props();
function bookUri(book: string) {
const v = encodeURIComponent(plugin.app.vault.getName());
const f = encodeURIComponent(book + ".md");
return `obsidian://open?vault=${v}&file=${f}`;
return getLinkpath(book + ".md");
}
const store = createReadingLog(plugin.readingLog);

View File

@ -26,6 +26,7 @@
orientation?: "tilted" | "on-display";
height?: number;
width?: number;
onClick?: () => void;
}
let {
@ -37,6 +38,7 @@
orientation,
height,
width,
onClick,
}: BookProps = $props();
function widthCheck(input: number | undefined) {
@ -69,10 +71,11 @@
{design}
{height}
{width}
{onClick}
/>
</BookTiltedDefault>
{:else if orientation === "on-display"}
<BookOnDisplay color={color.hex}>
<BookOnDisplay color={color.hex} {onClick}>
<div
class="book-display-crease"
style:--book-color={color.hex}
@ -84,19 +87,19 @@
</BookOnDisplay>
{/if}
{:else if design === "split-bands"}
<BookSplitBands color={color.hex} width={verifiedWidth}>
<BookSplitBands color={color.hex} width={verifiedWidth} {onClick}>
<BookText {title} {subtitle} />
</BookSplitBands>
{:else if design === "dual-top-bands"}
<BookDualTopBands color={color.hex} width={verifiedWidth}>
<BookDualTopBands color={color.hex} width={verifiedWidth} {onClick}>
<BookText {title} {subtitle} />
</BookDualTopBands>
{:else if design === "colored-spine"}
<BookColoredSpine color={color.hex} width={verifiedWidth}>
<BookColoredSpine color={color.hex} width={verifiedWidth} {onClick}>
<BookText {title} {subtitle} />
</BookColoredSpine>
{:else}
<BookDefault color={color.hex} width={verifiedWidth}>
<BookDefault color={color.hex} width={verifiedWidth} {onClick}>
<BookText {title} {subtitle} />
</BookDefault>
{/if}

View File

@ -11,6 +11,7 @@
subtitle?: string;
color?: ColorName | string;
design?: "default" | "split-bands" | "dual-top-bands" | "colored-spine";
onClick?: () => void;
}
let {
@ -18,6 +19,7 @@
subtitle,
color: colorRaw = "green",
design,
onClick,
}: Props = $props();
const color = $derived(
@ -29,19 +31,19 @@
<li class="bookshelf__bookstack-elem">
{#if design === "split-bands"}
<BookStackSplitBands color={color.hex}>
<BookStackSplitBands color={color.hex} {onClick}>
<BookText {title} {subtitle} />
</BookStackSplitBands>
{:else if design === "dual-top-bands"}
<BookStackDualTopBands color={color.hex}>
<BookStackDualTopBands color={color.hex} {onClick}>
<BookText {title} {subtitle} />
</BookStackDualTopBands>
{:else if design === "colored-spine"}
<BookStackColoredSpine color={color.hex}>
<BookStackColoredSpine color={color.hex} {onClick}>
<BookText {title} {subtitle} />
</BookStackColoredSpine>
{:else}
<BookStackDefault color={color.hex}>
<BookStackDefault color={color.hex} {onClick}>
<BookText {title} {subtitle} />
</BookStackDefault>
{/if}

View File

@ -19,6 +19,7 @@ $bookEdge: 2px;
display: inline-flex;
flex-flow: column nowrap;
list-style: none;
float: left;
margin: 0;
padding: 0;

View File

@ -7,9 +7,10 @@
children?: Snippet;
color?: string;
width?: number;
onClick?: () => void;
}
let { children, color = "green", width = 40 }: Props = $props();
let { children, color = "green", width = 40, onClick }: Props = $props();
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
const borderRightColor = $derived(chroma(color).mix("black", 0.04));
@ -26,6 +27,10 @@
style:--book-width={width + "px"}
style:width={width + "px"}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -7,9 +7,10 @@
children?: Snippet;
color?: string;
width?: number;
onClick?: () => void;
}
let { children, color = "green", width = 40 }: Props = $props();
let { children, color = "green", width = 40, onClick }: Props = $props();
const backgroundColor = $derived(chroma(color));
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
@ -25,6 +26,10 @@
style:--book-width={width + "px"}
style:width={width + "px"}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -7,9 +7,10 @@
children?: Snippet;
color?: string;
width?: number;
onClick?: () => void;
}
let { children, color = "green", width = 40 }: Props = $props();
let { children, color = "green", width = 40, onClick }: Props = $props();
const backgroundColor = $derived(chroma(color));
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
@ -27,6 +28,10 @@
style:--book-width={width + "px"}
style:width={width + "px"}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -4,14 +4,19 @@
interface Props {
children?: Snippet;
color?: string;
onClick?: () => void;
}
let { children, color = "green" }: Props = $props();
let { children, color = "green", onClick }: Props = $props();
</script>
<div
class="bookshelf__book-wrapper bookshelf__book-onDisplay"
style:--book-color={color}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -7,9 +7,10 @@
children?: Snippet;
color?: string;
width?: number;
onClick?: () => void;
}
let { children, color = "green", width = 40 }: Props = $props();
let { children, color = "green", width = 40, onClick }: Props = $props();
const backgroundColor = $derived(chroma(color));
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
@ -27,6 +28,10 @@
style:--book-width={width + "px"}
style:width={width + "px"}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -6,9 +6,10 @@
interface Props {
children?: Snippet;
color?: string;
onClick?: () => void;
}
let { children, color = "green" }: Props = $props();
let { children, color = "green", onClick }: Props = $props();
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
const borderRightColor = $derived(chroma(color).mix("black", 0.04));
@ -22,6 +23,10 @@
style:--book-border-right-color={borderRightColor.css()}
style:--book-background-color={backgroundColor.css()}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -6,9 +6,10 @@
interface Props {
children?: Snippet;
color?: string;
onClick?: () => void;
}
let { children, color = "green" }: Props = $props();
let { children, color = "green", onClick }: Props = $props();
const backgroundColor = $derived(chroma(color));
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
@ -22,6 +23,10 @@
style:--book-border-left-color={borderLeftColor.css()}
style:--book-border-right-color={borderRightColor.css()}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -6,9 +6,10 @@
interface Props {
children?: Snippet;
color?: string;
onClick?: () => void;
}
let { children, color = "green" }: Props = $props();
let { children, color = "green", onClick }: Props = $props();
const backgroundColor = $derived(chroma(color));
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
@ -24,6 +25,10 @@
style:--book-border-right-color={borderRightColor.css()}
style:--book-band-color={bandColor.css()}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -6,9 +6,10 @@
interface Props {
children?: Snippet;
color?: string;
onClick?: () => void;
}
let { children, color = "green" }: Props = $props();
let { children, color = "green", onClick }: Props = $props();
const backgroundColor = $derived(chroma(color));
const borderLeftColor = $derived(chroma(color).mix("white", 0.04));
@ -24,6 +25,10 @@
style:--book-border-right-color={borderRightColor.css()}
style:--book-band-color={bandColor.css()}
style:color={textColor}
onclick={onClick}
onkeydown={(ev) => ev.key === "Enter" && onClick?.()}
role="link"
tabindex="0"
>
{@render children?.()}
</div>

View File

@ -1,10 +1,11 @@
import { READ_STATE } from "@src/const";
import { STATUS_READ } from "@src/const";
import type { CachedMetadata, TFile } from "obsidian";
import { getContext, setContext } from "svelte";
import { getSettingsContext } from "./settings.svelte";
import type BookTrackerPlugin from "@src/main";
import { createDateFilter, type DateFilterStore } from "./date-filter.svelte";
import type { ReadingState } from "@src/types";
import type { BookTrackerPluginSettings } from "@ui/settings";
export type FileMetadata = {
file: TFile;
@ -16,34 +17,45 @@ export type FileProperty = {
value: any;
};
interface MetadataStore extends DateFilterStore {
export interface MetadataStore extends DateFilterStore {
get metadata(): FileMetadata[];
destroy(): void;
}
export function createMetadata(
function getMetadata(
plugin: BookTrackerPlugin,
state: ReadingState = READ_STATE
): MetadataStore {
const settingsStore = getSettingsContext();
settings: BookTrackerPluginSettings,
state: ReadingState
): FileMetadata[] {
const metadata: FileMetadata[] = [];
for (const file of plugin.app.vault.getMarkdownFiles()) {
const frontmatter =
plugin.app.metadataCache.getFileCache(file)?.frontmatter ?? {};
let metadata: FileMetadata[] = $state([]);
$effect(() => {
const newMetadata: FileMetadata[] = [];
for (const file of plugin.app.vault.getMarkdownFiles()) {
const frontmatter =
plugin.app.metadataCache.getFileCache(file)?.frontmatter ?? {};
if (frontmatter[settingsStore.settings.statusProperty] !== state) {
continue;
}
newMetadata.push({ file, frontmatter });
if (frontmatter[settings.statusProperty] !== state) {
continue;
}
metadata = newMetadata;
metadata.push({ file, frontmatter });
}
return metadata;
}
export function createMetadata(
plugin: BookTrackerPlugin,
statusFilter: ReadingState = STATUS_READ
): MetadataStore {
const settingsStore = getSettingsContext();
const initialMetadata = getMetadata(
plugin,
settingsStore.settings,
statusFilter
);
let metadata: FileMetadata[] = $state(initialMetadata);
$effect(() => {
metadata = getMetadata(plugin, settingsStore.settings, statusFilter);
});
function onChanged(file: TFile, _data: string, cache: CachedMetadata) {
@ -57,7 +69,6 @@ export function createMetadata(
return f;
});
}
plugin.registerEvent(plugin.app.metadataCache.on("changed", onChanged));
function onDeleted(file: TFile) {
@ -69,13 +80,17 @@ export function createMetadata(
() => metadata,
(f) => {
// @ts-expect-error Moment is provided by Obsidian
return moment(f.frontmatter[settings.endDateProperty]);
return moment(
f.frontmatter[settingsStore.settings.endDateProperty]
);
}
);
return {
get metadata() {
return dateFilter.filteredData;
return statusFilter === STATUS_READ
? dateFilter.filteredData
: metadata;
},
get filterYear() {
return dateFilter.filterYear;

View File

@ -2,7 +2,7 @@ import type BookTrackerPlugin from "@src/main";
import { type BookTrackerSettings, DEFAULT_SETTINGS } from "../settings/types";
import { getContext, setContext } from "svelte";
interface SettingsStore {
export interface SettingsStore {
settings: BookTrackerSettings;
load(): Promise<void>;
}

View File

@ -1,3 +1,10 @@
export function randomFloat(min?: number, max?: number) {
const minVal = min === undefined && max === undefined ? 0 : min ?? 0;
const maxVal = max === undefined ? (min === undefined ? 1 : min) : max;
return Math.random() * (maxVal - minVal) + minVal;
}
export function randomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}