feat: update some more components to svelte 5
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m48s

This commit is contained in:
Max Richter
2025-11-24 21:11:16 +01:00
parent d64877666b
commit cfcb447784
17 changed files with 130 additions and 80 deletions

View File

@@ -4,22 +4,26 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import type { NodeType } from "@nodes/types"; import type { NodeType } from "@nodes/types";
export let position: [x: number, y: number] | null; type Props = {
position: [x: number, y: number] | null;
graph: GraphManager;
};
export let graph: GraphManager; let { position = $bindable(), graph }: Props = $props();
let input: HTMLInputElement; let input: HTMLInputElement;
let value: string = ""; let value = $state<string>();
let activeNodeId: NodeType | undefined = undefined; let activeNodeId = $state<NodeType>();
const allNodes = graph.getNodeDefinitions(); const allNodes = graph.getNodeDefinitions();
function filterNodes() { function filterNodes() {
return allNodes.filter((node) => node.id.includes(value)); return allNodes.filter((node) => node.id.includes(value ?? ""));
} }
$: nodes = value === "" ? allNodes : filterNodes(); const nodes = $derived(value === "" ? allNodes : filterNodes());
$: if (nodes) { $effect(() => {
if (nodes) {
if (activeNodeId === undefined) { if (activeNodeId === undefined) {
activeNodeId = nodes[0].id; activeNodeId = nodes[0].id;
} else if (nodes.length) { } else if (nodes.length) {
@@ -29,6 +33,7 @@
} }
} }
} }
});
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
@@ -75,7 +80,7 @@
role="searchbox" role="searchbox"
placeholder="Search..." placeholder="Search..."
disabled={false} disabled={false}
on:keydown={handleKeyDown} onkeydown={handleKeyDown}
bind:value bind:value
bind:this={input} bind:this={input}
/> />
@@ -88,7 +93,7 @@
role="treeitem" role="treeitem"
tabindex="0" tabindex="0"
aria-selected={node.id === activeNodeId} aria-selected={node.id === activeNodeId}
on:keydown={(event) => { onkeydown={(event) => {
if (event.key === "Enter") { if (event.key === "Enter") {
if (position) { if (position) {
graph.createNode({ type: node.id, position, props: {} }); graph.createNode({ type: node.id, position, props: {} });
@@ -96,17 +101,17 @@
} }
} }
}} }}
on:mousedown={() => { onmousedown={() => {
if (position) { if (position) {
graph.createNode({ type: node.id, position, props: {} }); graph.createNode({ type: node.id, position, props: {} });
position = null; position = null;
} }
}} }}
on:focus={() => { onfocus={() => {
activeNodeId = node.id; activeNodeId = node.id;
}} }}
class:selected={node.id === activeNodeId} class:selected={node.id === activeNodeId}
on:mouseover={() => { onmouseover={() => {
activeNodeId = node.id; activeNodeId = node.id;
}} }}
> >

View File

@@ -54,8 +54,9 @@
}, },
}} }}
uniforms.camPos.value={cameraPosition} uniforms.camPos.value={cameraPosition}
uniforms.backgroundColor.value={appSettings.theme && colors["layer-0"]} uniforms.backgroundColor.value={appSettings.value.theme &&
uniforms.lineColor.value={appSettings.theme && colors["outline"]} colors["layer-0"]}
uniforms.lineColor.value={appSettings.value.theme && colors["outline"]}
uniforms.zoomLimits.value={[minZoom, maxZoom]} uniforms.zoomLimits.value={[minZoom, maxZoom]}
uniforms.dimensions.value={[width, height]} uniforms.dimensions.value={[width, height]}
/> />

View File

@@ -7,7 +7,7 @@
}); });
$effect.root(() => { $effect.root(() => {
$effect(() => { $effect(() => {
appSettings.theme; appSettings.value.theme;
circleMaterial.color = colors.edge.clone().convertSRGBToLinear(); circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
}); });
}); });
@@ -42,7 +42,7 @@
let geometry: BufferGeometry | null = $state(null); let geometry: BufferGeometry | null = $state(null);
const lineColor = $derived( const lineColor = $derived(
appSettings.theme && colors.edge.clone().convertSRGBToLinear(), appSettings.value.theme && colors.edge.clone().convertSRGBToLinear(),
); );
let lastId: number | null = null; let lastId: number | null = null;
@@ -114,6 +114,9 @@
{#if geometry} {#if geometry}
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}> <T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
<MeshLineMaterial width={Math.max(z * 0.0001, 0.00001)} color={lineColor} /> <MeshLineMaterial
width={Math.max(z * 0.00012, 0.00003)}
color={lineColor}
/>
</T.Mesh> </T.Mesh>
{/if} {/if}

