finished switch from svelte table to tanstack table

This commit is contained in:
John O'Keefe 2025-02-07 22:57:05 -05:00
parent 3ec5eb1a03
commit 45845c2a69
3 changed files with 870 additions and 974 deletions

View File

@ -23,10 +23,7 @@
} from "../mal/types/MALTypes";
import type { SimklAnime } from "../simkl/types/simklTypes";
import { writable } from "svelte/store";
import type {
StatusOption,
StatusOptions,
} from "../helperTypes/StatusTypes";
import type { StatusOption, StatusOptions } from "../helperTypes/StatusTypes";
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
import {
convertDateStringToAniList,
@ -45,7 +42,6 @@
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte";
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import Datepicker from "./Datepicker.svelte";
import AnimeTableTanstack from "./AnimeTableTanstack.svelte";
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
let isAniListLoggedIn: boolean;
@ -84,8 +80,7 @@
{ id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching" },
];
let startingAnilistStatusOption: StatusOption = statusOptions.filter(
(option) =>
currentAniListAnime.data.MediaList.status === option.aniList,
(option) => currentAniListAnime.data.MediaList.status === option.aniList,
)[0];
let startedAtDate: Date | null = convertAniListDateToDate(
currentAniListAnime.data.MediaList.startedAt,
@ -116,15 +111,11 @@
let startDate = "";
let finishDate = "";
if (currentMalAnime.my_list_status.start_date !== "") {
const startArray = re.exec(
currentMalAnime.my_list_status.start_date,
);
const startArray = re.exec(currentMalAnime.my_list_status.start_date);
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
}
if (currentMalAnime.my_list_status.finish_date !== "") {
const finishArray = re.exec(
currentMalAnime.my_list_status.finish_date,
);
const finishArray = re.exec(currentMalAnime.my_list_status.finish_date);
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
}
AddAnimeServiceToTable({
@ -201,10 +192,7 @@
submitData[key] = value;
}
if (
isAniListLoggedIn &&
currentAniListAnime.data.MediaList.mediaId !== 0
) {
if (isAniListLoggedIn && currentAniListAnime.data.MediaList.mediaId !== 0) {
let body: AniListUpdateVariables = {
mediaId: currentAniListAnime.data.MediaList.mediaId,
progress: submitData.episodes,
@ -215,8 +203,7 @@
startedAt: convertDateToAniList(startedAtDate),
completedAt: convertDateToAniList(completedAtDate),
};
await AniListUpdateEntry(body).then(
(value: AniListGetSingleAnime) => {
await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => {
/* TODO in future when you inevitably add tags to typescript, until Anilist fixes the api bug
where tags break the SaveMediaListEntry return, you'll want to use this delete line
delete value.data.MediaList.media.tags */
@ -240,8 +227,7 @@
repeat: currentAniListAnime.data.MediaList.repeat,
notes: currentAniListAnime.data.MediaList.notes,
});
},
);
});
}
if (malLoggedIn && currentMalAnime.id !== 0) {
@ -258,8 +244,7 @@
(malAnimeReturn: MalListStatus) => {
malAnime.update((value) => {
value.my_list_status.status = malAnimeReturn.status;
value.my_list_status.is_rewatching =
malAnimeReturn.is_rewatching;
value.my_list_status.is_rewatching = malAnimeReturn.is_rewatching;
value.my_list_status.score = malAnimeReturn.score;
value.my_list_status.num_episodes_watched =
malAnimeReturn.num_episodes_watched;
@ -286,14 +271,12 @@
id: `m-${currentMalAnime.id}`,
title: currentMalAnime.title,
service: "MyAnimeList",
progress:
currentMalAnime.my_list_status.num_episodes_watched,
progress: currentMalAnime.my_list_status.num_episodes_watched,
status: currentMalAnime.my_list_status.status,
startedAt: startDate,
completedAt: finishDate,
score: currentMalAnime.my_list_status.score,
repeat: currentMalAnime.my_list_status
.num_times_rewatched,
repeat: currentMalAnime.my_list_status.num_times_rewatched,
notes: currentMalAnime.my_list_status.comments,
});
},
@ -301,13 +284,9 @@
}
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
if (
currentSimklAnime.watched_episodes_count !== submitData.episodes
) {
await SimklSyncEpisodes(
currentSimklAnime,
submitData.episodes,
).then((value: SimklAnime) => {
if (currentSimklAnime.watched_episodes_count !== submitData.episodes) {
await SimklSyncEpisodes(currentSimklAnime, submitData.episodes).then(
(value: SimklAnime) => {
AddAnimeServiceToTable({
id: `s-${value.show.ids.simkl}`,
title: value.show.title,
@ -324,14 +303,13 @@
newValue = value;
return newValue;
});
});
},
);
}
if (currentSimklAnime.user_rating !== submitData.rating) {
await SimklSyncRating(
currentSimklAnime,
submitData.rating,
).then((value) => {
await SimklSyncRating(currentSimklAnime, submitData.rating).then(
(value) => {
AddAnimeServiceToTable({
id: `s-${value.show.ids.simkl}`,
title: value.show.title,
@ -348,14 +326,13 @@
newValue = value;
return newValue;
});
});
},
);
}
if (currentSimklAnime.status !== submitData.status.simkl) {
await SimklSyncStatus(
currentSimklAnime,
submitData.status.simkl,
).then((value) => {
await SimklSyncStatus(currentSimklAnime, submitData.status.simkl).then(
(value) => {
AddAnimeServiceToTable({
id: `s-${value.show.ids.simkl}`,
title: value.show.title,
@ -372,7 +349,8 @@
newValue = value;
return newValue;
});
});
},
);
}
}
@ -383,10 +361,7 @@
const deleteEntries = async () => {
submitting.set(true);
if (
isAniListLoggedIn &&
currentAniListAnime.data.MediaList.mediaId !== 0
) {
if (isAniListLoggedIn && currentAniListAnime.data.MediaList.mediaId !== 0) {
await AniListDeleteEntry(currentAniListAnime.data.MediaList.id);
AddAnimeServiceToTable({
id: `a-${currentAniListAnime.data.MediaList.mediaId}`,
@ -446,8 +421,7 @@
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0
) {
max =
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode -
1;
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode - 1;
}
</script>
@ -508,22 +482,18 @@
id="episodes"
class="border border-x-0 p-2.5 h-11 text-center text-sm block w-full placeholder-gray-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
{currentAniListAnime.data.MediaList.progress < 0 ||
(currentAniListAnime.data.MediaList.media.episodes >
0 &&
(currentAniListAnime.data.MediaList.media.episodes > 0 &&
currentAniListAnime.data.MediaList.progress >
currentAniListAnime.data.MediaList.media
.episodes) ||
(currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode > 0 &&
currentAniListAnime.data.MediaList.media.episodes) ||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode > 0 &&
currentAniListAnime.data.MediaList.progress >
currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode -
currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode -
1)
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'} w-24"
bind:value={
currentAniListAnime.data.MediaList.progress
}
bind:value={currentAniListAnime.data.MediaList.progress}
required
/>
<button
@ -552,16 +522,15 @@
</button>
</div>
<div>
/ {currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode !== 0
? currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode - 1
/ {currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode !== 0
? currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode - 1
: currentAniListAnime.data.MediaList.media.episodes}
</div>
{#if currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0}
<div>
of {currentAniListAnime.data.MediaList.media
.episodes}
of {currentAniListAnime.data.MediaList.media.episodes}
</div>
{/if}
</div>
@ -634,8 +603,7 @@
name="repeat"
min="0"
id="repeat"
class="border {currentAniListAnime.data.MediaList
.repeat < 0
class="border {currentAniListAnime.data.MediaList.repeat < 0
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
: 'border-gray-500 text-white focus:ring-blue-500 focus:border-blue-500'} text-sm rounded-lg block w-24 p-2.5 bg-gray-600 placeholder-gray-400 text-white"
bind:value={currentAniListAnime.data.MediaList.repeat}
@ -711,7 +679,6 @@
</Button>
</div>
</div>
<AnimeTableTanstack />
<AnimeTable />
<div class="flex rounded-lg shadow max-w-4-4 bg-gray-800">

View File

@ -1,119 +1,166 @@
<script lang="ts">
import { writable } from "svelte/store";
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 WebsiteLink from "./WebsiteLink.svelte"
createSvelteTable,
flexRender,
getCoreRowModel,
getSortedRowModel,
type OnChangeFn,
renderComponent,
} from "@tanstack/svelte-table";
import type {
ColumnDef,
SortingState,
TableOptions,
Updater,
} from "@tanstack/svelte-table";
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import type { TableItem, TableItems } from "../helperTypes/TableTypes";
import WebsiteLink from "./WebsiteLink.svelte";
//when adding sort here is code { sort: addSortBy() }
const table = createTable(tableItems, { sort: addSortBy() })
let currentTableItems: TableItems;
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({
tableItems.subscribe((value: TableItems) => (currentTableItems = value));
const defaultColumns: ColumnDef<TableItem>[] = [
{
accessorKey: "id",
cell: (cell) => {
return renderComponent(WebsiteLink, { id: cell.renderValue() });
},
header: () => "Service ID",
},
{
accessorKey: "title",
header: () => "Anime Title",
},
{
accessorKey: "service",
header: () => "Service",
},
{
accessorKey: "progress",
header: () => "Episode Progress",
},
{
accessorKey: "status",
header: "Status",
accessor: "status",
}),
table.column({
},
{
accessorKey: "startedAt",
header: "Started At",
accessor: "startedAt",
}),
table.column({
},
{
accessorKey: "completedAt",
header: "Completed At",
accessor: "completedAt",
}),
table.column({
},
{
accessorKey: "score",
header: "Rating",
accessor: "score",
}),
table.column({
},
{
accessorKey: "repeat",
header: "Repeat",
accessor: "repeat",
}),
table.column({
},
{
accessorKey: "notes",
header: "Notes",
accessor: "notes",
}),
])
},
];
//add pluginStates when add sort back
const { headerRows, rows, tableAttrs, tableBodyAttrs } =
table.createViewModel(columns)
//sorting info
let sorting: SortingState[] = [];
const setSorting: OnChangeFn<SortingState> = (updater) => {
if (updater instanceof Function) {
sorting = updater(sorting);
} else {
sorting = updater;
}
options.update((old) => ({
...old,
state: {
...old.state,
sorting,
},
}));
};
const options = writable<TableOptions<TableItem>>({
data: currentTableItems,
columns: defaultColumns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const rerender = () => {
options.update((options) => ({
...options,
data: currentTableItems,
}));
};
const table = createSvelteTable(options);
</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}
>
<div>
<div class="relative overflow-x-auto rounded-lg mb-5">
<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
{#each $table.getHeaderGroups() as headerGroup}
<tr>
{#each headerGroup.headers as header}
<th colSpan={header.colSpan} class="px-6 py-3">
{#if !header.isPlaceholder}
<div
class:cursor-pointer={header.column.getCanSort()}
class:select-none={header.column.getCanSort()}
on:click={header.column.getToggleSortingHandler()}
>
<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"}
<svelte:component
this={flexRender(
header.column.columnDef.header,
header.getContext(),
)}
/>
{#if header.column.getIsSorted().toString() === "asc"}
⬆️
{:else if header.column.getIsSorted().toString() === "desc"}
⬇️
{/if}
</div>
{/if}
</th>
</Subscribe>
{/each}
</tr>
</Subscribe>
{/each}
</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()} />
<tbody class="bg-gray-800 border-gray-700">
{#each $table.getRowModel().rows as row}
<tr>
{#each row.getVisibleCells() as cell}
<td class="px-6 py-4">
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
/>
</td>
</Subscribe>
{/each}
</tr>
</Subscribe>
{/each}
</tbody>
</table>
</div>
<!-- <button -->
<!-- class="text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-800 dark:focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none" -->
<!-- on:click={() => rerender()} -->
<!-- > -->
<!-- Rerender -->
<!-- </button> -->
</div>

View File

@ -1,118 +0,0 @@
<script lang="ts">
import { writable } from "svelte/store";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import type { ColumnDef, TableOptions } from "@tanstack/svelte-table";
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import type { TableItem, TableItems } from "../helperTypes/TableTypes";
import WebsiteLink from "./WebsiteLink.svelte";
let currentTableItems: TableItems;
tableItems.subscribe((value: TableItems) => (currentTableItems = value));
const defaultColumns: ColumnDef<TableItem>[] = [
{
accessorKey: "id",
cell: (cell) => {
return renderComponent(WebsiteLink, { id: cell.renderValue() });
},
header: () => "Service ID",
},
{
accessorKey: "title",
header: () => "Anime Title",
},
{
accessorKey: "service",
header: () => "Service",
},
{
accessorKey: "progress",
header: () => "Episode Progress",
},
{
accessorKey: "status",
header: "Status",
},
{
accessorKey: "startedAt",
header: "Started At",
},
{
accessorKey: "completedAt",
header: "Completed At",
},
{
accessorKey: "score",
header: "Rating",
},
{
accessorKey: "repeat",
header: "Repeat",
},
{
accessorKey: "notes",
header: "Notes",
},
];
const options = writable<TableOptions<TableItem>>({
data: currentTableItems,
columns: defaultColumns,
getCoreRowModel: getCoreRowModel(),
});
const rerender = () => {
options.update((options) => ({
...options,
data: currentTableItems,
}));
};
const table = createSvelteTable(options);
</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">
<thead class="text-xs uppercase bg-gray-700 text-gray-400">
{#each $table.getHeaderGroups() as headerGroup}
<tr>
{#each headerGroup.headers as header}
<th class="px-6 py-3">
{#if !header.isPlaceholder}
<svelte:component
this={flexRender(
header.column.columnDef.header,
header.getContext(),
)}
/>
{/if}
</th>
{/each}
</tr>
{/each}
</thead>
<tbody class="bg-gray-800 border-gray-700">
{#each $table.getRowModel().rows as row}
<tr>
{#each row.getVisibleCells() as cell}
<td class="px-6 py-4">
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
<button on:click={() => rerender()} class="border p-2"> Rerender </button>
</div>