aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Tools
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-10-12 20:12:26 -0700
committerFuwn <[email protected]>2024-10-12 20:12:26 -0700
commitc7da36d89c1d3a81aaa7e1095ee9a8a064a570b5 (patch)
tree56c839a144b3d24418447fb350e356686761627f /src/lib/Tools
parentfix(DueAnimeList): filter out dropped media (diff)
downloaddue.moe-c7da36d89c1d3a81aaa7e1095ee9a8a064a570b5.tar.xz
due.moe-c7da36d89c1d3a81aaa7e1095ee9a8a064a570b5.zip
feat(tools): add simple tracker
Diffstat (limited to 'src/lib/Tools')
-rw-r--r--src/lib/Tools/Picker.svelte2
-rw-r--r--src/lib/Tools/Tracker/Tool.svelte142
-rw-r--r--src/lib/Tools/tools.ts7
3 files changed, 150 insertions, 1 deletions
diff --git a/src/lib/Tools/Picker.svelte b/src/lib/Tools/Picker.svelte
index 583a7a0d..3f20300f 100644
--- a/src/lib/Tools/Picker.svelte
+++ b/src/lib/Tools/Picker.svelte
@@ -16,7 +16,7 @@
>
<option value="default" selected disabled hidden>Select a tool to continue</option>
- {#each Object.keys(tools).filter((t) => t !== 'default') as t}
+ {#each Object.keys(tools).filter((t) => t !== 'default' && !tools[t].hidden) as t}
<option value={t}>{tools[t].short || tools[t].name()}</option>
{/each}
</select>
diff --git a/src/lib/Tools/Tracker/Tool.svelte b/src/lib/Tools/Tracker/Tool.svelte
new file mode 100644
index 00000000..8906e72d
--- /dev/null
+++ b/src/lib/Tools/Tracker/Tool.svelte
@@ -0,0 +1,142 @@
+<script lang="ts">
+ import { v6 as uuidv6 } from 'uuid';
+ import { database, type TrackerEntry } from '$lib/Database/IDB/tracker';
+ import { onMount } from 'svelte';
+ import Message from '$lib/Loading/Message.svelte';
+
+ let url = '';
+ let title = '';
+ let progress = 0;
+ let error = '';
+ let masterList: TrackerEntry[] | null = null;
+ let confirmDelete = 0;
+
+ $: listAccess = masterList || [];
+
+ onMount(async () => {
+ masterList = await database.entries.toArray();
+ });
+
+ const adjustEntry = (id: string, to: number) => {
+ const entry = listAccess.find((entry) => entry.id === id);
+
+ if (!entry) return;
+
+ entry.progress = Math.max(0, to);
+
+ database.entries.update(id, { progress: entry.progress });
+
+ masterList = listAccess.map((entry) =>
+ entry.id === id ? { ...entry, progress: entry.progress } : entry
+ );
+ };
+
+ const addEntry = async (url: string, title: string, progress: number) => {
+ if (!url || !title) {
+ error = 'URL and title are required fields';
+
+ return;
+ }
+
+ if (listAccess.some((entry) => entry.url === url)) {
+ error =
+ 'Entry with URL already exists: ' + listAccess.find((entry) => entry.url === url)?.title;
+
+ return;
+ }
+
+ await database.entries.add({ url, title, progress, id: uuidv6() });
+
+ masterList = await database.entries.toArray();
+ };
+
+ const deleteEntry = async (id: string) => {
+ if (confirmDelete !== 1) {
+ confirmDelete = 1;
+ error = 'Click again to confirm deletion';
+
+ return;
+ }
+
+ await database.entries.delete(id);
+
+ masterList = await database.entries.toArray();
+ confirmDelete = 0;
+ error = '';
+ };
+</script>
+
+<div class="card">
+ {#if error}
+ <p><b>Error</b>: {error}</p>
+ {/if}
+
+ <input type="url" placeholder="URL" bind:value={url} />
+ <input type="text" placeholder="Title" bind:value={title} />
+ <input type="number" placeholder="Progress (defaults to 0)" bind:value={progress} />
+ <button class="button-lined" on:click={() => addEntry(url, title, progress)}> Add </button>
+
+ <p />
+
+ {#if masterList === null}
+ <Message message="Loading entries ..." />
+ {:else}
+ <ul>
+ {#each listAccess.sort((a, b) => a.title.localeCompare(b.title)) as entry}
+ <li>
+ <div class="entry" id={entry.id}>
+ <a href={entry.url} title={entry.id}>
+ {entry.title}
+ </a>
+
+ <span class="opaque">|</span>
+
+ <input
+ type="number"
+ value={entry.progress}
+ size={3}
+ on:change={(e) =>
+ adjustEntry(entry.id, e.target ? e.target.value || entry.progress : entry.progress)}
+ />
+
+ <span class="entry-adjust">
+ <span class="opaque">|</span>
+ <button
+ class="button-square button-action"
+ on:click={() => adjustEntry(entry.id, entry.progress - 1)}
+ >-
+ </button>
+ <button
+ class="button-square button-action"
+ on:click={() => adjustEntry(entry.id, entry.progress + 1)}
+ >
+ +
+ </button>
+ <span class="opaque">|</span>
+ <button on:click={() => deleteEntry(entry.id)}>Remove</button>
+ </span>
+ </div>
+ </li>
+ {/each}
+ </ul>
+ {/if}
+</div>
+
+<style>
+ .entry {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ width: 100%;
+ }
+
+ .entry-adjust {
+ opacity: 0;
+ transition: opacity 0.2s;
+ }
+
+ .entry:hover .entry-adjust {
+ opacity: 1;
+ transition: opacity 0.2s;
+ }
+</style>
diff --git a/src/lib/Tools/tools.ts b/src/lib/Tools/tools.ts
index b85ff7f6..06249ab3 100644
--- a/src/lib/Tools/tools.ts
+++ b/src/lib/Tools/tools.ts
@@ -7,6 +7,7 @@ export const tools: {
short?: string;
description?: () => string;
id: string;
+ hidden?: boolean;
};
} = {
default: {
@@ -35,6 +36,12 @@ export const tools: {
"Find media with prequels you haven't seen yet for any given simulcast season",
id: 'sequel_spy'
},
+ tracker: {
+ name: () => 'Tracker',
+ description: () => 'Track your anime and manga progress with ease, intended for media that doesn\'t qualify for an AniList entry',
+ id: 'tracker',
+ hidden: true
+ },
uma_musume_birthdays: {
name: () => {
return 'Uma Musume: Pretty Derby Character Birthdays';