View File

@@ -19,7 +19,7 @@
const { invalidate } = useThrelte(); const { invalidate } = useThrelte();
$effect(() => { $effect(() => {
appSettings.theme; appSettings.value.theme;
invalidate(); invalidate();
}); });

View File

@@ -12,21 +12,23 @@ const variables = [
"edge", "edge",
] as const; ] as const;
function getColor(variable: typeof variables[number]) { function getColor(variable: (typeof variables)[number]) {
const style = getComputedStyle(document.body.parentElement!); const style = getComputedStyle(document.body.parentElement!);
let color = style.getPropertyValue(`--${variable}`); let color = style.getPropertyValue(`--${variable}`);
return new Color().setStyle(color, LinearSRGBColorSpace); return new Color().setStyle(color, LinearSRGBColorSpace);
} }
export const colors = Object.fromEntries(variables.map(v => [v, getColor(v)])) as Record<typeof variables[number], Color>; export const colors = Object.fromEntries(
variables.map((v) => [v, getColor(v)]),
) as Record<(typeof variables)[number], Color>;
$effect.root(() => { $effect.root(() => {
$effect(() => { $effect(() => {
if (!appSettings.theme || !("getComputedStyle" in globalThis)) return; if (!appSettings.value.theme || !("getComputedStyle" in globalThis)) return;
const style = getComputedStyle(document.body.parentElement!); const style = getComputedStyle(document.body.parentElement!);
for (const v of variables) { for (const v of variables) {
const hex = style.getPropertyValue(`--${v}`); const hex = style.getPropertyValue(`--${v}`);
colors[v].setStyle(hex, LinearSRGBColorSpace); colors[v].setStyle(hex, LinearSRGBColorSpace);
} }
}); });
}) });

View File

