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

View File

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

View File

@@ -7,7 +7,7 @@
});
$effect.root(() => {
$effect(() => {
appSettings.theme;
appSettings.value.theme;
circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
});
});
@@ -42,7 +42,7 @@
let geometry: BufferGeometry | null = $state(null);
const lineColor = $derived(
appSettings.theme && colors.edge.clone().convertSRGBToLinear(),
appSettings.value.theme && colors.edge.clone().convertSRGBToLinear(),
);
let lastId: number | null = null;
@@ -114,6 +114,9 @@
{#if 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>
{/if}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,34 @@
export function localState<T>(key: string, defaultValue: T): T {
const stored = localStorage.getItem(key);
const state = $state(stored ? JSON.parse(stored) : defaultValue);
import { browser } from "$app/environment";
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(() => {
const value = $state.snapshot(state);
localStorage.setItem(key, JSON.stringify(value));
localStorage.setItem(this.key, this.serialize(this.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">
import { T, useTask, useThrelte } from "@threlte/core";
import { MeshLineGeometry, MeshLineMaterial, Text } from "@threlte/extras";
import {
Grid,
MeshLineGeometry,
MeshLineMaterial,
Text,
} from "@threlte/extras";
import {
type Group,
type BufferGeometry,
@@ -70,7 +75,7 @@
}
$effect(() => {
const wireframe = appSettings.debug.wireframe;
const wireframe = appSettings.value.debug.wireframe;
scene.traverse(function (child) {
if (isMesh(child) && isMatCapMaterial(child.material)) {
child.material.wireframe = wireframe;
@@ -90,18 +95,25 @@
<Camera {center} {centerCamera} />
{#if appSettings.showGrid}
<T.GridHelper
args={[20, 20]}
colorGrid={colors["outline"]}
colorCenterLine={new Color("red")}
{#if appSettings.value.showGrid}
<Grid
cellColor={colors["outline"]}
cellThickness={0.7}
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}
<T.Group>
{#if geometries}
{#each geometries as geo}
{#if appSettings.debug.showIndices}
{#if appSettings.value.debug.showIndices}
{#each geo.attributes.position.array as _, i}
{#if i % 3 === 0}
<Text fontSize={0.25} position={getPosition(geo, i)} />
@@ -109,7 +121,7 @@
{/each}
{/if}
{#if appSettings.debug.showVertices}
{#if appSettings.value.debug.showVertices}
<T.Points visible={true}>
<T is={geo} />
<T.PointsMaterial size={0.25} />
@@ -121,7 +133,7 @@
<T.Group bind:ref={scene}></T.Group>
</T.Group>
{#if appSettings.debug.showStemLines && lines}
{#if appSettings.value.debug.showStemLines && lines}
{#each lines as line}
<T.Mesh>
<MeshLineGeometry points={line} />

View File

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

View File

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

View File

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

View File

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