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:
@@ -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();
|
||||||
|
|||||||
@@ -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}>
|
<th
|
||||||
{#each headerRow.cells as cell (cell.id)}
|
onclick={() => toggleSort(column.key)}
|
||||||
<Subscribe
|
class:sorted={sortKey === column.key &&
|
||||||
attrs={cell.attrs()}
|
sortOrder !== undefined}
|
||||||
let:attrs
|
class="px-6 py-3 cursor-pointer"
|
||||||
props={cell.props()}
|
>
|
||||||
let:props
|
<div>
|
||||||
>
|
{column.header}
|
||||||
<th
|
{#if sortKey === column.key && sortOrder === "asc"}
|
||||||
{...attrs}
|
⬇️
|
||||||
on:click={props.sort.toggle}
|
{:else if sortKey === column.key && sortOrder === "desc"}
|
||||||
class:sorted={props.sort.order !==
|
⬆️
|
||||||
undefined}
|
{/if}
|
||||||
class="px-6 py-3"
|
</div>
|
||||||
>
|
</th>
|
||||||
<div>
|
{/each}
|
||||||
<Render of={cell.render()} />
|
</tr>
|
||||||
{#if props.sort.order === "asc"}
|
|
||||||
⬇️
|
|
||||||
{:else if props.sort.order === "desc"}
|
|
||||||
⬆️
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</Subscribe>
|
|
||||||
{/each}
|
|
||||||
</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}
|
||||||
</td>
|
{row[column.key]}
|
||||||
</Subscribe>
|
{/if}
|
||||||
{/each}
|
</td>
|
||||||
</tr>
|
{/each}
|
||||||
</Subscribe>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user