@@ -23,7 +23,7 @@
const isSelected = $derived(graphState.selectedNodes.has(node.id)); const isSelected = $derived(graphState.selectedNodes.has(node.id));
let strokeColor = $state(colors.selected); let strokeColor = $state(colors.selected);
$effect(() => { $effect(() => {
appSettings.theme; appSettings.value.theme;
strokeColor = isSelected strokeColor = isSelected
? colors.selected ? colors.selected
: isActive : isActive

View File

@@ -57,7 +57,7 @@
{#each parameters as [key, value], i} {#each parameters as [key, value], i}
<NodeParameter <NodeParameter
{node} bind:node
id={key} id={key}
input={value} input={value}
isLast={i == parameters.length - 1} isLast={i == parameters.length - 1}

View File

@@ -3,7 +3,7 @@
import type { Node, Socket } from "@nodes/types"; import type { Node, Socket } from "@nodes/types";
import { getContext } from "svelte"; import { getContext } from "svelte";
const { node = $bindable<Node>() } = $props(); const { node }: { node: Node } = $props();
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket"); const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
const getSocketPosition = const getSocketPosition =

View File

@@ -17,7 +17,7 @@
isLast?: boolean; isLast?: boolean;
}; };
const { node, input, id, isLast }: Props = $props(); let { node = $bindable(), input, id, isLast }: Props = $props();
const inputType = node?.tmp?.type?.inputs?.[id]!; const inputType = node?.tmp?.type?.inputs?.[id]!;
@@ -87,7 +87,7 @@
<label for={elementId}>{input.label || id}</label> <label for={elementId}>{input.label || id}</label>
{/if} {/if}
{#if inputType.external !== true} {#if inputType.external !== true}
<NodeInput {elementId} {node} {input} {id} /> <NodeInput {elementId} bind:node {input} {id} />
{/if} {/if}
</div> </div>

View File

@@ -8,7 +8,7 @@
index = getContext<() => number>("registerCell")(); index = getContext<() => number>("registerCell")();
} }
const sizes = getContext<string[]>("sizes"); const sizes = getContext<{ value: string[] }>("sizes");
let downSizes: string[] = []; let downSizes: string[] = [];
let downWidth = 0; let downWidth = 0;
@@ -16,7 +16,7 @@
let startX = 0; let startX = 0;
function handleMouseDown(event: MouseEvent) { function handleMouseDown(event: MouseEvent) {
downSizes = [...sizes]; downSizes = [...sizes.value];
mouseDown = true; mouseDown = true;
startX = event.clientX; startX = event.clientX;
downWidth = wrapper.getBoundingClientRect().width; downWidth = wrapper.getBoundingClientRect().width;
@@ -25,7 +25,7 @@
function handleMouseMove(event: MouseEvent) { function handleMouseMove(event: MouseEvent) {
if (mouseDown) { if (mouseDown) {
const width = downWidth + startX - event.clientX; const width = downWidth + startX - event.clientX;
sizes[index] = `${width}px`; sizes.value[index] = `${width}px`;
} }
} }
</script> </script>

View File

@@ -11,8 +11,8 @@
setContext("registerCell", function () { setContext("registerCell", function () {
let index = registerIndex; let index = registerIndex;
registerIndex++; registerIndex++;
if (registerIndex > sizes.length) { if (registerIndex > sizes.value.length) {
sizes = [...sizes, "1fr"]; sizes.value = [...sizes.value, "1fr"];
} }
return index; return index;
}); });
@@ -20,7 +20,7 @@
setContext("sizes", sizes); setContext("sizes", sizes);
const cols = $derived( const cols = $derived(
sizes.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" "), sizes.value.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" "),
); );
</script> </script>

View File

@@ -1,11 +1,34 @@
export function localState<T>(key: string, defaultValue: T): T { import { browser } from "$app/environment";
const stored = localStorage.getItem(key);
const state = $state(stored ? JSON.parse(stored) : defaultValue); export class LocalStore<T> {
value = $state<T>() as T;
key = "";
constructor(key: string, value: T) {
this.key = key;
this.value = value;
if (browser) {
const item = localStorage.getItem(key);
if (item) this.value = this.deserialize(item);
}
$effect.root(() => { $effect.root(() => {
$effect(() => { $effect(() => {
const value = $state.snapshot(state); localStorage.setItem(this.key, this.serialize(this.value));
localStorage.setItem(key, JSON.stringify(value));
}); });
}); });
return state; }
serialize(value: T): string {
return JSON.stringify(value);
}
deserialize(item: string): T {
return JSON.parse(item);
}
}
export function localState<T>(key: string, value: T) {
return new LocalStore(key, value);
} }

View File

@@ -1,6 +1,11 @@
<script lang="ts"> <script lang="ts">
import { T, useTask, useThrelte } from "@threlte/core"; import { T, useTask, useThrelte } from "@threlte/core";
import { MeshLineGeometry, MeshLineMaterial, Text } from "@threlte/extras"; import {
Grid,
MeshLineGeometry,
MeshLineMaterial,
Text,
} from "@threlte/extras";
import { import {
type Group, type Group,
type BufferGeometry, type BufferGeometry,
@@ -70,7 +75,7 @@
} }
$effect(() => { $effect(() => {
const wireframe = appSettings.debug.wireframe; const wireframe = appSettings.value.debug.wireframe;
scene.traverse(function (child) { scene.traverse(function (child) {
if (isMesh(child) && isMatCapMaterial(child.material)) { if (isMesh(child) && isMatCapMaterial(child.material)) {
child.material.wireframe = wireframe; child.material.wireframe = wireframe;
@@ -90,18 +95,25 @@
<Camera {center} {centerCamera} /> <Camera {center} {centerCamera} />
{#if appSettings.showGrid} {#if appSettings.value.showGrid}
<T.GridHelper <Grid
args={[20, 20]} cellColor={colors["outline"]}
colorGrid={colors["outline"]} cellThickness={0.7}
colorCenterLine={new Color("red")} infiniteGrid
sectionThickness={0.7}
sectionDistance={2}
sectionColor={colors["outline"]}
fadeDistance={50}
fadeStrength={10}
fadeOrigin={new Vector3(0, 0, 0)}
/> />
<!-- <T.GridHelper args={[20, 20]} color5={colors["outline"]} /> -->
{/if} {/if}
<T.Group> <T.Group>
{#if geometries} {#if geometries}
{#each geometries as geo} {#each geometries as geo}
{#if appSettings.debug.showIndices} {#if appSettings.value.debug.showIndices}
{#each geo.attributes.position.array as _, i} {#each geo.attributes.position.array as _, i}
{#if i % 3 === 0} {#if i % 3 === 0}
<Text fontSize={0.25} position={getPosition(geo, i)} /> <Text fontSize={0.25} position={getPosition(geo, i)} />
@@ -109,7 +121,7 @@
{/each} {/each}
{/if} {/if}
{#if appSettings.debug.showVertices} {#if appSettings.value.debug.showVertices}
<T.Points visible={true}> <T.Points visible={true}>
<T is={geo} /> <T is={geo} />
<T.PointsMaterial size={0.25} /> <T.PointsMaterial size={0.25} />
@@ -121,7 +133,7 @@
<T.Group bind:ref={scene}></T.Group> <T.Group bind:ref={scene}></T.Group>
</T.Group> </T.Group>
{#if appSettings.debug.showStemLines && lines} {#if appSettings.value.debug.showStemLines && lines}
{#each lines as line} {#each lines as line}
<T.Mesh> <T.Mesh>
<MeshLineGeometry points={line} /> <MeshLineGeometry points={line} />

View File

@@ -68,7 +68,7 @@
const inputs = splitNestedArray(result); const inputs = splitNestedArray(result);
perf.endPoint(); perf.endPoint();
if (appSettings.debug.showStemLines) { if (appSettings.value.debug.showStemLines) {
perf.addPoint("create-lines"); perf.addPoint("create-lines");
lines = inputs lines = inputs
.map((input) => { .map((input) => {
@@ -91,7 +91,7 @@
}; };
</script> </script>
{#if appSettings.debug.showPerformancePanel} {#if appSettings.value.debug.showPerformancePanel}
<SmallPerformanceViewer {fps} store={perf} /> <SmallPerformanceViewer {fps} store={perf} />
{/if} {/if}

View File

@@ -77,13 +77,13 @@
let internalValue = $state(getDefaultValue()); let internalValue = $state(getDefaultValue());
let open = $state(openSections[id]); let open = $state(openSections.value[id]);
// Persist <details> open/closed state for groups // Persist <details> open/closed state for groups
if (depth > 0 && !isNodeInput(type[key!])) { if (depth > 0 && !isNodeInput(type[key!])) {
$effect(() => { $effect(() => {
if (open !== undefined) { if (open !== undefined) {
openSections[id] = open; openSections.value[id] = open;
} }
}); });
} }
@@ -110,7 +110,7 @@
<!-- Leaf input --> <!-- Leaf input -->
<div class="input input-{type[key].type}" class:first-level={depth === 1}> <div class="input input-{type[key].type}" class:first-level={depth === 1}>
{#if type[key].type === "button"} {#if type[key].type === "button"}
<button onclick={() => console.log(type[key])}> <button onclick={() => type[key].callback()}>
{type[key].label || key} {type[key].label || key}
</button> </button>
{:else} {:else}
@@ -124,7 +124,7 @@
<NestedSettings <NestedSettings
id={`${id}.${childKey}`} id={`${id}.${childKey}`}
key={childKey} key={childKey}
{value} bind:value
{type} {type}
depth={depth + 1} depth={depth + 1}
/> />
@@ -142,7 +142,7 @@
<NestedSettings <NestedSettings
id={`${id}.${childKey}`} id={`${id}.${childKey}`}
key={childKey} key={childKey}
value={value[key] as SettingsValue} bind:value={value[key] as SettingsValue}
type={type[key] as unknown as SettingsType} type={type[key] as unknown as SettingsType}
depth={depth + 1} depth={depth + 1}
/> />

View File

@@ -144,7 +144,7 @@ type ExtractSettingsValues<T> = {
: never; : never;
}; };
function settingsToStore<T>(settings: T): ExtractSettingsValues<T> { export function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
const result = {} as any; const result = {} as any;
for (const key in settings) { for (const key in settings) {
const value = settings[key]; const value = settings[key];
@@ -166,7 +166,7 @@ export let appSettings = localState(
$effect.root(() => { $effect.root(() => {
$effect(() => { $effect(() => {
const theme = appSettings.theme; const theme = appSettings.value.theme;
const classes = document.documentElement.classList; const classes = document.documentElement.classList;
const newClassName = `theme-${theme}`; const newClassName = `theme-${theme}`;
if (classes) { if (classes) {

View File

@@ -38,7 +38,7 @@
memoryRuntime.perf = performanceStore; memoryRuntime.perf = performanceStore;
const runtime = $derived( const runtime = $derived(
appSettings.debug.useWorker ? workerRuntime : memoryRuntime, appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime,
); );
let activeNode = $state<Node | undefined>(undefined); let activeNode = $state<Node | undefined>(undefined);
@@ -69,6 +69,11 @@
}, },
]); ]);
let graphSettings = $state<Record<string, any>>({}); let graphSettings = $state<Record<string, any>>({});
$effect(() => {
if (graphSettings) {
manager?.setSettings($state.snapshot(graphSettings));
}
});
type BooleanSchema = { type BooleanSchema = {
[key: string]: { [key: string]: {
type: "boolean"; type: "boolean";
@@ -92,7 +97,7 @@
); );
let b = performance.now(); let b = performance.now();
if (appSettings.debug.useWorker) { if (appSettings.value.debug.useWorker) {
let perfData = await runtime.getPerformanceData(); let perfData = await runtime.getPerformanceData();
let lastRun = perfData?.at(-1); let lastRun = perfData?.at(-1);
if (lastRun?.total) { if (lastRun?.total) {
@@ -118,13 +123,13 @@
//@ts-ignore //@ts-ignore
AppSettingTypes.debug.stressTest.loadGrid.callback = () => { AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
graph = templates.grid( graph = templates.grid(
appSettings.debug.amount.value, appSettings.value.debug.amount.value,
appSettings.debug.amount.value, appSettings.value.debug.amount.value,
); );
}; };
//@ts-ignore //@ts-ignore
AppSettingTypes.debug.stressTest.loadTree.callback = () => { AppSettingTypes.debug.stressTest.loadTree.callback = () => {
graph = templates.tree(appSettings.debug.amount.value); graph = templates.tree(appSettings.value.debug.amount.value);
}; };
//@ts-ignore //@ts-ignore
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => { AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
@@ -155,7 +160,7 @@
bind:scene bind:scene
bind:this={viewerComponent} bind:this={viewerComponent}
perf={performanceStore} perf={performanceStore}
centerCamera={appSettings.centerCamera} centerCamera={appSettings.value.centerCamera}
/> />
</Grid.Cell> </Grid.Cell>
<Grid.Cell> <Grid.Cell>
@@ -163,10 +168,10 @@
{graph} {graph}
bind:this={graphInterface} bind:this={graphInterface}
registry={nodeRegistry} registry={nodeRegistry}
showGrid={appSettings.nodeInterface.showNodeGrid} showGrid={appSettings.value.nodeInterface.showNodeGrid}
snapToGrid={appSettings.nodeInterface.snapToGrid} snapToGrid={appSettings.value.nodeInterface.snapToGrid}
bind:activeNode bind:activeNode
bind:showHelp={appSettings.nodeInterface.showHelp} bind:showHelp={appSettings.value.nodeInterface.showHelp}
bind:settings={graphSettings} bind:settings={graphSettings}
bind:settingTypes={graphSettingTypes} bind:settingTypes={graphSettingTypes}
onresult={(result) => handleUpdate(result)} onresult={(result) => handleUpdate(result)}
@@ -176,7 +181,7 @@
<Panel id="general" title="General" icon="i-tabler-settings"> <Panel id="general" title="General" icon="i-tabler-settings">
<NestedSettings <NestedSettings
id="general" id="general"
value={appSettings} bind:value={appSettings.value}
type={AppSettingTypes} type={AppSettingTypes}
/> />
</Panel> </Panel>
@@ -207,7 +212,7 @@
id="performance" id="performance"
title="Performance" title="Performance"
classes="text-red-400" classes="text-red-400"
hidden={!appSettings.debug.showPerformancePanel} hidden={!appSettings.value.debug.showPerformancePanel}
icon="i-tabler-brand-speedtest" icon="i-tabler-brand-speedtest"
> >
{#if $performanceStore} {#if $performanceStore}
@@ -218,7 +223,7 @@
id="benchmark" id="benchmark"
title="Benchmark" title="Benchmark"
classes="text-red-400" classes="text-red-400"
hidden={!appSettings.debug.showBenchmarkPanel} hidden={!appSettings.value.debug.showBenchmarkPanel}
icon="i-tabler-graph" icon="i-tabler-graph"
> >
<BenchmarkPanel run={randomGenerate} /> <BenchmarkPanel run={randomGenerate} />
@@ -250,7 +255,6 @@
<style> <style>
header { header {
/* border-bottom: solid thin var(--outline); */
background-color: var(--layer-1); background-color: var(--layer-1);
} }