chore: make eslint happy
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m6s
🚀 Lint & Test & Deploy / quality (pull_request) Failing after 1m7s
🚀 Lint & Test & Deploy / test-unit (pull_request) Successful in 31s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Failing after 32s
🚀 Lint & Test & Deploy / deploy (pull_request) Has been skipped

This commit is contained in:
2026-05-04 14:50:11 +02:00
parent 2a54fa7590
commit e695c76490
11 changed files with 49 additions and 59 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
"dev": "vite dev", "dev": "vite dev",
"predev": "rm static/CHANGELOG.md && ln -s ../../CHANGELOG.md static/CHANGELOG.md", "predev": "rm static/CHANGELOG.md && ln -s ../../CHANGELOG.md static/CHANGELOG.md",
"build": "svelte-kit sync && vite build", "build": "svelte-kit sync && vite build",
"test:unit": "vitest", "test:unit": "vitest --browser=false",
"test": "npm run test:unit -- --run && npm run test:e2e", "test": "npm run test:unit -- --run && npm run test:e2e",
"test:e2e": "playwright test", "test:e2e": "playwright test",
"preview": "vite preview", "preview": "vite preview",
@@ -19,12 +19,12 @@ import EventEmitter from './helpers/EventEmitter';
import { HistoryManager } from './history-manager'; import { HistoryManager } from './history-manager';
const logger = createLogger('graph-manager'); const logger = createLogger('graph-manager');
// logger.mute(); logger.mute();
const remoteRegistry = new RemoteNodeRegistry(''); const remoteRegistry = new RemoteNodeRegistry('');
const clone = 'structuredClone' in self const clone = 'structuredClone' in globalThis
? self.structuredClone ? globalThis.structuredClone
: (args: unknown) => JSON.parse(JSON.stringify(args)); : (args: unknown) => JSON.parse(JSON.stringify(args));
function areSocketsCompatible( function areSocketsCompatible(
@@ -104,7 +104,7 @@ export class GraphManager extends EventEmitter<{
history: HistoryManager = new HistoryManager(); history: HistoryManager = new HistoryManager();
private serializeFullGraph(): Graph { public serializeFullGraph(): Graph {
if (this.graphStack.length === 0) return this.serialize(); if (this.graphStack.length === 0) return this.serialize();
let merged = this.serialize(); let merged = this.serialize();
for (let i = this.graphStack.length - 1; i >= 0; i--) { for (let i = this.graphStack.length - 1; i >= 0; i--) {
@@ -538,7 +538,7 @@ export class GraphManager extends EventEmitter<{
const inputs = { const inputs = {
'groupId': { 'groupId': {
type: 'select', type: 'select',
label: '', label: 'Group',
value: node.props?.groupId, value: node.props?.groupId,
internal: true, internal: true,
options: this.graph.groups.map((g, i) => ({ options: this.graph.groups.map((g, i) => ({
@@ -551,6 +551,10 @@ export class GraphManager extends EventEmitter<{
const groupType = { const groupType = {
...node.state.type, ...node.state.type,
meta: {
title: 'Group',
...node?.state?.type?.meta || {}
},
inputs, inputs,
outputs: groupDefinition?.outputs?.map(o => o.type) outputs: groupDefinition?.outputs?.map(o => o.type)
} as NodeDefinition; } as NodeDefinition;
@@ -620,7 +624,6 @@ export class GraphManager extends EventEmitter<{
} }
removeNode(node: NodeInstance, { restoreEdges = false } = {}) { removeNode(node: NodeInstance, { restoreEdges = false } = {}) {
console.log('REMOVING NODE', $state.snapshot({ node }));
const edgesToNode = this.edges.filter((edge) => edge[2].id === node.id); const edgesToNode = this.edges.filter((edge) => edge[2].id === node.id);
const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id); const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id);
for (const edge of [...edgesToNode, ...edgesFromNode]) { for (const edge of [...edgesToNode, ...edgesFromNode]) {
@@ -686,7 +689,7 @@ export class GraphManager extends EventEmitter<{
this.graphStack.push({ this.graphStack.push({
rootGraph: this.serialize(), rootGraph: this.serialize(),
savedNodes: new Map(this.nodes), savedNodes: new SvelteMap(this.nodes),
savedEdges: [...this.edges], savedEdges: [...this.edges],
outerGraph: this.graph, outerGraph: this.graph,
groupId, groupId,
@@ -743,8 +746,6 @@ export class GraphManager extends EventEmitter<{
...this.graph.groups.flatMap(g => g.nodes.map(n => n.id)) ...this.graph.groups.flatMap(g => g.nodes.map(n => n.id))
]; ];
console.log('CREATE NODE ID', ids);
let id = 0; let id = 0;
while (ids.includes(id)) { while (ids.includes(id)) {
id++; id++;
@@ -796,7 +797,7 @@ export class GraphManager extends EventEmitter<{
} }
removeUnusedGroups() { removeUnusedGroups() {
const usedGroups = new Set(this.getAllNodes().map(n => n.props?.groupId)); const usedGroups = new SvelteSet(this.getAllNodes().map(n => n.props?.groupId));
const unusedGroupAmount = this.graph.groups.length - usedGroups.size; const unusedGroupAmount = this.graph.groups.length - usedGroups.size;
this.graph.groups = this.graph.groups.filter(g => usedGroups.has(g.id)); this.graph.groups = this.graph.groups.filter(g => usedGroups.has(g.id));
this.save(); this.save();
@@ -808,14 +809,14 @@ export class GraphManager extends EventEmitter<{
this.removeUnusedGroups(); this.removeUnusedGroups();
const nodes = [ const nodes = [
...new Set(nodeIds).values().map(id => this.getNode(id)).filter(Boolean) ...new SvelteSet(nodeIds).values().map(id => this.getNode(id)).filter(Boolean)
] as NodeInstance[]; ] as NodeInstance[];
if (!nodes.length) return; if (!nodes.length) return;
logger.log(`Grouping ${nodes.length} nodes`, { nodes }); logger.log(`Grouping ${nodes.length} nodes`, { nodes });
const ids = new Set(nodes.map(n => n.id)); const ids = new SvelteSet(nodes.map(n => n.id));
// We use the map to dedupe when one external node is connected to multiple internal nodes // We use the map to dedupe when one external node is connected to multiple internal nodes
// ┌──internal_a // ┌──internal_a
@@ -823,14 +824,14 @@ export class GraphManager extends EventEmitter<{
// └──internal_b // └──internal_b
// This should only result in one group input not two // This should only result in one group input not two
const incomingEdges = this.edges.filter((edge) => ids.has(edge[2].id) && !ids.has(edge[0].id)); const incomingEdges = this.edges.filter((edge) => ids.has(edge[2].id) && !ids.has(edge[0].id));
const groupInputs = new Map<string, Edge>(); const groupInputs = new SvelteMap<string, Edge>();
for (const edge of incomingEdges) { for (const edge of incomingEdges) {
groupInputs.set(`${edge[0].id}-${edge[1]}`, edge); groupInputs.set(`${edge[0].id}-${edge[1]}`, edge);
} }
// And the same for the outputs // And the same for the outputs
const outgoingEdges = this.edges.filter((edge) => ids.has(edge[0].id) && !ids.has(edge[2].id)); const outgoingEdges = this.edges.filter((edge) => ids.has(edge[0].id) && !ids.has(edge[2].id));
const groupOutputs = new Map<string, Edge>(); const groupOutputs = new SvelteMap<string, Edge>();
for (const edge of outgoingEdges) { for (const edge of outgoingEdges) {
groupOutputs.set(`${edge[2].id}-${edge[3]}`, edge); groupOutputs.set(`${edge[2].id}-${edge[3]}`, edge);
} }
@@ -864,11 +865,11 @@ export class GraphManager extends EventEmitter<{
// Map from deduped edge source key → group input index, used for both // Map from deduped edge source key → group input index, used for both
// internal edge wiring and external edge socket naming. // internal edge wiring and external edge socket naming.
const inputIndexByEdgeKey = new Map<string, number>(); const inputIndexByEdgeKey = new SvelteMap<string, number>();
[...groupInputs.keys()].forEach((key, i) => inputIndexByEdgeKey.set(key, i)); [...groupInputs.keys()].forEach((key, i) => inputIndexByEdgeKey.set(key, i));
// Allocate all needed IDs up front so sequential calls never collide. // Allocate all needed IDs up front so sequential calls never collide.
const usedIds = new Set<number>([ const usedIds = new SvelteSet<number>([
...this.nodes.keys(), ...this.nodes.keys(),
...this.graph.groups.map(g => g.id), ...this.graph.groups.map(g => g.id),
...this.graph.groups.flatMap(g => g.nodes.map(n => n.id)) ...this.graph.groups.flatMap(g => g.nodes.map(n => n.id))
@@ -949,7 +950,6 @@ export class GraphManager extends EventEmitter<{
this.removeNode(node); this.removeNode(node);
} }
console.log('FINISHED', this.serialize());
this.saveUndoGroup(); this.saveUndoGroup();
return groupNode; return groupNode;
@@ -1,11 +1,7 @@
import { assert, beforeEach, describe, expect, it } from 'vitest'; import { assert, describe, expect, it } from 'vitest';
import { GraphManager } from './graph-manager.svelte'; import { GraphManager } from './graph-manager.svelte';
import { GraphState } from './graph-state.svelte'; import { GraphState } from './graph-state.svelte';
import { import { createMockNodeRegistry, mockFloatInputNode, mockFloatOutputNode } from './test-utils';
createMockNodeRegistry,
mockFloatInputNode,
mockFloatOutputNode
} from './test-utils';
// GraphState constructor reads localStorage synchronously — mock before any instantiation // GraphState constructor reads localStorage synchronously — mock before any instantiation
Object.defineProperty(globalThis, 'localStorage', { Object.defineProperty(globalThis, 'localStorage', {
+10 -13
View File
@@ -136,6 +136,12 @@
/> />
<label for="drop-zone"></label> <label for="drop-zone"></label>
{#if graph.isInsideGroup}
<button class="exit-group" onclick={() => graphState.exitGroupNode()}>
↑ Exit Group
</button>
{/if}
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}> <Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
<Camera <Camera
bind:camera={graphState.camera} bind:camera={graphState.camera}
@@ -169,14 +175,6 @@
{/if} {/if}
{#if graph.status === 'idle'} {#if graph.status === 'idle'}
{#if graph.isInsideGroup}
<HTML transform={false}>
<button class="exit-group" onclick={() => graphState.exitGroupNode()}>
↑ Exit Group
</button>
</HTML>
{/if}
{#if graphState.addMenuPosition} {#if graphState.addMenuPosition}
<AddMenu <AddMenu
onnode={handleNodeCreation} onnode={handleNodeCreation}
@@ -252,11 +250,10 @@
height: 100%; height: 100%;
} }
:global(.exit-group) { .exit-group {
position: fixed; position: absolute;
top: 12px; top: 12px;
left: 50%; left: 12px;
transform: translateX(-50%);
z-index: 1000; z-index: 1000;
padding: 4px 12px; padding: 4px 12px;
background: var(--color-layer-2); background: var(--color-layer-2);
@@ -268,7 +265,7 @@
opacity: 0.85; opacity: 0.85;
} }
:global(.exit-group:hover) { .exit-group:hover {
opacity: 1; opacity: 1;
} }
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createKeyMap } from '$lib/helpers/createKeyMap'; import { createKeyMap } from '$lib/helpers/createKeyMap';
import type { Graph, NodeInstance, NodeRegistry } from '@nodarium/types'; import type { Graph, NodeInstance, NodeRegistry } from '@nodarium/types';
import { onMount } from 'svelte';
import { GraphManager } from '../graph-manager.svelte'; import { GraphManager } from '../graph-manager.svelte';
import { GraphState, setGraphManager, setGraphState } from '../graph-state.svelte'; import { GraphState, setGraphManager, setGraphState } from '../graph-state.svelte';
import { setupKeymaps } from '../keymaps'; import { setupKeymaps } from '../keymaps';
@@ -1,4 +1,4 @@
import type { NodeDefinition, NodeInstance } from '@nodarium/types'; import type { NodeDefinition } from '@nodarium/types';
export function getParameterHeight(node: NodeDefinition, inputKey: string) { export function getParameterHeight(node: NodeDefinition, inputKey: string) {
const input = node.inputs?.[inputKey]; const input = node.inputs?.[inputKey];
@@ -71,7 +71,7 @@
{#if appSettings.value.debug.advancedMode} {#if appSettings.value.debug.advancedMode}
<span class="bg-white text-black! mr-2 px-1 rounded-sm opacity-30">{node.id}</span> <span class="bg-white text-black! mr-2 px-1 rounded-sm opacity-30">{node.id}</span>
{/if} {/if}
{node.type.split('/').pop()} {nodeType?.meta?.title || node.type?.split('/').pop()}
</div> </div>
<div <div
class="target" class="target"
+13 -13
View File
@@ -6,11 +6,22 @@ import type {
RuntimeExecutor, RuntimeExecutor,
SyncCache SyncCache
} from '@nodarium/types'; } from '@nodarium/types';
import {
concatEncodedArrays,
createLogger,
encodeFloat,
fastHashArrayBuffer,
type PerformanceStore
} from '@nodarium/utils';
import type { RuntimeNode } from './types';
const log = createLogger('runtime-executor');
log.mute();
export function expandGroups(graph: Graph): Graph { export function expandGroups(graph: Graph): Graph {
if (!graph.groups || graph.groups.length === 0) return graph; if (!graph.groups || graph.groups.length === 0) return graph;
let nodes = [...graph.nodes]; const nodes = [...graph.nodes];
let edges = [...graph.edges]; let edges = [...graph.edges];
let changed = true; let changed = true;
@@ -87,17 +98,6 @@ export function expandGroups(graph: Graph): Graph {
return { ...graph, nodes, edges }; return { ...graph, nodes, edges };
} }
import {
concatEncodedArrays,
createLogger,
encodeFloat,
fastHashArrayBuffer,
type PerformanceStore
} from '@nodarium/utils';
import type { RuntimeNode } from './types';
const log = createLogger('runtime-executor');
log.mute();
function getValue(input: NodeInput, value?: unknown) { function getValue(input: NodeInput, value?: unknown) {
if (value === undefined && 'value' in input) { if (value === undefined && 'value' in input) {
@@ -160,7 +160,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
const nonVirtualTypes = graph.nodes const nonVirtualTypes = graph.nodes
.map(node => node.type) .map(node => node.type)
.filter(t => !t.startsWith('__internal/')); .filter(t => !t.startsWith('__internal/'));
await this.registry.load(nonVirtualTypes as any); await this.registry.load(nonVirtualTypes);
const typeMap = new Map<string, NodeDefinition>(); const typeMap = new Map<string, NodeDefinition>();
for (const node of graph.nodes) { for (const node of graph.nodes) {
@@ -15,10 +15,7 @@
isGroupInstance ? manager.getGroup(node!.props?.groupId as number) : undefined isGroupInstance ? manager.getGroup(node!.props?.groupId as number) : undefined
); );
let groupName = $state(''); const groupName = $derived(activeGroup?.name ?? '');
$effect(() => {
groupName = activeGroup?.name ?? '';
});
function handleRename(e: Event) { function handleRename(e: Event) {
const name = (e.target as HTMLInputElement).value; const name = (e.target as HTMLInputElement).value;
+1 -1
View File
@@ -321,7 +321,7 @@
hidden={!appSettings.value.debug.advancedMode} hidden={!appSettings.value.debug.advancedMode}
icon="i-[tabler--code]" icon="i-[tabler--code]"
> >
<GraphSource graph={manager?.serialize()} /> <GraphSource graph={manager?.serializeFullGraph()} />
</Panel> </Panel>
<Panel <Panel
id="benchmark" id="benchmark"
+2 -1
View File
@@ -1,5 +1,6 @@
<script module> <script module>
const cache = new Map<string, Record<string, boolean>>(); import { SvelteMap } from 'svelte/reactivity';
const cache = new SvelteMap<string, Record<string, boolean>>();
function getStore(root: string): Record<string, boolean> { function getStore(root: string): Record<string, boolean> {
if (!cache.has(root)) { if (!cache.has(root)) {