feat: migrate some more stuff to svelte-5, mainly app settings
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 4s

This commit is contained in:
max_richter 2024-11-08 02:38:19 +01:00
parent 4f03f2af5a
commit 5421349c79
34 changed files with 375 additions and 165 deletions

View File

@ -11,7 +11,6 @@
}, },
"dependencies": { "dependencies": {
"@nodes/registry": "link:../packages/registry", "@nodes/registry": "link:../packages/registry",
"@nodes/runtime": "link:../packages/runtime",
"@nodes/ui": "link:../packages/ui", "@nodes/ui": "link:../packages/ui",
"@nodes/utils": "link:../packages/utils", "@nodes/utils": "link:../packages/utils",
"@sveltejs/kit": "^2.7.4", "@sveltejs/kit": "^2.7.4",

View File

@ -29,16 +29,22 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
private _nodes: Map<number, Node> = new Map(); private _nodes: Map<number, Node> = new Map();
nodes: Writable<Map<number, Node>> = writable(new Map()); nodes: Writable<Map<number, Node>> = writable(new Map());
settingTypes: Record<string, NodeInput> = {};
settings: Record<string, unknown> = {};
private _edges: Edge[] = []; private _edges: Edge[] = [];
edges: Writable<Edge[]> = writable([]); edges: Writable<Edge[]> = writable([]);
settingTypes: Record<string, NodeInput> = {};
settings: Record<string, unknown> = {};
currentUndoGroup: number | null = null; currentUndoGroup: number | null = null;
inputSockets: Writable<Set<string>> = writable(new Set()); inputSockets: Writable<Set<string>> = writable(new Set());
history: HistoryManager = new HistoryManager(); history: HistoryManager = new HistoryManager();
execute = throttle(() => {
if (this.loaded === false) return;
this.emit("result", this.serialize());
}, 10);
constructor(public registry: NodeRegistry) { constructor(public registry: NodeRegistry) {
super(); super();
@ -53,7 +59,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
} }
this.inputSockets.set(s); this.inputSockets.set(s);
}); });
this.execute = throttle(() => this._execute(), 10);
} }
serialize(): Graph { serialize(): Graph {
@ -67,7 +72,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]) as Graph["edges"]; const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]) as Graph["edges"];
const serialized = { id: this.graph.id, settings: this.settings, nodes, edges }; const serialized = { id: this.graph.id, settings: this.settings, nodes, edges };
logger.groupEnd(); logger.groupEnd();
console.log({ serialized });
return clone(serialized); return clone(serialized);
} }
@ -86,11 +90,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
} }
execute() { }
_execute() {
if (this.loaded === false) return;
this.emit("result", this.serialize());
}
getNodeDefinitions() { getNodeDefinitions() {
return this.registry.getAllNodes(); return this.registry.getAllNodes();
@ -111,9 +110,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
return [...nodes.values()]; return [...nodes.values()];
} }
getEdgesBetweenNodes(nodes: Node[]): [number, number, number, string][] { getEdgesBetweenNodes(nodes: Node[]): [number, number, number, string][] {
const edges = []; const edges = [];
for (const node of nodes) { for (const node of nodes) {
const children = node.tmp?.children || []; const children = node.tmp?.children || [];

View File

@ -21,11 +21,10 @@
import HelpView from "../HelpView.svelte"; import HelpView from "../HelpView.svelte";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import { Canvas } from "@threlte/core"; import { Canvas } from "@threlte/core";
import { getGraphManager } from "./context.js";
const state = getGraphState(); const state = getGraphState();
export let manager: GraphManager;
export let snapToGrid = true; export let snapToGrid = true;
export let showGrid = true; export let showGrid = true;
export let showHelp = false; export let showHelp = false;
@ -33,7 +32,8 @@
let keymap = let keymap =
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]); getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
setContext("graphManager", manager); const manager = getGraphManager();
const status = manager.status; const status = manager.status;
const nodes = manager.nodes; const nodes = manager.nodes;
const edges = manager.edges; const edges = manager.edges;
@ -965,6 +965,7 @@
<style> <style>
.graph-wrapper { .graph-wrapper {
position: relative; position: relative;
z-index: 0;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
height: 100%; height: 100%;
} }

View File

@ -8,6 +8,9 @@
import { createKeyMap } from "$lib/helpers/createKeyMap"; import { createKeyMap } from "$lib/helpers/createKeyMap";
import { GraphState } from "./state.svelte"; import { GraphState } from "./state.svelte";
const state = new GraphState();
setContext("graphState", state);
type Props = { type Props = {
graph: Graph; graph: Graph;
registry: NodeRegistry; registry: NodeRegistry;
@ -38,10 +41,9 @@
}: Props = $props(); }: Props = $props();
export const keymap = createKeyMap([]); export const keymap = createKeyMap([]);
export const manager = new GraphManager(registry);
const state = new GraphState(); export const manager = new GraphManager(registry);
setContext("graphState", state); setContext("graphManager", manager);
$effect(() => { $effect(() => {
if (state.activeNodeId !== -1) { if (state.activeNodeId !== -1) {
@ -75,4 +77,4 @@
manager.load(graph); manager.load(graph);
</script> </script>
<GraphEl {manager} bind:showGrid bind:snapToGrid bind:showHelp /> <GraphEl bind:showGrid bind:snapToGrid bind:showHelp />

View File

@ -18,7 +18,6 @@ let lastStyle = "";
function updateColors() { function updateColors() {
if (!("getComputedStyle" in globalThis)) return; if (!("getComputedStyle" in globalThis)) return;
console.log("updateColors")
const style = getComputedStyle(document.body.parentElement!); const style = getComputedStyle(document.body.parentElement!);
let hash = ""; let hash = "";
for (const v of variables) { for (const v of variables) {

View File

@ -8,12 +8,7 @@ export function getGraphState() {
export class GraphState { export class GraphState {
activeNodeId = $state(-1); activeNodeId = $state(-1);
selectedNodes = $state(new Set<number>()); selectedNodes = $state(new Set<number>());
clearSelection() {
this.selectedNodes = new Set();
}
activeSocket = $state<Socket | null>(null); activeSocket = $state<Socket | null>(null);
hoveredSocket = $state<Socket | null>(null); hoveredSocket = $state<Socket | null>(null);
possibleSockets = $state<Socket[]>([]); possibleSockets = $state<Socket[]>([]);
@ -21,6 +16,10 @@ export class GraphState {
this.possibleSockets.map((s) => `${s.node.id}-${s.index}`), this.possibleSockets.map((s) => `${s.node.id}-${s.index}`),
)); ));
clearSelection() {
this.selectedNodes = new Set();
}
} }
export { colors } from "./colors"; export { colors } from "./colors";

View File

@ -3,7 +3,6 @@
import NodeHeader from "./NodeHeader.svelte"; import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte"; import NodeParameter from "./NodeParameter.svelte";
import { getContext, onMount } from "svelte"; import { getContext, onMount } from "svelte";
import Page from "../../../routes/+page.svelte";
export let isActive = false; export let isActive = false;
export let isSelected = false; export let isSelected = false;
export let inView = true; export let inView = true;

View File

@ -26,8 +26,8 @@
const aspectRatio = 0.25; const aspectRatio = 0.25;
const path = createNodePath({ const path = createNodePath({
depth: 7, depth: 5,
height: 40, height: 29,
y: 50, y: 50,
cornerTop, cornerTop,
rightBump, rightBump,

View File

@ -59,9 +59,9 @@
aspectRatio, aspectRatio,
}); });
const pathDisabled = createNodePath({ const pathDisabled = createNodePath({
depth: 0, depth: 4.5,
height: 15, height: 14,
y: 50, y: 50.5,
cornerBottom, cornerBottom,
leftBump, leftBump,
aspectRatio, aspectRatio,
@ -80,7 +80,7 @@
class="wrapper" class="wrapper"
data-node-type={node.type} data-node-type={node.type}
data-node-input={id} data-node-input={id}
class:disabled={!graphState.possibleSocketIds.has(socketId)} class:disabled={!graphState?.possibleSocketIds.has(socketId)}
> >
{#key id && graphId} {#key id && graphId}
<div class="content" class:disabled={$inputSockets?.has(socketId)}> <div class="content" class:disabled={$inputSockets?.has(socketId)}>

View File

@ -0,0 +1,24 @@
export function isObject(item: Record<string, unknown> | unknown): item is Record<string, unknown> {
return (typeof item === 'object' && !Array.isArray(item));
}
type Object = Record<string, unknown>;
export function mergeDeep<T extends Object>(target: T, ...sources: Object[]): T {
if (!sources.length) return target;
const source = sources.shift();
if (source === undefined) return target;
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
if (isObject(target[key])) mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}

View File

@ -0,0 +1,11 @@
export function localState<T>(key: string, defaultValue: T): T {
const stored = localStorage.getItem(key)
const state = $state(stored ? JSON.parse(stored) : defaultValue)
$effect.root(() => {
$effect(() => {
const value = $state.snapshot(state);
localStorage.setItem(key, JSON.stringify(value));
});
});
return state;
}

View File

@ -3,6 +3,7 @@
import type { NodeDefinition } from "@nodes/types"; import type { NodeDefinition } from "@nodes/types";
export let node: NodeDefinition; export let node: NodeDefinition;
console.log(node);
let dragging = false; let dragging = false;

View File

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import localStore from "$lib/helpers/localStore";
import type { RemoteNodeRegistry } from "$lib/node-registry-client";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import BreadCrumbs from "./BreadCrumbs.svelte"; import BreadCrumbs from "./BreadCrumbs.svelte";
import DraggableNode from "./DraggableNode.svelte"; import DraggableNode from "./DraggableNode.svelte";
import type { RemoteNodeRegistry } from "@nodes/registry";
export let registry: RemoteNodeRegistry; export let registry: RemoteNodeRegistry;
@ -58,7 +57,7 @@
{:then collection} {:then collection}
{#each collection.nodes as node} {#each collection.nodes as node}
{#await registry.fetchNodeDefinition(node.id)} {#await registry.fetchNodeDefinition(node.id)}
<div>Loading...</div> <div>Loading... {node.id}</div>
{:then node} {:then node}
{#if node} {#if node}
<DraggableNode {node} /> <DraggableNode {node} />

View File

@ -25,44 +25,42 @@
<div class="wrapper"> <div class="wrapper">
<table> <table>
<tbody> <tbody>
<tr on:click={() => ($open.runtime = !$open.runtime)}> <tr on:click={() => ($open.runtime = !$open.runtime)}>
<td>{$open.runtime ? "-" : "+"} runtime </td> <td>{$open.runtime ? "-" : "+"} runtime </td>
<td>{humanizeDuration(runtime || 1000)}</td> <td>{humanizeDuration(runtime || 1000)}</td>
</tr> </tr>
{#if $open.runtime} {#if $open.runtime}
<tr> <tr>
<td colspan="2"> <td colspan="2">
<SmallGraph points={getPoints($store, "runtime")} /> <SmallGraph points={getPoints($store, "runtime")} />
</td>
</tr>
{/if}
<tr on:click={() => ($open.fps = !$open.fps)}>
<td>{$open.fps ? "-" : "+"} fps </td>
<td>
{Math.floor(fps[fps.length - 1])}fps
</td> </td>
</tr> </tr>
{/if} {#if $open.fps}
<tr>
<td colspan="2">
<SmallGraph points={fps} />
</td>
</tr>
{/if}
<tr on:click={() => ($open.fps = !$open.fps)}>
<td>{$open.fps ? "-" : "+"} fps </td>
<td>
{#if fps[fps.length - 1] > 5}
{Math.floor(1000 / fps[fps.length - 1])}fps
{/if}
</td>
</tr>
{#if $open.fps}
<tr> <tr>
<td colspan="2"> <td>vertices </td>
<SmallGraph points={fps} /> <td>{humanizeNumber(vertices || 0)}</td>
</td>
</tr> </tr>
{/if}
<tr> <tr>
<td>vertices </td> <td>faces </td>
<td>{humanizeNumber(vertices || 0)}</td> <td>{humanizeNumber(faces || 0)}</td>
</tr> </tr>
</tbody>
<tr>
<td>faces </td>
<td>{humanizeNumber(faces || 0)}</td>
</tr>
</tbody>
</table> </table>
</div> </div>
@ -71,6 +69,7 @@
position: absolute; position: absolute;
top: 10px; top: 10px;
left: 10px; left: 10px;
z-index: 2;
background: var(--layer-0); background: var(--layer-0);
border: solid thin var(--outline); border: solid thin var(--outline);
border-collapse: collapse; border-collapse: collapse;

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { T, useThrelte } from "@threlte/core"; import { T, useTask, useThrelte } from "@threlte/core";
import { MeshLineGeometry, MeshLineMaterial, Text } from "@threlte/extras"; import { MeshLineGeometry, MeshLineMaterial, Text } from "@threlte/extras";
import { import {
type Group, type Group,
@ -8,23 +8,29 @@
type Vector3Tuple, type Vector3Tuple,
Box3, Box3,
Mesh, Mesh,
MeshMatcapMaterial,
MeshBasicMaterial, MeshBasicMaterial,
} from "three"; } from "three";
import { AppSettings } from "../settings/app-settings"; import { AppSettings } from "../settings/app-settings";
import Camera from "./Camera.svelte"; import Camera from "./Camera.svelte";
const threlte = useThrelte(); const { renderStage, invalidate: _invalidate } = useThrelte();
export let fps: number[] = []; export let fps: number[] = [];
let renderer = threlte.renderer; // let renderer = threlte.renderer;
let rendererRender = renderer.render; // let rendererRender = renderer.render;
renderer.render = function (scene, camera) { // renderer.render = function (scene, camera) {
const a = performance.now(); // const a = performance.now();
rendererRender.call(renderer, scene, camera); // rendererRender.call(renderer, scene, camera);
fps.push(performance.now() - a); // fps.push(performance.now() - a);
fps = fps.slice(-100); // fps = fps.slice(-100);
}; // };
useTask(
(delta) => {
fps.push(1 / delta);
fps = fps.slice(-100);
},
{ stage: renderStage, autoInvalidate: false },
);
export const invalidate = function () { export const invalidate = function () {
if (scene) { if (scene) {
@ -44,7 +50,7 @@
.max(new Vector3(-4, -4, -4)) .max(new Vector3(-4, -4, -4))
.min(new Vector3(4, 4, 4)); .min(new Vector3(4, 4, 4));
} }
threlte.invalidate(); _invalidate();
}; };
let geometries: BufferGeometry[] = []; let geometries: BufferGeometry[] = [];
@ -68,7 +74,7 @@
child.material.wireframe = $AppSettings.wireframe; child.material.wireframe = $AppSettings.wireframe;
} }
}); });
threlte.invalidate(); invalidate();
} }
function getPosition(geo: BufferGeometry, i: number) { function getPosition(geo: BufferGeometry, i: number) {

View File

@ -1,3 +1,4 @@
export * from "./runtime-executor" export * from "./runtime-executor"
export * from "./runtime-executor-cache" export * from "./runtime-executor-cache"
export * from "./worker-runtime-executor" export * from "./worker-runtime-executor"

View File

@ -1,9 +1,10 @@
/// <reference types="vite-plugin-comlink/client" /> /// <reference types="vite-plugin-comlink/client" />
import type { Graph, RuntimeExecutor } from "@nodes/types"; import type { Graph, RuntimeExecutor } from "@nodes/types";
export class WorkerRuntimeExecutor implements RuntimeExecutor { export class WorkerRuntimeExecutor implements RuntimeExecutor {
private worker = new ComlinkWorker<typeof import('./worker-runtime-executor-backend.ts')>(new URL("worker-runtime-executor-backend.ts", import.meta.url)); private worker = new ComlinkWorker<typeof import('./worker-runtime-executor-backend.ts')>(new URL(`./worker-runtime-executor-backend.ts`, import.meta.url));
constructor() { constructor() {
console.log(import.meta.url) console.log(import.meta.url)
} }

View File

@ -94,6 +94,7 @@
.content { .content {
background: var(--layer-1); background: var(--layer-1);
z-index: 10;
position: relative; position: relative;
max-height: 100vh; max-height: 100vh;
overflow-y: auto; overflow-y: auto;

View File

@ -0,0 +1,153 @@
import { localState } from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types";
const themes = ["dark", "light", "catppuccin", "solarized", "high-contrast", "nord", "dracula"];
export const AppSettingTypes = {
theme: {
type: "select",
options: themes,
label: "Theme",
value: themes[0],
},
showGrid: {
type: "boolean",
label: "Show Grid",
value: true,
},
centerCamera: {
type: "boolean",
label: "Center Camera",
value: true
},
nodeInterface: {
title: "Node Interface",
showNodeGrid: {
type: "boolean",
label: "Show Grid",
value: true
},
snapToGrid: {
type: "boolean",
label: "Snap to Grid",
value: true
},
showHelp: {
type: "boolean",
label: "Show Help",
value: false
}
},
debug: {
wireframe: {
type: "boolean",
label: "Wireframe",
value: false,
},
useWorker: {
type: "boolean",
label: "Execute runtime in worker",
value: true,
},
showIndices: {
type: "boolean",
label: "Show Indices",
value: false,
},
showPerformancePanel: {
type: "boolean",
label: "Show Performance Panel",
value: false,
},
showBenchmarkPanel: {
type: "boolean",
label: "Show Benchmark Panel",
value: false,
},
showVertices: {
type: "boolean",
label: "Show Vertices",
value: false,
},
showStemLines: {
type: "boolean",
label: "Show Stem Lines",
value: false,
},
stressTest: {
title: "Stress Test",
amount: {
type: "integer",
min: 2,
max: 15
},
loadGrid: {
type: "button",
label: "Load Grid"
},
loadTree: {
type: "button",
label: "Load Tree"
},
lottaFaces: {
type: "button",
label: "Load 'lots of faces'"
},
lottaNodes: {
type: "button",
label: "Load 'lots of nodes'"
},
lottaNodesAndFaces: {
type: "button",
label: "Load 'lots of nodes and faces'"
}
},
}
} as const
type IsInputDefinition<T> = T extends NodeInput ? T : never;
type HasTitle = { title: string };
type ExtractSettingsValues<T> = {
[K in keyof T]: T[K] extends HasTitle
? ExtractSettingsValues<Omit<T[K], 'title'>>
: T[K] extends IsInputDefinition<T[K]>
? T[K] extends { value: any }
? T[K]['value']
: never
: T[K] extends Record<string, any>
? ExtractSettingsValues<T[K]>
: never;
};
function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
const result = {} as any;
for (const key in settings) {
const value = settings[key];
if (value && typeof value === 'object') {
if ('value' in value) {
result[key] = value.value;
} else {
result[key] = settingsToStore(value);
}
}
}
return result;
}
export const appSettings = localState("app-settings", settingsToStore(AppSettingTypes));
$effect.root(() => {
$effect(() => {
const { theme } = $state.snapshot(appSettings);
const classes = document.body.parentElement?.classList;
const newClassName = `theme-${theme}`;
if (classes) {
for (const className of classes) {
if (className.startsWith("theme-") && className !== newClassName) {
classes.remove(className);
}
}
}
document.body?.parentElement?.classList.add(newClassName);
});
});

View File

@ -11,7 +11,7 @@
function filterInputs(inputs: Record<string, NodeInput>) { function filterInputs(inputs: Record<string, NodeInput>) {
return Object.fromEntries( return Object.fromEntries(
Object.entries(inputs) Object.entries(inputs)
.filter(([key, value]) => { .filter(([_key, value]) => {
return value.hidden === true; return value.hidden === true;
}) })
.map(([key, value]) => { .map(([key, value]) => {

View File

@ -3,6 +3,7 @@
import type { OBJExporter } from "three/addons/exporters/OBJExporter.js"; import type { OBJExporter } from "three/addons/exporters/OBJExporter.js";
import type { GLTFExporter } from "three/addons/exporters/GLTFExporter.js"; import type { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import { appSettings } from "../app-settings.svelte";
// Download // Download
const download = ( const download = (
@ -51,6 +52,8 @@
// download .obj file // download .obj file
download(result, "plant", "text/plain", "obj"); download(result, "plant", "text/plain", "obj");
} }
</script> </script>
<div class="p-2"> <div class="p-2">

View File

@ -1,70 +1,100 @@
<script lang="ts"> <script lang="ts">
import localStore from "$lib/helpers/localStore"; import NestedSettings from "./NestedSettings.svelte";
import {localState} from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types"; import type { NodeInput } from "@nodes/types";
import Input from "@nodes/ui"; import Input from "@nodes/ui";
import type { Writable } from "svelte/store";
type Button = { type: "button"; label?: string; callback: () => void }; type Button = { type: "button"; label?: string };
type InputType = NodeInput | Button; type InputType = NodeInput | Button;
interface Nested { interface Nested {
[key: string]: (Nested & { __title?: string }) | InputType; [key: string]: (Nested & { title?: string }) | InputType;
} }
export let id: string; type Props = {
id: string;
key?: string;
value: Record<string, unknown> | string | number | boolean;
type: Nested;
depth?: number;
};
$: expandedDetails = localStore<Record<string, boolean>>( let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
`nodes.settings.expanded.${id}`,
{},
);
export let settings: Nested;
export let store: Writable<Record<string, any>>;
export let depth = 0;
const keys = Object.keys(settings).filter((key) => key !== "__title");
function isNodeInput(v: InputType | Nested): v is InputType { function isNodeInput(v: InputType | Nested): v is InputType {
return v && "type" in v; return v && "type" in v;
} }
let internalValue = $state(Array.isArray(type?.[key]?.options) ? type[key]?.options?.indexOf(value?.[key]) : value?.[key]);
let openSections = localState("open-details", {});
let open = $state(openSections[id]);
if(depth > 0 && !isNodeInput(type[key])){
$effect(() => {
if(open !== undefined){}
openSections[id] = open;
});
}
$effect(() => {
if(key === "" || internalValue === undefined) return;
if(isNodeInput(type[key]) && Array.isArray(type[key]?.options) && typeof internalValue === "number"){
value[key] = type[key].options?.[internalValue];
}else{
value[key] = internalValue;
}
})
</script> </script>
{#if $store} {#if key && isNodeInput(type?.[key]) }
{#each keys as key} <div class="input input-{type[key].type}">
{@const value = settings[key]} {#if type[key].type === "button"}
<div class="wrapper" class:first-level={depth === 0}> <button onclick={() => console.log(type[key])}>
{#if value !== undefined && isNodeInput(value)} {type[key].label || key}
<div </button>
class="input input-{settings[key].type}" {:else}
data-node-type={value?.__node_type || null} <label for={id}>{type[key].label || key}</label>
data-node-input={value?.__node_input || null} <Input id={id} input={type[key]} bind:value={internalValue} />
> {/if}
{#if value.type === "button"} </div>
<button on:click={() => value?.callback?.()} {:else}
>{value.label || key}</button {#if depth === 0}
> {#each Object.keys(type).filter((key) => key !== "title") as childKey}
{:else if "setting" in value && value.setting !== undefined} <NestedSettings
<label for={key}>{settings[key].label || key}</label> id={`${id}.${childKey}`}
<Input id={key} input={value} bind:value={$store[value?.setting]} /> key={childKey}
{:else} value={value as Record<string, unknown>}
<label for={key}>{settings[key].label || key}</label> type={type as Nested}
<Input id={key} input={value} bind:value={$store[key]} /> depth={depth + 1}
{/if} />
</div> {/each}
{:else} {#if depth > 0}
{#if depth > 0} <hr />
<hr /> {/if}
{/if} {:else if key && type?.[key]}
{#if depth > 0}
<hr />
{/if}
<details bind:open>
<summary>{type[key]?.title||key}</summary>
<div class="content">
{#each Object.keys(type[key]).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
value={value[key] as Record<string, unknown>}
type={type[key] as Nested}
depth={depth + 1}
/>
{/each}
</div>
</details>
{/if}
<details bind:open={$expandedDetails[key]}>
<summary>{settings[key]?.__title || key}</summary>
<div class="content">
<svelte:self settings={settings[key]} {store} depth={depth + 1} />
</div>
</details>
{/if}
</div>
{/each}
{/if} {/if}
<style> <style>

View File

@ -6,11 +6,11 @@
import Viewer from "$lib/result-viewer/Viewer.svelte"; import Viewer from "$lib/result-viewer/Viewer.svelte";
import Settings from "$lib/settings/Settings.svelte"; import Settings from "$lib/settings/Settings.svelte";
import { AppSettingTypes, AppSettings } from "$lib/settings/app-settings"; import { AppSettingTypes, AppSettings } from "$lib/settings/app-settings";
import { writable, type Writable } from "svelte/store"; import { appSettings as _appSettings, AppSettingTypes as _AppSettingTypes} from "$lib/settings/app-settings.svelte";
import { writable } from "svelte/store";
import Keymap from "$lib/settings/panels/Keymap.svelte"; import Keymap from "$lib/settings/panels/Keymap.svelte";
import { createKeyMap } from "$lib/helpers/createKeyMap"; import { createKeyMap } from "$lib/helpers/createKeyMap";
import NodeStore from "$lib/node-store/NodeStore.svelte"; import NodeStore from "$lib/node-store/NodeStore.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager";
import ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte"; import ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte";
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte"; import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
import Panel from "$lib/settings/Panel.svelte"; import Panel from "$lib/settings/Panel.svelte";
@ -22,12 +22,11 @@
MemoryRuntimeCache, MemoryRuntimeCache,
WorkerRuntimeExecutor, WorkerRuntimeExecutor,
MemoryRuntimeExecutor, MemoryRuntimeExecutor,
} from "@nodes/runtime"; } from "$lib/runtime";
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry"; import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
import { createPerformanceStore } from "@nodes/utils"; import { createPerformanceStore } from "@nodes/utils";
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte"; import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
import { debounceAsyncFunction } from "$lib/helpers"; import { debounceAsyncFunction } from "$lib/helpers";
import type { Component } from "svelte";
let performanceStore = createPerformanceStore(); let performanceStore = createPerformanceStore();
@ -160,8 +159,8 @@
<Panel id="general" title="General" icon="i-tabler-settings"> <Panel id="general" title="General" icon="i-tabler-settings">
<NestedSettings <NestedSettings
id="general" id="general"
store={AppSettings} value={_appSettings}
settings={AppSettingTypes} type={_AppSettingTypes}
/> />
</Panel> </Panel>
<Panel <Panel

View File

@ -11,11 +11,13 @@
"type": "float", "type": "float",
"min": 0, "min": 0,
"max": 1 "max": 1
"value": 1,
}, },
"curviness": { "curviness": {
"type": "float", "type": "float",
"hidden": true, "hidden": true,
"min": 0, "min": 0,
"value": 0.5,
"max": 1 "max": 1
}, },
"depth": { "depth": {

View File

@ -6,14 +6,17 @@
"inputs": { "inputs": {
"0": { "0": {
"type": "float", "type": "float",
"value": 0,
"label": "" "label": ""
}, },
"1": { "1": {
"type": "float", "type": "float",
"value": 0,
"label": "" "label": ""
}, },
"2": { "2": {
"type": "float", "type": "float",
"value": 0,
"label": "" "label": ""
} }
} }

View File

@ -1,21 +0,0 @@
{
"name": "@nodes/runtime",
"version": "0.0.0",
"description": "",
"main": "src/index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@nodes/registry": "link:../registry",
"@nodes/types": "link:../types",
"@nodes/utils": "link:../utils"
},
"devDependencies": {
"comlink": "^4.4.1",
"vite-plugin-comlink": "^5.1.0"
}
}

View File

@ -0,0 +1,7 @@
type ExtractValues<T> = {
[K in keyof T]: T[K] extends { value: infer V }
? V
: T[K] extends object
? ExtractValues<T[K]>
: never;
};

View File

@ -47,9 +47,6 @@ html {
--neutral-800: #111111; --neutral-800: #111111;
--neutral-900: #060606; --neutral-900: #060606;
/* Secondary color */
--secondary-color: #6c757d;
--layer-0: var(--neutral-900); --layer-0: var(--neutral-900);
--layer-1: var(--neutral-500); --layer-1: var(--neutral-500);
--layer-2: var(--neutral-400); --layer-2: var(--neutral-400);

View File

@ -4,7 +4,7 @@
id?: string; id?: string;
} }
let { value = $bindable(), id = '' }: Props = $props(); let { value = $bindable(false), id = '' }: Props = $props();
$effect(() => { $effect(() => {
if (typeof value === 'string') { if (typeof value === 'string') {
value = value === 'true'; value = value === 'true';
@ -98,4 +98,3 @@
display: block; display: block;
} }
</style> </style>

View File

@ -25,4 +25,3 @@
border: none; border: none;
} }
</style> </style>