refactor: migrate to svelte 5

This commit is contained in:
Tomáš Mládek 2025-09-26 00:01:59 +02:00
parent f37019c3ef
commit 2d5c5f4130
23 changed files with 3529 additions and 126 deletions

View file

@ -31,7 +31,7 @@
"prettier": "^3.5.0", "prettier": "^3.5.0",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"puppeteer": "^22.15.0", "puppeteer": "^22.15.0",
"svelte-check": "^3.8.6", "svelte-check": "^4.0.0",
"wait-on": "^7.2.0" "wait-on": "^7.2.0"
}, },
"type": "module", "type": "module",
@ -41,13 +41,13 @@
"@sveltejs/adapter-auto": "^3.3.1", "@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.17.1", "@sveltejs/kit": "^2.17.1",
"@sveltejs/vite-plugin-svelte": "^3.1.2", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tabler/icons-webfont": "^2.47.0", "@tabler/icons-webfont": "^2.47.0",
"debug": "^4.4.0", "debug": "^4.4.0",
"i18next": "^23.16.8", "i18next": "^23.16.8",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"svelte": "^4.2.19", "svelte": "^5.0.0",
"svelte-i18next": "^2.2.2", "svelte-i18next": "^2.2.2",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.7.3", "typescript": "^5.7.3",

