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 { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
import {App} from "../bindings/AniTrack";
import { loc } from "svelte-spa-router";
import { router } from "svelte-spa-router";
import ErrorModal from "./helperComponents/ErrorModal.svelte";
onMount(async () => {
let isAniListLoggedIn: boolean;
let isMALLoggedIn: boolean;
let isSimklLoggedIn: boolean;
let isAniListLoggedIn!: boolean;
let isMALLoggedIn!: boolean;
let isSimklLoggedIn!: boolean;
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
@@ -39,7 +39,7 @@
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
});
$: if ($loc?.location === "/" && $watchlistNeedsRefresh) {
$: if (router.location === "/" && $watchlistNeedsRefresh) {
(async () => {
if ($aniListLoggedIn && $aniListPrimary) {
await CheckIfAniListLoggedInAndLoadWatchList();
+81 -104
View File
@@ -1,118 +1,95 @@
<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 type { TableItem } from "../helperTypes/TableTypes";
import WebsiteLink from "./WebsiteLink.svelte";
//when adding sort here is code { sort: addSortBy() }
const table = createTable(tableItems, { sort: addSortBy() });
type SortKey = keyof TableItem;
type SortOrder = "asc" | "desc" | undefined;
const columns = table.createColumns([
table.column({
header: "Service Id",
cell: ({ value }) => createRender(WebsiteLink, { id: value }),
accessor: "id",
}),
table.column({
header: "Anime Title",
accessor: "title",
}),
table.column({
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",
}),
]);
const columns: { key: SortKey; header: string }[] = [
{ key: "id", header: "Service Id" },
{ key: "title", header: "Anime Title" },
{ key: "service", header: "Service" },
{ key: "progress", header: "Episode Progress" },
{ key: "status", header: "Status" },
{ key: "startedAt", header: "Started At" },
{ key: "completedAt", header: "Completed At" },
{ key: "score", header: "Rating" },
{ key: "repeat", header: "Repeat" },
{ key: "notes", header: "Notes" },
];
//add pluginStates when add sort back
const { headerRows, rows, tableAttrs, tableBodyAttrs } =
table.createViewModel(columns);
let sortKey = $state<SortKey | undefined>(undefined);
let sortOrder = $state<SortOrder>(undefined);
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>
<div class="relative overflow-x-auto rounded-lg mb-5">
<table
class="w-full text-sm text-left rtl:text-right text-gray-400"
{...$tableAttrs}
>
<table class="w-full text-sm text-left rtl:text-right text-gray-400">
<thead class="text-xs uppercase bg-gray-700 text-gray-400">
{#each $headerRows as headerRow (headerRow.id)}
<Subscribe attrs={headerRow.attrs()} let:attrs>
<tr {...attrs}>
{#each headerRow.cells as cell (cell.id)}
<Subscribe
attrs={cell.attrs()}
let:attrs
props={cell.props()}
let:props
>
<th
{...attrs}
on:click={props.sort.toggle}
class:sorted={props.sort.order !==
undefined}
class="px-6 py-3"
>
<div>
<Render of={cell.render()} />
{#if props.sort.order === "asc"}
⬇️
{:else if props.sort.order === "desc"}
⬆️
{/if}
</div>
</th>
</Subscribe>
{/each}
</tr>
</Subscribe>
{/each}
<tr>
{#each columns as column (column.key)}
<th
onclick={() => toggleSort(column.key)}
class:sorted={sortKey === column.key &&
sortOrder !== undefined}
class="px-6 py-3 cursor-pointer"
>
<div>
{column.header}
{#if sortKey === column.key && sortOrder === "asc"}
⬇️
{:else if sortKey === column.key && sortOrder === "desc"}
⬆️
{/if}
</div>
</th>
{/each}
</tr>
</thead>
<tbody {...$tableBodyAttrs}>
{#each $rows as row (row.id)}
<Subscribe attrs={row.attrs()} let:attrs>
<tr {...attrs} class="bg-gray-800 border-gray-700">
{#each row.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs>
<td {...attrs} class="px-6 py-4">
<Render of={cell.render()} />
</td>
</Subscribe>
{/each}
</tr>
</Subscribe>
<tbody>
{#each sortedRows as row (row.id + row.service)}
<tr class="bg-gray-800 border-gray-700">
{#each columns as column (column.key)}
<td class="px-6 py-4">
{#if column.key === "id"}
<WebsiteLink id={row.id} />
{:else}
{row[column.key]}
{/if}
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
+3 -2
View File
@@ -1,8 +1,9 @@
import './style.css'
import { mount } from 'svelte'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')
const app = mount(App, {
target: document.getElementById('app')!
})
export default app