generated from tpl/obsidian-sample-plugin
Rename bookshelf code block to shelf and add table view
This commit is contained in:
parent
94fe4d5f1c
commit
db732fd8a6
|
@ -22,7 +22,7 @@ import { BackupReadingLogCommand } from "@commands/CreateReadingLogBackupCommand
|
|||
import { RestoreReadingLogBackupCommand } from "@commands/RestoreReadingLogBackupCommand";
|
||||
import { Goodreads } from "@data-sources/Goodreads";
|
||||
import { CreateBookFromGoodreadsUrlCommand } from "@commands/CreateBookFromGoodreadsUrlCommand";
|
||||
import { registerBookshelfCodeBlockProcessor } from "@ui/code-blocks/BookshelfCodeBlock";
|
||||
import { registerShelfCodeBlockProcessor } from "@ui/code-blocks/ShelfCodeBlock";
|
||||
|
||||
export default class BookTrackerPlugin extends Plugin {
|
||||
public settings: BookTrackerPluginSettings;
|
||||
|
@ -86,7 +86,7 @@ export default class BookTrackerPlugin extends Plugin {
|
|||
|
||||
registerReadingLogCodeBlockProcessor(this);
|
||||
registerReadingStatsCodeBlockProcessor(this);
|
||||
registerBookshelfCodeBlockProcessor(this);
|
||||
registerShelfCodeBlockProcessor(this);
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
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
|
||||
): void {
|
||||
registerCodeBlockRenderer(
|
||||
plugin,
|
||||
"bookshelf",
|
||||
(source, el) => new BookshelfCodeBlockRenderer(plugin, source, el)
|
||||
);
|
||||
}
|
||||
|
||||
export const BookshelfSettingsSchema = z.object({
|
||||
titleProperty: z.string(),
|
||||
subtitleProperty: z.optional(z.string()),
|
||||
authorsProperty: z.string(),
|
||||
});
|
||||
|
||||
export class BookshelfCodeBlockRenderer extends SvelteCodeBlockRenderer<
|
||||
typeof BookshelfCodeBlockView
|
||||
> {
|
||||
constructor(
|
||||
plugin: BookTrackerPlugin,
|
||||
source: string,
|
||||
contentEl: HTMLElement
|
||||
) {
|
||||
super(contentEl, BookshelfCodeBlockView, { props: { plugin, source } });
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
<script lang="ts">
|
||||
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";
|
||||
import Bookshelf from "@ui/components/bookshelf/Bookshelf.svelte";
|
||||
import BookStack from "@ui/components/bookshelf/BookStack.svelte";
|
||||
import BookStackElement from "@ui/components/bookshelf/BookStackElement.svelte";
|
||||
import {
|
||||
createMetadata,
|
||||
setMetadataContext,
|
||||
type FileMetadata,
|
||||
} from "@ui/stores/metadata.svelte";
|
||||
import {
|
||||
createSettings,
|
||||
setSettingsContext,
|
||||
} from "@ui/stores/settings.svelte";
|
||||
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;
|
||||
}
|
||||
|
||||
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(STATUS_TO_BE_READ);
|
||||
|
||||
const metadataStore = createMetadata(plugin, stateFilter);
|
||||
setMetadataContext(metadataStore);
|
||||
|
||||
const designs = [
|
||||
"default",
|
||||
"colored-spine",
|
||||
"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 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;
|
||||
}
|
||||
}
|
||||
|
||||
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: metadata.frontmatter[
|
||||
settingsStore.settings.pageCountProperty
|
||||
],
|
||||
color: randomColor(),
|
||||
design: randomDesign(),
|
||||
orientation: randomOrientation(),
|
||||
file: metadata.file,
|
||||
};
|
||||
}
|
||||
|
||||
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>
|
||||
{#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>
|
|
@ -0,0 +1,46 @@
|
|||
import { registerCodeBlockRenderer } from ".";
|
||||
import { SvelteCodeBlockRenderer } from "./SvelteCodeBlockRenderer";
|
||||
import ShelfCodeBockView from "./ShelfCodeBlockView.svelte";
|
||||
import type BookTrackerPlugin from "@src/main";
|
||||
import z from "zod/v4";
|
||||
import { STATUS_IN_PROGRESS, STATUS_READ, STATUS_TO_BE_READ } from "@src/const";
|
||||
|
||||
export function registerShelfCodeBlockProcessor(
|
||||
plugin: BookTrackerPlugin
|
||||
): void {
|
||||
registerCodeBlockRenderer(
|
||||
plugin,
|
||||
"shelf",
|
||||
(source, el) => new ShelfCodeBlockRenderer(plugin, source, el)
|
||||
);
|
||||
}
|
||||
|
||||
export const SHELF_VIEWS = ["table", "bookshelf"] as const;
|
||||
export type ShelfView = (typeof SHELF_VIEWS)[number];
|
||||
|
||||
export const ShelfSettingsSchema = z.object({
|
||||
statusFilter: z
|
||||
.enum([STATUS_TO_BE_READ, STATUS_IN_PROGRESS, STATUS_READ])
|
||||
.default(STATUS_TO_BE_READ),
|
||||
defaultView: z.enum(SHELF_VIEWS).default("table"),
|
||||
coverProperty: z.string(),
|
||||
titleProperty: z.string(),
|
||||
subtitleProperty: z.optional(z.string()),
|
||||
authorsProperty: z.string(),
|
||||
seriesTitleProperty: z.optional(z.string()),
|
||||
seriesNumberProperty: z.optional(z.string()),
|
||||
});
|
||||
|
||||
export class ShelfCodeBlockRenderer extends SvelteCodeBlockRenderer<
|
||||
typeof ShelfCodeBockView
|
||||
> {
|
||||
constructor(
|
||||
plugin: BookTrackerPlugin,
|
||||
source: string,
|
||||
contentEl: HTMLElement
|
||||
) {
|
||||
super(contentEl, ShelfCodeBockView, { props: { plugin, source } });
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
<script lang="ts">
|
||||
import { STATUS_IN_PROGRESS, STATUS_READ } from "@src/const";
|
||||
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 BookStackElement from "@ui/components/bookshelf/BookStackElement.svelte";
|
||||
import {
|
||||
createMetadata,
|
||||
setMetadataContext,
|
||||
type FileMetadata,
|
||||
} from "@ui/stores/metadata.svelte";
|
||||
import {
|
||||
createSettings,
|
||||
setSettingsContext,
|
||||
} from "@ui/stores/settings.svelte";
|
||||
import { COLOR_NAMES, type ColorName } from "@utils/color";
|
||||
import { randomElement, randomFloat, randomInt } from "@utils/rand";
|
||||
import { onDestroy } from "svelte";
|
||||
import { ShelfSettingsSchema } from "./ShelfCodeBlock";
|
||||
import { parseYaml, TFile } from "obsidian";
|
||||
import DateFilter from "@ui/components/DateFilter.svelte";
|
||||
import Rating from "@ui/components/Rating.svelte";
|
||||
|
||||
interface Props {
|
||||
plugin: BookTrackerPlugin;
|
||||
source: string;
|
||||
}
|
||||
|
||||
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 = ShelfSettingsSchema.parse(parseYaml(source));
|
||||
|
||||
const settingsStore = createSettings(plugin);
|
||||
setSettingsContext(settingsStore);
|
||||
|
||||
const metadataStore = createMetadata(plugin, settings.statusFilter);
|
||||
setMetadataContext(metadataStore);
|
||||
|
||||
const designs = [
|
||||
"default",
|
||||
"colored-spine",
|
||||
"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 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;
|
||||
}
|
||||
}
|
||||
|
||||
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: metadata.frontmatter[
|
||||
settingsStore.settings.pageCountProperty
|
||||
],
|
||||
color: randomColor(),
|
||||
design: randomDesign(),
|
||||
orientation: randomOrientation(),
|
||||
file: metadata.file,
|
||||
};
|
||||
}
|
||||
|
||||
let view = $state(settings.defaultView);
|
||||
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>
|
||||
|
||||
<div
|
||||
class="shelf-code-block"
|
||||
class:table-view={view === "table"}
|
||||
class:bookshelf-view={view === "bookshelf"}
|
||||
>
|
||||
<div class="controls">
|
||||
<select bind:value={view}>
|
||||
<option value="table">Table</option>
|
||||
<option value="bookshelf">Bookshelf</option>
|
||||
</select>
|
||||
{#if settings.statusFilter === STATUS_READ}
|
||||
<DateFilter store={metadataStore} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if view === "bookshelf"}
|
||||
<Bookshelf>
|
||||
{#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>
|
||||
{:else if view === "table"}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cover</th>
|
||||
<th>Title</th>
|
||||
<th>Authors</th>
|
||||
{#if settings.seriesTitleProperty}
|
||||
<th>Series</th>
|
||||
{/if}
|
||||
{#if settings.seriesNumberProperty}
|
||||
<th>#</th>
|
||||
{/if}
|
||||
{#if settings.statusFilter === STATUS_IN_PROGRESS || settings.statusFilter === STATUS_READ}
|
||||
<th>Start Date</th>
|
||||
{/if}
|
||||
{#if settings.statusFilter === STATUS_READ}
|
||||
<th>End Date</th>
|
||||
<th>Rating</th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each metadataStore.metadata as book}
|
||||
<tr>
|
||||
<td>
|
||||
<img
|
||||
src={plugin.app.vault.getResourcePath(
|
||||
plugin.app.vault.getFileByPath(
|
||||
book.frontmatter[
|
||||
settings.coverProperty
|
||||
],
|
||||
)!,
|
||||
)}
|
||||
alt={book.frontmatter[settings.titleProperty]}
|
||||
width="50"
|
||||
/>
|
||||
</td>
|
||||
<td>{book.frontmatter[settings.titleProperty]}</td>
|
||||
<td>
|
||||
{book.frontmatter[settings.authorsProperty].join(
|
||||
", ",
|
||||
)}
|
||||
</td>
|
||||
{#if settings.seriesTitleProperty}
|
||||
<td>
|
||||
{book.frontmatter[settings.seriesTitleProperty]}
|
||||
</td>
|
||||
{/if}
|
||||
{#if settings.seriesNumberProperty}
|
||||
<td>
|
||||
{book.frontmatter[
|
||||
settings.seriesNumberProperty
|
||||
]}
|
||||
</td>
|
||||
{/if}
|
||||
{#if settings.statusFilter === STATUS_IN_PROGRESS || settings.statusFilter === STATUS_READ}
|
||||
<td>
|
||||
{book.frontmatter[
|
||||
settingsStore.settings.startDateProperty
|
||||
]}
|
||||
</td>
|
||||
{/if}
|
||||
{#if settings.statusFilter === STATUS_READ}
|
||||
<td>
|
||||
{book.frontmatter[
|
||||
settingsStore.settings.endDateProperty
|
||||
]}
|
||||
</td>
|
||||
<td>
|
||||
<Rating
|
||||
rating={book.frontmatter[
|
||||
settingsStore.settings.ratingProperty
|
||||
]}
|
||||
/>
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.shelf-code-block {
|
||||
.controls {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&.bookshelf-view {
|
||||
.controls {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
ALL_TIME,
|
||||
type DateFilterStore,
|
||||
} from "@ui/stores/date-filter.svelte";
|
||||
|
||||
interface Props {
|
||||
store: Pick<
|
||||
DateFilterStore,
|
||||
"filterYear" | "filterYears" | "filterMonth" | "filterMonths"
|
||||
>;
|
||||
showAllMonths?: boolean;
|
||||
}
|
||||
|
||||
const { store, showAllMonths }: Props = $props();
|
||||
</script>
|
||||
|
||||
<select class="year-filter" bind:value={store.filterYear}>
|
||||
{#each store.filterYears as year}
|
||||
<option value={year}>{year}</option>
|
||||
{/each}
|
||||
<option value={ALL_TIME}>All Time</option>
|
||||
</select>
|
||||
{#if store.filterYear !== ALL_TIME}
|
||||
<select class="month-filter" bind:value={store.filterMonth}>
|
||||
<option value={ALL_TIME}>Select Month</option>
|
||||
{#if showAllMonths}
|
||||
<option value={1}>January</option>
|
||||
<option value={2}>February</option>
|
||||
<option value={3}>March</option>
|
||||
<option value={4}>April</option>
|
||||
<option value={5}>May</option>
|
||||
<option value={6}>June</option>
|
||||
<option value={7}>July</option>
|
||||
<option value={8}>August</option>
|
||||
<option value={9}>September</option>
|
||||
<option value={10}>October</option>
|
||||
<option value={11}>November</option>
|
||||
<option value={12}>December</option>
|
||||
{:else}
|
||||
{#each store.filterMonths as month}
|
||||
<option value={month.value}>{month.label}</option>
|
||||
{/each}
|
||||
{/if}
|
||||
</select>
|
||||
{/if}
|
|
@ -0,0 +1,186 @@
|
|||
<script lang="ts">
|
||||
interface Props {
|
||||
rating: number;
|
||||
}
|
||||
|
||||
let { rating }: Props = $props();
|
||||
</script>
|
||||
|
||||
<span data-star={rating}>{rating}</span>
|
||||
|
||||
<style>
|
||||
[data-star] {
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
[data-star]::before {
|
||||
display: block;
|
||||
content: "★★★★★";
|
||||
color: #eee;
|
||||
}
|
||||
[data-star]::after {
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: "★★★★★";
|
||||
width: 0;
|
||||
color: #ff8c00;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[data-star^="0.1"]::after {
|
||||
width: 2%;
|
||||
}
|
||||
[data-star^="0.2"]::after {
|
||||
width: 4%;
|
||||
}
|
||||
[data-star^="0.3"]::after {
|
||||
width: 6%;
|
||||
}
|
||||
[data-star^="0.4"]::after {
|
||||
width: 8%;
|
||||
}
|
||||
[data-star^="0.5"]::after {
|
||||
width: 10%;
|
||||
}
|
||||
[data-star^="0.6"]::after {
|
||||
width: 12%;
|
||||
}
|
||||
[data-star^="0.7"]::after {
|
||||
width: 14%;
|
||||
}
|
||||
[data-star^="0.8"]::after {
|
||||
width: 16%;
|
||||
}
|
||||
[data-star^="0.9"]::after {
|
||||
width: 18%;
|
||||
}
|
||||
[data-star^="1"]::after {
|
||||
width: 20%;
|
||||
}
|
||||
[data-star^="1.1"]::after {
|
||||
width: 22%;
|
||||
}
|
||||
[data-star^="1.2"]::after {
|
||||
width: 24%;
|
||||
}
|
||||
[data-star^="1.3"]::after {
|
||||
width: 26%;
|
||||
}
|
||||
[data-star^="1.4"]::after {
|
||||
width: 28%;
|
||||
}
|
||||
[data-star^="1.5"]::after {
|
||||
width: 30%;
|
||||
}
|
||||
[data-star^="1.6"]::after {
|
||||
width: 32%;
|
||||
}
|
||||
[data-star^="1.7"]::after {
|
||||
width: 34%;
|
||||
}
|
||||
[data-star^="1.8"]::after {
|
||||
width: 36%;
|
||||
}
|
||||
[data-star^="1.9"]::after {
|
||||
width: 38%;
|
||||
}
|
||||
[data-star^="2"]::after {
|
||||
width: 40%;
|
||||
}
|
||||
[data-star^="2.1"]::after {
|
||||
width: 42%;
|
||||
}
|
||||
[data-star^="2.2"]::after {
|
||||
width: 44%;
|
||||
}
|
||||
[data-star^="2.3"]::after {
|
||||
width: 46%;
|
||||
}
|
||||
[data-star^="2.4"]::after {
|
||||
width: 48%;
|
||||
}
|
||||
[data-star^="2.5"]::after {
|
||||
width: 50%;
|
||||
}
|
||||
[data-star^="2.6"]::after {
|
||||
width: 52%;
|
||||
}
|
||||
[data-star^="2.7"]::after {
|
||||
width: 54%;
|
||||
}
|
||||
[data-star^="2.8"]::after {
|
||||
width: 56%;
|
||||
}
|
||||
[data-star^="2.9"]::after {
|
||||
width: 58%;
|
||||
}
|
||||
[data-star^="3"]::after {
|
||||
width: 60%;
|
||||
}
|
||||
[data-star^="3.1"]::after {
|
||||
width: 62%;
|
||||
}
|
||||
[data-star^="3.2"]::after {
|
||||
width: 64%;
|
||||
}
|
||||
[data-star^="3.3"]::after {
|
||||
width: 66%;
|
||||
}
|
||||
[data-star^="3.4"]::after {
|
||||
width: 68%;
|
||||
}
|
||||
[data-star^="3.5"]::after {
|
||||
width: 70%;
|
||||
}
|
||||
[data-star^="3.6"]::after {
|
||||
width: 72%;
|
||||
}
|
||||
[data-star^="3.7"]::after {
|
||||
width: 74%;
|
||||
}
|
||||
[data-star^="3.8"]::after {
|
||||
width: 76%;
|
||||
}
|
||||
[data-star^="3.9"]::after {
|
||||
width: 78%;
|
||||
}
|
||||
[data-star^="4"]::after {
|
||||
width: 80%;
|
||||
}
|
||||
[data-star^="4.1"]::after {
|
||||
width: 82%;
|
||||
}
|
||||
[data-star^="4.2"]::after {
|
||||
width: 84%;
|
||||
}
|
||||
[data-star^="4.3"]::after {
|
||||
width: 86%;
|
||||
}
|
||||
[data-star^="4.4"]::after {
|
||||
width: 88%;
|
||||
}
|
||||
[data-star^="4.5"]::after {
|
||||
width: 90%;
|
||||
}
|
||||
[data-star^="4.6"]::after {
|
||||
width: 92%;
|
||||
}
|
||||
[data-star^="4.7"]::after {
|
||||
width: 94%;
|
||||
}
|
||||
[data-star^="4.8"]::after {
|
||||
width: 96%;
|
||||
}
|
||||
[data-star^="4.9"]::after {
|
||||
width: 98%;
|
||||
}
|
||||
[data-star^="5"]::after {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -22,21 +22,23 @@ export function createDateFilter<T>(
|
|||
initialMonth ? today.getMonth() + 1 : ALL_TIME
|
||||
);
|
||||
const filteredData = $derived.by(() => {
|
||||
return data().filter((item) => {
|
||||
const date = selector(item);
|
||||
if (filterYear !== ALL_TIME) {
|
||||
if (date.year() !== filterYear) {
|
||||
return false;
|
||||
return data()
|
||||
.filter((item) => {
|
||||
const date = selector(item);
|
||||
if (filterYear !== ALL_TIME) {
|
||||
if (date.year() !== filterYear) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filterMonth !== ALL_TIME) {
|
||||
if (date.month() !== filterMonth - 1) {
|
||||
return false;
|
||||
if (filterMonth !== ALL_TIME) {
|
||||
if (date.month() !== filterMonth - 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => selector(a).diff(selector(b)));
|
||||
});
|
||||
|
||||
const filterYears = $derived.by(() => {
|
||||
|
|
Loading…
Reference in New Issue