3333
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let heightOdd = false; let heightOdd = $state(false);
let widthOdd = false; let widthOdd = $state(false);
function updateOdd() { function updateOdd() {
heightOdd = window.innerHeight % 2 === 1; heightOdd = window.innerHeight % 2 === 1;

View file

@ -7,17 +7,21 @@
const MAX_COUNT = 33; const MAX_COUNT = 33;
const MARGIN_SIZE = 16; const MARGIN_SIZE = 16;
let horizontalCount = START_COUNT; let horizontalCount = $state(START_COUNT);
let verticalCount = START_COUNT; let verticalCount = $state(START_COUNT);
let blockSize = 64; let blockSize = $state(64);
let cornerBlocks = 2; let cornerBlocks = $state(2);
let horizontalMargin = MARGIN_SIZE; let horizontalMargin = $state(MARGIN_SIZE);
let verticalMargin = MARGIN_SIZE; let verticalMargin = $state(MARGIN_SIZE);
let unloaded = true; let unloaded = $state(true);
export let transparent = false; interface Props {
export let subdued = false; transparent?: boolean;
subdued?: boolean;
}
let { transparent = false, subdued = false }: Props = $props();
function updateCounts() { function updateCounts() {
const gridWidth = window.innerWidth - MARGIN_SIZE; const gridWidth = window.innerWidth - MARGIN_SIZE;

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let time = new Date(); let time = $state(new Date());
onMount(() => { onMount(() => {
setInterval(() => { setInterval(() => {

View file

@ -3,9 +3,9 @@
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
let screenResolution = '... x ...'; let screenResolution = $state('... x ...');
let windowResolution = ''; let windowResolution = $state('');
let dpr = "1"; let dpr = $state("1");
function updateResolution() { function updateResolution() {
const realWidth = Math.round(screen.width) * window.devicePixelRatio; const realWidth = Math.round(screen.width) * window.devicePixelRatio;

View file

@ -8,25 +8,29 @@
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{ focus: void }>(); const dispatch = createEventDispatcher<{ focus: void }>();
export let bg = false; interface Props {
bg?: boolean;
}
let sizes = { let { bg = false }: Props = $props();
let sizes = $state({
blockSize: 64, blockSize: 64,
horizontalCount: 16, horizontalCount: 16,
verticalCount: 16, verticalCount: 16,
horizontalMargin: 0, horizontalMargin: 0,
verticalMargin: 0 verticalMargin: 0
}; });
$: columnWidth = sizes.horizontalCount % 2 === 0 ? 3 : 4; let columnWidth = $derived(sizes.horizontalCount % 2 === 0 ? 3 : 4);
$: columnHeight = 2 * Math.floor((sizes.verticalCount * 0.75) / 2) + (sizes.verticalCount % 2); let columnHeight = $derived(2 * Math.floor((sizes.verticalCount * 0.75) / 2) + (sizes.verticalCount % 2));
$: leftColumn = sizes.horizontalCount / 4 - columnWidth / 2; let leftColumn = $derived(sizes.horizontalCount / 4 - columnWidth / 2);
$: circleBlocks = let circleBlocks =
2 * Math.floor((Math.min(sizes.horizontalCount, sizes.verticalCount) * 0.66) / 2) + $derived(2 * Math.floor((Math.min(sizes.horizontalCount, sizes.verticalCount) * 0.66) / 2) +
(sizes.horizontalCount % 2); (sizes.horizontalCount % 2));
</script> </script>
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div <div
class="test-card" class="test-card"
class:bg class:bg
@ -37,7 +41,7 @@
--column-width: {columnWidth}; --column-width: {columnWidth};
--column-height: {columnHeight}; --column-height: {columnHeight};
--left-column: {leftColumn};" --left-column: {leftColumn};"
on:dblclick={() => dispatch('focus') && document.body.requestFullscreen()} ondblclick={() => dispatch('focus') && document.body.requestFullscreen()}
> >
<BackgroundGrid on:change={(ev) => (sizes = ev.detail)} subdued={bg} /> <BackgroundGrid on:change={(ev) => (sizes = ev.detail)} subdued={bg} />

View file

@ -1,9 +1,14 @@
<script> <script lang="ts">
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
</script> </script>
<a href=".." class="hide-idle"><i class="ti ti-arrow-back"></i> {$i18n.t('Back')}</a> <a href=".." class="hide-idle"><i class="ti ti-arrow-back"></i> {$i18n.t('Back')}</a>
<slot /> {@render children?.()}
<style> <style>
a { a {

View file

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import TestCard from '$lib/TestCard.svelte'; import TestCard from '$lib/TestCard.svelte';
let x = -1; let x = $state(-1);
let y = -1; let y = $state(-1);
let leftButton = false; let leftButton = $state(false);
let rightButton = false; let rightButton = $state(false);
function onMouseMove(ev: MouseEvent) { function onMouseMove(ev: MouseEvent) {
x = ev.x; x = ev.x;
@ -31,10 +31,10 @@
</script> </script>
<svelte:body <svelte:body
on:mousemove={onMouseMove} onmousemove={onMouseMove}
on:mousedown={onMouseDown} onmousedown={onMouseDown}
on:mouseup={onMouseUp} onmouseup={onMouseUp}
on:contextmenu={(ev) => { oncontextmenu={(ev) => {
ev.preventDefault(); ev.preventDefault();
return false; return false;
}} }}

View file

@ -1,14 +1,19 @@
<script lang="ts"> <script lang="ts">
import TestCard from '$lib/TestCard.svelte'; import TestCard from '$lib/TestCard.svelte';
import { page } from '$app/stores'; import { page } from '$app/state';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
</script> </script>
<TestCard bg on:focus={() => goto('/card')} /> <TestCard bg on:focus={() => goto('/card')} />
<main class:sub={!$page.data.root}> <main class:sub={!page.data.root}>
<a href=".." class="button button-back"><i class="ti ti-arrow-back" />{$i18n.t('Back')}</a> <a href=".." class="button button-back"><i class="ti ti-arrow-back"></i>{$i18n.t('Back')}</a>
<slot /> {@render children?.()}
</main> </main>
<style> <style>

View file

@ -1,10 +1,12 @@
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { version } from '../../../package.json'; import { version } from '../../../package.json';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
import type { Snapshot } from '@sveltejs/kit'; import type { Snapshot } from '@sveltejs/kit';
const buildDate = import.meta.env.VITE_BUILD_DATE || "???"; const buildDate = import.meta.env.VITE_BUILD_DATE || "???";
let search = ''; let search = $state('');
type Entry = { type Entry = {
id: string; id: string;
@ -126,8 +128,8 @@
} }
]; ];
let filteredTests: Test[] = tests; let filteredTests: Test[] = $state(tests);
let filteredCategories: Category[] = []; let filteredCategories: Category[] = $state([]);
function doSearch(search: string) { function doSearch(search: string) {
filteredTests = tests.filter((test) => { filteredTests = tests.filter((test) => {
@ -141,7 +143,9 @@
); );
}); });
} }
$: doSearch(search); run(() => {
doSearch(search);
});
function setFilter(category: Category) { function setFilter(category: Category) {
if (filteredCategories.includes(category)) { if (filteredCategories.includes(category)) {
@ -151,13 +155,13 @@
} }
} }
$: nonEmptyCategories = categories.filter((category) => { let nonEmptyCategories = $derived(categories.filter((category) => {
const categoryTests = filteredTests.filter((test) => test.categories.includes(category.id)); const categoryTests = filteredTests.filter((test) => test.categories.includes(category.id));
return categoryTests.some( return categoryTests.some(
(test) => (test) =>
!filteredCategories.length || filteredCategories.every((f) => test.categories.includes(f)) !filteredCategories.length || filteredCategories.every((f) => test.categories.includes(f))
); );
}); }));
export const snapshot: Snapshot<string> = { export const snapshot: Snapshot<string> = {
capture: () => JSON.stringify({ filtered: filteredCategories, search }), capture: () => JSON.stringify({ filtered: filteredCategories, search }),
@ -172,13 +176,13 @@
<h1>Total Tech Test</h1> <h1>Total Tech Test</h1>
<nav> <nav>
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y_autofocus -->
<input type="search" placeholder={$i18n.t('Search')} bind:value={search} autofocus /> <input type="search" placeholder={$i18n.t('Search')} bind:value={search} autofocus />
<div class="options"> <div class="options">
{#each superCategories as category} {#each superCategories as category}
<button <button
on:click={() => setFilter(category.id)} onclick={() => setFilter(category.id)}
class:active={!filteredCategories.length || filteredCategories.includes(category.id)} class:active={!filteredCategories.length || filteredCategories.includes(category.id)}
class="super" class="super"
> >
@ -189,7 +193,7 @@
<div class="separator"></div> <div class="separator"></div>
{#each categories as category} {#each categories as category}
<button <button
on:click={() => setFilter(category.id)} onclick={() => setFilter(category.id)}
class:active={!filteredCategories.length || filteredCategories.includes(category.id)} class:active={!filteredCategories.length || filteredCategories.includes(category.id)}
> >
<i class="ti {category.icon}"></i> <i class="ti {category.icon}"></i>

View file

@ -1,11 +1,16 @@
<script lang="ts"> <script lang="ts">
import CycleButton from './cycle-button.svelte'; import CycleButton from './cycle-button.svelte';
interface Props {
children?: import('svelte').Snippet;
}
let channelsEl: HTMLDivElement; let { children }: Props = $props();
let channelsEl: HTMLDivElement = $state();
</script> </script>
<div class="channels" bind:this={channelsEl}> <div class="channels" bind:this={channelsEl}>
<slot /> {@render children?.()}
</div> </div>
<div class="controls"> <div class="controls">
<CycleButton element={channelsEl} /> <CycleButton element={channelsEl} />

View file

@ -2,9 +2,13 @@
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
export let element: HTMLElement; interface Props {
element: HTMLElement;
}
let cycling = false; let { element }: Props = $props();
let cycling = $state(false);
let currentChannel: HTMLAudioElement | undefined; let currentChannel: HTMLAudioElement | undefined;
async function cycleChannels() { async function cycleChannels() {
cycling = true; cycling = true;
@ -44,7 +48,7 @@
}); });
</script> </script>
<button on:click={onClick}> <button onclick={onClick}>
<i class="ti ti-refresh"></i> <i class="ti ti-refresh"></i>
{#if cycling} {#if cycling}
{$i18n.t('Stop Cycling')} {$i18n.t('Stop Cycling')}

View file

@ -1,13 +1,26 @@
<script lang="ts"> <script lang="ts">
export let src: string; interface Props {
export let left = false; src: string;
export let center = false; left?: boolean;
export let right = false; center?: boolean;
export let lfe = false; right?: boolean;
export let inline = false; lfe?: boolean;
inline?: boolean;
children?: import('svelte').Snippet;
}
let currentTime = 0; let {
let paused = true; src,
left = false,
center = false,
right = false,
lfe = false,
inline = false,
children
}: Props = $props();
let currentTime = $state(0);
let paused = $state(true);
function play() { function play() {
currentTime = 0; currentTime = 0;
paused = false; paused = false;
@ -22,14 +35,14 @@
class:lfe class:lfe
class:inline class:inline
class:playing={!paused} class:playing={!paused}
on:click={play} onclick={play}
> >
{#if !lfe} {#if !lfe}
<i class="ti ti-volume"></i> <i class="ti ti-volume"></i>
{:else} {:else}
<i class="ti ti-wave-sine"></i> <i class="ti ti-wave-sine"></i>
{/if} {/if}
<span><slot /></span> <span>{@render children?.()}</span>
<audio bind:currentTime bind:paused {src}></audio> <audio bind:currentTime bind:paused {src}></audio>
</button> </button>

View file

@ -6,7 +6,7 @@
import CycleButton from './cycle-button.svelte'; import CycleButton from './cycle-button.svelte';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
let speakersEl: HTMLElement; let speakersEl: HTMLElement = $state();
</script> </script>
<div class="test"> <div class="test">

View file

@ -1,6 +1,11 @@
<script lang="ts"> <script lang="ts">
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
</script> </script>
<h2><i class="ti ti-volume"></i> {$i18n.t('Audio test')}</h2> <h2><i class="ti ti-volume"></i> {$i18n.t('Audio test')}</h2>
<slot /> {@render children?.()}

View file

@ -2,8 +2,8 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
let frequency = 60; let frequency = $state(60);
let playing = false; let playing = $state(false);
let audioCtx: AudioContext | undefined; let audioCtx: AudioContext | undefined;
let oscillatorL: OscillatorNode | undefined; let oscillatorL: OscillatorNode | undefined;
@ -58,9 +58,9 @@
<input type="number" bind:value={frequency} min="20" max="20000" disabled={playing} />Hz <input type="number" bind:value={frequency} min="20" max="20000" disabled={playing} />Hz
</label> </label>
<div class="controls"> <div class="controls">
<button on:click={() => start('inPhase')}>{$i18n.t('In Phase')}</button> <button onclick={() => start('inPhase')}>{$i18n.t('In Phase')}</button>
<button on:click={() => start('outOfPhase')}>{$i18n.t('Out of Phase')}</button> <button onclick={() => start('outOfPhase')}>{$i18n.t('Out of Phase')}</button>
<button class="stop" on:click={stop} disabled={!playing}>{$i18n.t('Stop')}</button> <button class="stop" onclick={stop} disabled={!playing}>{$i18n.t('Stop')}</button>
</div> </div>
</div> </div>

View file

@ -1,18 +1,18 @@
<script lang="ts"> <script lang="ts">
import videoUrl from '@assets/avsync.webm'; import videoUrl from '@assets/avsync.webm';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
let paused = true; let paused = $state(true);
</script> </script>
<h2><i class="ti ti-time-duration-off"></i> {$i18n.t('Audio/Video Synchronization')}</h2> <h2><i class="ti ti-time-duration-off"></i> {$i18n.t('Audio/Video Synchronization')}</h2>
<!-- svelte-ignore a11y-media-has-caption --> <!-- svelte-ignore a11y_media_has_caption -->
<video <video
class:playing={!paused} class:playing={!paused}
autoplay autoplay
loop loop
bind:paused bind:paused
src={videoUrl} src={videoUrl}
on:click={() => (paused = false)} onclick={() => (paused = false)}
></video> ></video>
<style> <style>

View file

@ -1,25 +1,31 @@
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import debug from 'debug'; import debug from 'debug';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
const dbg = debug('app:camera'); const dbg = debug('app:camera');
let video: HTMLVideoElement; let video: HTMLVideoElement = $state();
let devices: MediaDeviceInfo[] = []; let devices: MediaDeviceInfo[] = $state([]);
let currentDevice: string | undefined; let currentDevice: string | undefined = $state();
let requestResolution: [number, number] | 'auto' = 'auto'; let requestResolution: [number, number] | 'auto' = $state('auto');
let requestFramerate: number | 'auto' = 'auto'; let requestFramerate: number | 'auto' = $state('auto');
let deviceInfo: { let deviceInfo: {
resolution?: string; resolution?: string;
frameRate?: number; frameRate?: number;
} = {}; } = $state({});
let snapshot: string | undefined; let snapshot: string | undefined = $state();
let flipped = false; let flipped = $state(false);
$: dbg('devices %O', devices); run(() => {
$: dbg('currentDevice %s', currentDevice); dbg('devices %O', devices);
});
run(() => {
dbg('currentDevice %s', currentDevice);
});
onMount(() => { onMount(() => {
refreshDevices(); refreshDevices();
@ -48,21 +54,23 @@
} }
} }
$: if (currentDevice) { run(() => {
navigator.mediaDevices if (currentDevice) {
.getUserMedia({ navigator.mediaDevices
video: { .getUserMedia({
deviceId: currentDevice, video: {
width: requestResolution === 'auto' ? undefined : requestResolution[0], deviceId: currentDevice,
height: requestResolution === 'auto' ? undefined : requestResolution[1], width: requestResolution === 'auto' ? undefined : requestResolution[0],
frameRate: requestFramerate === 'auto' ? undefined : requestFramerate height: requestResolution === 'auto' ? undefined : requestResolution[1],
} frameRate: requestFramerate === 'auto' ? undefined : requestFramerate
}) }
.then((stream) => { })
video.srcObject = stream; .then((stream) => {
refreshDevices(); video.srcObject = stream;
}); refreshDevices();
} });
}
});
async function takeSnapshot() { async function takeSnapshot() {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
@ -92,7 +100,7 @@
{/each} {/each}
</select> </select>
</label> </label>
<button on:click={refreshDevices}> <button onclick={refreshDevices}>
<i class="ti ti-refresh"></i> <i class="ti ti-refresh"></i>
{$i18n.t('Refresh')} {$i18n.t('Refresh')}
</button> </button>
@ -124,13 +132,13 @@
</div> </div>
<div class="display" class:snapshot={Boolean(snapshot)}> <div class="display" class:snapshot={Boolean(snapshot)}>
<!-- svelte-ignore a11y-media-has-caption --> <!-- svelte-ignore a11y_media_has_caption -->
<video class:flipped bind:this={video} autoplay class:unloaded={!currentDevice}></video> <video class:flipped bind:this={video} autoplay class:unloaded={!currentDevice}></video>
{#if snapshot} {#if snapshot}
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y_missing_attribute -->
<!--suppress HtmlRequiredAltAttribute --> <!--suppress HtmlRequiredAltAttribute -->
<img src={snapshot} /> <img src={snapshot} />
<button on:click={() => (snapshot = undefined)}><i class="ti ti-x"></i></button> <button onclick={() => (snapshot = undefined)}><i class="ti ti-x"></i></button>
{/if} {/if}
</div> </div>
@ -149,11 +157,11 @@
{/key} {/key}
</ul> </ul>
<div class="controls"> <div class="controls">
<button on:click={takeSnapshot}> <button onclick={takeSnapshot}>
<i class="ti ti-camera"></i> <i class="ti ti-camera"></i>
{$i18n.t('Take picture')} {$i18n.t('Take picture')}
</button> </button>
<button on:click={() => (flipped = !flipped)}> <button onclick={() => (flipped = !flipped)}>
<i class="ti ti-flip-vertical"></i> <i class="ti ti-flip-vertical"></i>
{#if flipped} {#if flipped}
{$i18n.t('Unflip image')} {$i18n.t('Unflip image')}

View file

@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import debug from 'debug'; import debug from 'debug';
@ -6,10 +8,10 @@
const dbg = debug('app:camera'); const dbg = debug('app:camera');
let gamepads: Gamepad[] = []; let gamepads: Gamepad[] = $state([]);
let currentGamepad: Gamepad | undefined; let currentGamepad: Gamepad | undefined = $state();
let buttons: GamepadButton[] = []; let buttons: GamepadButton[] = $state([]);
let axes: number[] = []; let axes: number[] = $state([]);
const axisHistory: number[][] = []; const axisHistory: number[][] = [];
const sizes: [number, number][] = []; const sizes: [number, number][] = [];
@ -58,17 +60,23 @@
requestAnimationFrame(update); requestAnimationFrame(update);
} }
$: { run(() => {
if (currentGamepad) { if (currentGamepad) {
update(); update();
} }
} });
$: dbg('Gamepads %O', gamepads); run(() => {
$: dbg('Current gamepad %s', currentGamepad); dbg('Gamepads %O', gamepads);
});
run(() => {
dbg('Current gamepad %s', currentGamepad);
});
$: currentGamepad?.vibrationActuator?.playEffect('dual-rumble', { run(() => {
duration: 1000 currentGamepad?.vibrationActuator?.playEffect('dual-rumble', {
duration: 1000
});
}); });
onMount(() => { onMount(() => {
@ -105,7 +113,7 @@
{/each} {/each}
</select> </select>
</label> </label>
<button on:click={refreshGamepads}> <button onclick={refreshGamepads}>
<i class="ti ti-refresh"></i> <i class="ti ti-refresh"></i>
{$i18n.t('Refresh')} {$i18n.t('Refresh')}
</button> </button>

View file

@ -2,9 +2,9 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
let key: string; let key: string = $state();
let code: string; let code: string = $state();
let pressedKeys: string[] = []; let pressedKeys: string[] = $state([]);
onMount(() => { onMount(() => {
document.addEventListener('keydown', (event) => { document.addEventListener('keydown', (event) => {
key = event.key; key = event.key;

View file

@ -2,10 +2,10 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { i18n } from '$lib/i18n'; import { i18n } from '$lib/i18n';
let time = 0; let time = $state(0);
let fps = 0; let fps = 0;
let start = 0; let start = 0;
let displayFps = '?'; let displayFps = $state('?');
let fpsInterval: NodeJS.Timeout | undefined; let fpsInterval: NodeJS.Timeout | undefined;
const times: number[] = []; const times: number[] = [];
@ -41,7 +41,7 @@
<div class="time">{time}</div> <div class="time">{time}</div>
<div class="fps">{displayFps} {$i18n.t('FPS')}</div> <div class="fps">{displayFps} {$i18n.t('FPS')}</div>
</div> </div>
<button on:click={restart}>{$i18n.t('Restart')}</button> <button onclick={restart}>{$i18n.t('Restart')}</button>
<style> <style>
div, div,

View file

@ -7,6 +7,11 @@
import '@tabler/icons-webfont/tabler-icons.css'; import '@tabler/icons-webfont/tabler-icons.css';
import '../index.css'; import '../index.css';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
let idleTimeout: NodeJS.Timeout | undefined; let idleTimeout: NodeJS.Timeout | undefined;
onMount(() => { onMount(() => {
@ -20,7 +25,7 @@
}); });
</script> </script>
<slot /> {@render children?.()}
<style> <style>
:global(.hide-idle) { :global(.hide-idle) {