Fix desync in dragged elements with client side state object

This commit is contained in:
Mihkel Martin Kasterpalu 2025-01-22 17:49:25 +02:00
parent a4e4f1157b
commit 4d24b90afa
4 changed files with 66 additions and 12 deletions
src

View file

@ -0,0 +1,44 @@
import { getContext, onDestroy, setContext } from 'svelte';
import type { AlbumData, AlbumDataField } from '$lib/types';
export class AlbumClientState {
albums = $state<AlbumData>({
names: [],
artists: [],
images: []
});
constructor() {
onDestroy(() => {
this.albums = {
names: [],
artists: [],
images: []
};
});
}
update(items: AlbumDataField[], type: string) {
if (type === 'names') {
this.albums.names = items;
}
if (type === 'artists') {
this.albums.artists = items;
}
if (type === 'images') {
this.albums.images = items;
}
}
}
const ALBUM_KEY = Symbol('ALBUM');
export function setAlbumClientState() {
return setContext(ALBUM_KEY, new AlbumClientState());
}
export function getAlbumClientState() {
return getContext<ReturnType<typeof setAlbumClientState>>(ALBUM_KEY);
}

View file

@ -11,16 +11,20 @@
type Item
} from 'svelte-dnd-action';
import * as Card from '$lib/components/ui/card/index.js';
import { getAlbumClientState } from '$lib/client/AlbumClientState.svelte';
import { onMount } from 'svelte';
let { items = $bindable(), image = false, type = 'default' } = $props();
let { items, image = false, type = 'default' } = $props();
const flipDurationMs = 300;
const clientState = getAlbumClientState();
function handleDndConsider(e: CustomEvent<DndEvent<Item>>) {
items = e.detail.items;
}
function handleDndFinalize(e: CustomEvent<DndEvent<Item>>) {
items = e.detail.items;
clientState.update(e.detail.items as AlbumDataField[], type);
}
function transformDraggedElement(draggedEl: HTMLElement | undefined) {
if (!draggedEl) {
@ -44,6 +48,10 @@
: 'border-blue-400'
}`
);
onMount(() => {
clientState.update(items as AlbumDataField[], type);
});
</script>
{#snippet card(item: AlbumDataField, i: number)}

View file

@ -0,0 +1,9 @@
<script lang="ts">
import { setAlbumClientState } from '$lib/client/AlbumClientState.svelte';
let { children } = $props();
setAlbumClientState();
</script>
{@render children()}

View file

@ -10,20 +10,13 @@
import type { AlbumData } from '$lib/types';
import { fade } from 'svelte/transition';
import { expoIn, expoOut } from 'svelte/easing';
import { getAlbumClientState } from '$lib/client/AlbumClientState.svelte';
let { data }: { data: PageData; form: FormData } = $props();
let loading = $state(false);
let oldAlbums: AlbumData | undefined = $state();
// Used when user answers wrong and no new data comes in
$effect(() => {
if (data.streamed?.albums) {
data.streamed.albums.then((data) => {
oldAlbums = data as AlbumData;
});
}
});
const clientState = getAlbumClientState();
</script>
{#snippet footer(loading: boolean)}
@ -93,7 +86,7 @@
</AlertDialog.Content>
</AlertDialog.Root>
<header class="font-title mb-12 flex flex-col items-center">
<header class="mb-12 flex flex-col items-center font-title">
<h1 class="mb-1 scroll-m-20 text-5xl font-extrabold tracking-tight lg:text-6xl">Paku biiti</h1>
<p class="text-xl font-semibold text-muted-foreground">
Lohista kokku õiged albumi <span class="text-red-600 dark:text-red-400">nimed</span>,
@ -126,7 +119,7 @@
{/await}
{:else}
<div class="grid w-full gap-4" out:fade={{ duration: 150, easing: expoOut }}>
{@render playArea(oldAlbums)}
{@render playArea(clientState.albums)}
</div>
{@render footer(false)}
{/if}