diff options
| author | Fuwn <[email protected]> | 2024-10-12 20:12:26 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-10-12 20:12:26 -0700 |
| commit | c7da36d89c1d3a81aaa7e1095ee9a8a064a570b5 (patch) | |
| tree | 56c839a144b3d24418447fb350e356686761627f /src/lib/Tools | |
| parent | fix(DueAnimeList): filter out dropped media (diff) | |
| download | due.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.svelte | 2 | ||||
| -rw-r--r-- | src/lib/Tools/Tracker/Tool.svelte | 142 | ||||
| -rw-r--r-- | src/lib/Tools/tools.ts | 7 |
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'; |