obsidian-book-tracker/src/ui/code-blocks/ReadingLogCodeBlockView.svelte

234 lines
5.3 KiB
Svelte

<script lang="ts">
import type { ReadingLogEntry } from "@utils/ReadingLog";
import { Edit, Trash, Plus } from "lucide-svelte";
import { ReadingLogEntryEditModal } from "@ui/modals";
import type BookTrackerPlugin from "@src/main";
const ALL_TIME = "ALL_TIME";
interface Props {
plugin: BookTrackerPlugin;
}
let { 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}`;
}
let entries = $state(
plugin.readingLog.getEntries().map((entry, id) => ({
...entry,
id,
})),
);
function reload() {
entries = plugin.readingLog.getEntries().map((entry, id) => ({
...entry,
id,
}));
}
const years = $derived([
...new Set(entries.map((entry) => entry.createdAt.year())),
]);
// @ts-expect-error Moment is provided by Obsidian
let selectedYear = $state(moment().year().toString());
const filterYear = $derived(
selectedYear === ALL_TIME ? ALL_TIME : parseInt(selectedYear, 10),
);
// @ts-expect-error Moment is provided by Obsidian
let selectedMonth = $state(moment().format("MM"));
const filterMonth = $derived(
selectedMonth === "" ? undefined : parseInt(selectedMonth, 10),
);
const filteredEntries = $derived.by(() => {
if (filterYear === ALL_TIME) {
return entries;
}
// @ts-expect-error Moment is provided by Obsidian
let startDate = moment().year(filterYear).startOf("year");
// @ts-expect-error Moment is provided by Obsidian
let endDate = moment().year(filterYear).endOf("year");
if (filterMonth !== undefined) {
startDate = startDate.month(filterMonth - 1).startOf("month");
endDate = endDate.month(filterMonth - 1).endOf("month");
}
return entries.filter((entry) => {
return (
entry.createdAt.isSameOrAfter(startDate) &&
entry.createdAt.isSameOrBefore(endDate)
);
});
});
function createEntry() {
const modal = new ReadingLogEntryEditModal(plugin, async (entry) => {
modal.close();
await plugin.readingLog.addRawEntry(entry);
reload();
});
modal.open();
}
function editEntry(i: number, entry: ReadingLogEntry) {
const modal = new ReadingLogEntryEditModal(
plugin,
async (entry) => {
modal.close();
await plugin.readingLog.updateEntry(i, entry);
reload();
},
entry,
);
modal.open();
}
async function deleteEntry(i: number) {
await plugin.readingLog.spliceEntry(i);
reload();
}
</script>
<div class="obt-reading-log-viewer">
<div class="controls">
<div class="left">
<select class="year-filter" bind:value={selectedYear}>
{#each years as year}
<option value={year.toString()}>{year}</option>
{/each}
<option class="all-time" value={ALL_TIME}>All Time</option>
</select>
<select class="month-filter" bind:value={selectedMonth}>
<option value="">Select Month</option>
<option value="01">January</option>
<option value="02">February</option>
<option value="03">March</option>
<option value="04">April</option>
<option value="05">May</option>
<option value="06">June</option>
<option value="07">July</option>
<option value="08">August</option>
<option value="09">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
</div>
<div class="right">
<button onclick={createEntry} class="create-entry" type="button">
<Plus />
Create
</button>
</div>
</div>
<table>
<thead>
<tr>
<th class="date">Date</th>
<th class="book">Book</th>
<th class="pages-read">Pages Read</th>
<th class="percent-complete">Percent Complete</th>
<th class="actions">Actions</th>
</tr>
</thead>
<tbody>
{#each filteredEntries as entry}
<tr>
<td class="date">{entry.createdAt.format("YYYY-MM-DD")}</td>
<td class="book"
><a href={bookUri(entry.book)}>{entry.book}</a></td
>
<td class="pages-read">{entry.pagesRead}</td>
<td class="percent-complete">
{Math.round(
(entry.pagesReadTotal /
(entry.pagesReadTotal + entry.pagesRemaining)) *
100,
)}%
</td>
<td class="actions">
<button onclick={() => editEntry(entry.id, entry)}>
<Edit />
<span>Edit</span>
</button>
<button onclick={() => deleteEntry(entry.id)}>
<Trash />
<span>Delete</span>
</button>
</td>
</tr>
{:else}
<tr>
<td colspan="5">No entries found</td>
</tr>
{/each}
</tbody>
</table>
</div>
<!-- svelte-ignore css_unused_selector -->
<style lang="scss">
@use "../styles/utils";
.obt-reading-log-viewer {
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
.right {
text-align: right;
button.create-entry {
display: inline-flex;
gap: var(--size-2-2);
align-items: center;
:global(svg) {
width: var(--icon-s);
height: var(--icon-s);
}
}
}
}
.year-filter:has(> option.all-time:checked) + .month-filter {
display: none;
}
table {
width: 100%;
td.book {
width: 100%;
}
th,
td:not(.book) {
white-space: nowrap;
}
td.pages-read,
td.percent-complete {
text-align: center;
}
td.actions span {
@include utils.visually-hidden;
}
}
}
</style>