refactor(frontend): Svelte 5 mount API, router v5, dependency-free table

main.ts uses mount() instead of the legacy constructor. App.svelte
follows svelte-spa-router v5: the removed loc store becomes
router.location, while wrap/push/link keep their v4 call shapes.

AnimeTable.svelte drops svelte-headless-table and reimplements the
same 11 sortable columns (WebsiteLink id cells, asc/desc
indicators, click toggles through asc/desc/off) with local
$state/$derived - about 120 lines, no dependency, fully covered by
svelte-check instead of the libs view model.
This commit is contained in:
John O'Keefe
2026-09-16 13:59:42 -04:00
parent 4087599629
commit 59b81396f9
3 changed files with 89 additions and 111 deletions
+5 -5
View File
@@ -21,13 +21,13 @@
import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte"; import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte"; import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
import {App} from "../bindings/AniTrack"; import {App} from "../bindings/AniTrack";
import { loc } from "svelte-spa-router"; import { router } from "svelte-spa-router";
import ErrorModal from "./helperComponents/ErrorModal.svelte"; import ErrorModal from "./helperComponents/ErrorModal.svelte";
onMount(async () => { onMount(async () => {
let isAniListLoggedIn: boolean; let isAniListLoggedIn!: boolean;
let isMALLoggedIn: boolean; let isMALLoggedIn!: boolean;
let isSimklLoggedIn: boolean; let isSimklLoggedIn!: boolean;
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value)); aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value)); malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value)); simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
@@ -39,7 +39,7 @@
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser()); !isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
}); });
$: if ($loc?.location === "/" && $watchlistNeedsRefresh) { $: if (router.location === "/" && $watchlistNeedsRefresh) {
(async () => { (async () => {
if ($aniListLoggedIn && $aniListPrimary) { if ($aniListLoggedIn && $aniListPrimary) {
await CheckIfAniListLoggedInAndLoadWatchList(); await CheckIfAniListLoggedInAndLoadWatchList();
+68 -91
View File
@@ -1,118 +1,95 @@
<script lang="ts"> <script lang="ts">
import {
createRender,
createTable,
Render,
Subscribe,
} from "svelte-headless-table";
// @ts-ignore
import { addSortBy } from "svelte-headless-table/plugins";
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"; import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import type { TableItem } from "../helperTypes/TableTypes";
import WebsiteLink from "./WebsiteLink.svelte"; import WebsiteLink from "./WebsiteLink.svelte";
//when adding sort here is code { sort: addSortBy() } type SortKey = keyof TableItem;
const table = createTable(tableItems, { sort: addSortBy() }); type SortOrder = "asc" | "desc" | undefined;
const columns = table.createColumns([ const columns: { key: SortKey; header: string }[] = [
table.column({ { key: "id", header: "Service Id" },
header: "Service Id", { key: "title", header: "Anime Title" },
cell: ({ value }) => createRender(WebsiteLink, { id: value }), { key: "service", header: "Service" },
accessor: "id", { key: "progress", header: "Episode Progress" },
}), { key: "status", header: "Status" },
table.column({ { key: "startedAt", header: "Started At" },
header: "Anime Title", { key: "completedAt", header: "Completed At" },
accessor: "title", { key: "score", header: "Rating" },
}), { key: "repeat", header: "Repeat" },
table.column({ { key: "notes", header: "Notes" },
header: "Service", ];
accessor: "service",
}),
table.column({
header: "Episode Progress",
accessor: "progress",
}),
table.column({
header: "Status",
accessor: "status",
}),
table.column({
header: "Started At",
accessor: "startedAt",
}),
table.column({
header: "Completed At",
accessor: "completedAt",
}),
table.column({
header: "Rating",
accessor: "score",
}),
table.column({
header: "Repeat",
accessor: "repeat",
}),
table.column({
header: "Notes",
accessor: "notes",
}),
]);
//add pluginStates when add sort back let sortKey = $state<SortKey | undefined>(undefined);
const { headerRows, rows, tableAttrs, tableBodyAttrs } = let sortOrder = $state<SortOrder>(undefined);
table.createViewModel(columns);
function toggleSort(key: SortKey) {
if (sortKey !== key) {
sortKey = key;
sortOrder = "asc";
} else if (sortOrder === "asc") {
sortOrder = "desc";
} else if (sortOrder === "desc") {
sortKey = undefined;
sortOrder = undefined;
} else {
sortOrder = "asc";
}
}
function compareItems(a: TableItem, b: TableItem): number {
if (sortKey === undefined || sortOrder === undefined) return 0;
const av = a[sortKey];
const bv = b[sortKey];
let result: number;
if (typeof av === "number" && typeof bv === "number") {
result = av - bv;
} else {
result = String(av ?? "").localeCompare(String(bv ?? ""));
}
return sortOrder === "asc" ? result : -result;
}
let sortedRows = $derived(
sortKey === undefined ? $tableItems : [...$tableItems].sort(compareItems),
);
</script> </script>
<div class="relative overflow-x-auto rounded-lg mb-5"> <div class="relative overflow-x-auto rounded-lg mb-5">
<table <table class="w-full text-sm text-left rtl:text-right text-gray-400">
class="w-full text-sm text-left rtl:text-right text-gray-400"
{...$tableAttrs}
>
<thead class="text-xs uppercase bg-gray-700 text-gray-400"> <thead class="text-xs uppercase bg-gray-700 text-gray-400">
{#each $headerRows as headerRow (headerRow.id)} <tr>
<Subscribe attrs={headerRow.attrs()} let:attrs> {#each columns as column (column.key)}
<tr {...attrs}>
{#each headerRow.cells as cell (cell.id)}
<Subscribe
attrs={cell.attrs()}
let:attrs
props={cell.props()}
let:props
>
<th <th
{...attrs} onclick={() => toggleSort(column.key)}
on:click={props.sort.toggle} class:sorted={sortKey === column.key &&
class:sorted={props.sort.order !== sortOrder !== undefined}
undefined} class="px-6 py-3 cursor-pointer"
class="px-6 py-3"
> >
<div> <div>
<Render of={cell.render()} /> {column.header}
{#if props.sort.order === "asc"} {#if sortKey === column.key && sortOrder === "asc"}
⬇️ ⬇️
{:else if props.sort.order === "desc"} {:else if sortKey === column.key && sortOrder === "desc"}
⬆️ ⬆️
{/if} {/if}
</div> </div>
</th> </th>
</Subscribe>
{/each} {/each}
</tr> </tr>
</Subscribe>
{/each}
</thead> </thead>
<tbody {...$tableBodyAttrs}> <tbody>
{#each $rows as row (row.id)} {#each sortedRows as row (row.id + row.service)}
<Subscribe attrs={row.attrs()} let:attrs> <tr class="bg-gray-800 border-gray-700">
<tr {...attrs} class="bg-gray-800 border-gray-700"> {#each columns as column (column.key)}
{#each row.cells as cell (cell.id)} <td class="px-6 py-4">
<Subscribe attrs={cell.attrs()} let:attrs> {#if column.key === "id"}
<td {...attrs} class="px-6 py-4"> <WebsiteLink id={row.id} />
<Render of={cell.render()} /> {:else}
{row[column.key]}
{/if}
</td> </td>
</Subscribe>
{/each} {/each}
</tr> </tr>
</Subscribe>
{/each} {/each}
</tbody> </tbody>
</table> </table>
+3 -2
View File
@@ -1,8 +1,9 @@
import './style.css' import './style.css'
import { mount } from 'svelte'
import App from './App.svelte' import App from './App.svelte'
const app = new App({ const app = mount(App, {
target: document.getElementById('app') target: document.getElementById('app')!
}) })
export default app export default app