chore: setup linting

This commit is contained in:
Max Richter
2026-02-02 16:22:14 +01:00
parent 137425b31b
commit 30e897468a
174 changed files with 6043 additions and 5107 deletions

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { T } from "@threlte/core";
import BackgroundVert from "./Background.vert";
import BackgroundFrag from "./Background.frag";
import { colors } from "../graph/colors.svelte";
import { appSettings } from "$lib/settings/app-settings.svelte";
import { T } from "@threlte/core";
import { colors } from "../graph/colors.svelte";
import BackgroundFrag from "./Background.frag";
import BackgroundVert from "./Background.vert";
type Props = {
minZoom: number;

View File

@@ -116,7 +116,7 @@
</div>
<div class="content">
{#each nodes as node}
{#each nodes as node (node.id)}
<div
class="result"
role="treeitem"

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { HTML } from "@threlte/extras";
import { HTML } from '@threlte/extras';
type Props = {
p1: { x: number; y: number };
@@ -10,7 +10,7 @@
const {
p1 = { x: 0, y: 0 },
p2 = { x: 0, y: 0 },
cameraPosition = [0, 1, 0],
cameraPosition = [0, 1, 0]
}: Props = $props();
const width = $derived(Math.abs(p1.x - p2.x) * cameraPosition[2]);
@@ -24,7 +24,8 @@
<div
class="box-selection"
style={`width: ${width}px; height: ${height}px;`}
></div>
>
</div>
</HTML>
<style>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { T } from "@threlte/core";
import { type OrthographicCamera } from "three";
import { T } from '@threlte/core';
import { type OrthographicCamera } from 'three';
type Props = {
camera: OrthographicCamera;
position: [number, number, number];

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { NodeDefinition, NodeRegistry } from "@nodarium/types";
import { onMount } from "svelte";
import type { NodeDefinition, NodeRegistry } from '@nodarium/types';
import { onMount } from 'svelte';
let mx = $state(0);
let my = $state(0);
@@ -20,15 +20,15 @@
my = ev.clientY;
if (!target) return;
const closest = target?.closest?.("[data-node-type]");
const closest = target?.closest?.('[data-node-type]');
if (!closest) {
node = undefined;
return;
}
let nodeType = closest.getAttribute("data-node-type");
let nodeInput = closest.getAttribute("data-node-input");
let nodeType = closest.getAttribute('data-node-type');
let nodeInput = closest.getAttribute('data-node-input');
if (!nodeType) {
node = undefined;
@@ -40,9 +40,9 @@
onMount(() => {
const style = wrapper.parentElement?.style;
style?.setProperty("cursor", "help");
style?.setProperty('cursor', 'help');
return () => {
style?.removeProperty("cursor");
style?.removeProperty('cursor');
};
});
</script>
@@ -53,12 +53,12 @@
class="help-wrapper p-4"
class:visible={node}
bind:clientWidth={width}
style="--my:{my}px; --mx:{Math.min(mx, window.innerWidth - width - 20)}px;"
style="--my: {my}px; --mx: {Math.min(mx, window.innerWidth - width - 20)}px"
bind:this={wrapper}
>
<p class="m-0 text-light opacity-40 flex items-center gap-3 mb-4">
<span class="i-tabler-help block w-4 h-4"></span>
{node?.id.split("/").at(-1) || "Help"}
{node?.id.split('/').at(-1) || 'Help'}
{#if input}
<span>> {input}</span>
{/if}
@@ -77,7 +77,7 @@
{#if !input}
<div>
<span class="i-tabler-arrow-right opacity-30">-></span>
{node?.outputs?.map((o) => o).join(", ") ?? "nothing"}
{node?.outputs?.map((o) => o).join(', ') ?? 'nothing'}
</div>
{/if}
{/if}

View File

@@ -1,11 +1,32 @@
<script lang="ts">
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { points, lines, rects } from "./store.js";
import { T } from "@threlte/core";
import { Color } from "three";
import type { Box } from '@nodarium/types';
import { T } from '@threlte/core';
import { MeshLineGeometry, MeshLineMaterial } from '@threlte/extras';
import { Color, Vector3 } from 'three';
import { lines, points, rects } from './store.js';
type Line = {
points: Vector3[];
color?: Color;
};
function getEachKey(value: Vector3 | Box | Line): string {
if ('x' in value) {
return [value.x, value.y, value.z].join('-');
}
if ('minX' in value) {
return [value.maxX, value.minX, value.maxY, value.minY].join('-');
}
if ('points' in value) {
return getEachKey(value.points[Math.floor(value.points.length / 2)]);
}
return '';
}
</script>
{#each $points as point}
{#each $points as point (getEachKey(point))}
<T.Mesh
position.x={point.x}
position.y={point.y}
@@ -17,7 +38,7 @@
</T.Mesh>
{/each}
{#each $rects as rect, i}
{#each $rects as rect, i (getEachKey(rect))}
<T.Mesh
position.x={(rect.minX + rect.maxX) / 2}
position.y={0}
@@ -32,11 +53,11 @@
</T.Mesh>
{/each}
{#each $lines as line}
{#each $lines as line (getEachKey(line))}
<T.Mesh position.y={1}>
<MeshLineGeometry points={line.points} />
<MeshLineMaterial
color={line.color || "red"}
color={line.color || 'red'}
linewidth={1}
attenuate={false}
/>

View File

@@ -1,16 +1,18 @@
<script module lang="ts">
import { colors } from "../graph/colors.svelte";
import { colors } from '../graph/colors.svelte';
const circleMaterial = new MeshBasicMaterial({
color: colors.edge.clone(),
toneMapped: false,
toneMapped: false
});
let lineColor = $state(colors.edge.clone().convertSRGBToLinear());
$effect.root(() => {
$effect(() => {
appSettings.value.theme;
if (appSettings.value.theme === undefined) {
return;
}
circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
lineColor = colors.edge.clone().convertSRGBToLinear();
});
@@ -20,19 +22,19 @@
new Vector2(0, 0),
new Vector2(0, 0),
new Vector2(0, 0),
new Vector2(0, 0),
new Vector2(0, 0)
);
</script>
<script lang="ts">
import { T } from "@threlte/core";
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { MeshBasicMaterial, Vector3 } from "three";
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
import { Vector2 } from "three/src/math/Vector2.js";
import { appSettings } from "$lib/settings/app-settings.svelte";
import { getGraphState } from "../graph-state.svelte";
import { onDestroy } from "svelte";
import { appSettings } from '$lib/settings/app-settings.svelte';
import { T } from '@threlte/core';
import { MeshLineGeometry, MeshLineMaterial } from '@threlte/extras';
import { onDestroy } from 'svelte';
import { MeshBasicMaterial, Vector3 } from 'three';
import { CubicBezierCurve } from 'three/src/extras/curves/CubicBezierCurve.js';
import { Vector2 } from 'three/src/math/Vector2.js';
import { getGraphState } from '../graph-state.svelte';
const graphState = getGraphState();
@@ -63,7 +65,7 @@
lastId = curveId;
const length = Math.floor(
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4
);
const samples = Math.max(length * 16, 10);
@@ -83,7 +85,7 @@
id,
x1,
y1,
$state.snapshot(points) as unknown as Vector3[],
$state.snapshot(points) as unknown as Vector3[]
);
}
}

View File

@@ -1,23 +1,23 @@
export const setXYZXYZ = (array: number[], location: number, x: number, y: number, z: number) => {
array[location + 0] = x
array[location + 1] = y
array[location + 2] = z
array[location + 0] = x;
array[location + 1] = y;
array[location + 2] = z;
array[location + 3] = x
array[location + 4] = y
array[location + 5] = z
}
array[location + 3] = x;
array[location + 4] = y;
array[location + 5] = z;
};
export const setXY = (array: number[], location: number, x: number, y: number) => {
array[location + 0] = x
array[location + 1] = y
}
array[location + 0] = x;
array[location + 1] = y;
};
export const setXYZ = (array: number[], location: number, x: number, y: number, z: number) => {
array[location + 0] = x
array[location + 1] = y
array[location + 2] = z
}
array[location + 0] = x;
array[location + 1] = y;
array[location + 2] = z;
};
export const setXYZW = (
array: number[],
@@ -27,8 +27,8 @@ export const setXYZW = (
z: number,
w: number
) => {
array[location + 0] = x
array[location + 1] = y
array[location + 2] = z
array[location + 3] = w
}
array[location + 0] = x;
array[location + 1] = y;
array[location + 2] = z;
array[location + 3] = w;
};

View File

@@ -1,5 +1,5 @@
import throttle from '$lib/helpers/throttle';
import { RemoteNodeRegistry } from '@nodarium/registry';
import { RemoteNodeRegistry } from '$lib/node-registry/index';
import type {
Edge,
Graph,
@@ -12,7 +12,7 @@ import type {
} from '@nodarium/types';
import { fastHashString } from '@nodarium/utils';
import { createLogger } from '@nodarium/utils';
import { SvelteMap } from 'svelte/reactivity';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import EventEmitter from './helpers/EventEmitter';
import { HistoryManager } from './history-manager';
@@ -23,7 +23,7 @@ const remoteRegistry = new RemoteNodeRegistry('');
const clone = 'structuredClone' in self
? self.structuredClone
: (args: any) => JSON.parse(JSON.stringify(args));
: (args: unknown) => JSON.parse(JSON.stringify(args));
function areSocketsCompatible(
output: string | undefined,
@@ -57,7 +57,7 @@ function areEdgesEqual(firstEdge: Edge, secondEdge: Edge) {
export class GraphManager extends EventEmitter<{
save: Graph;
result: any;
result: unknown;
settings: {
types: Record<string, NodeInput>;
values: Record<string, unknown>;
@@ -79,7 +79,7 @@ export class GraphManager extends EventEmitter<{
currentUndoGroup: number | null = null;
inputSockets = $derived.by(() => {
const s = new Set<string>();
const s = new SvelteSet<string>();
for (const edge of this.edges) {
s.add(`${edge[2].id}-${edge[3]}`);
}
@@ -122,7 +122,7 @@ export class GraphManager extends EventEmitter<{
private lastSettingsHash = 0;
setSettings(settings: Record<string, unknown>) {
let hash = fastHashString(JSON.stringify(settings));
const hash = fastHashString(JSON.stringify(settings));
if (hash === this.lastSettingsHash) return;
this.lastSettingsHash = hash;
@@ -136,7 +136,7 @@ export class GraphManager extends EventEmitter<{
}
getLinkedNodes(node: NodeInstance) {
const nodes = new Set<NodeInstance>();
const nodes = new SvelteSet<NodeInstance>();
const stack = [node];
while (stack.length) {
const n = stack.pop();
@@ -171,7 +171,7 @@ export class GraphManager extends EventEmitter<{
const targetInput = toNode.state?.type?.inputs?.[toSocketKey];
const targetAcceptedTypes = [targetInput?.type, ...(targetInput?.accepts || [])];
const bestInputEntry = draggedInputs.find(([_, input]) => {
const bestInputEntry = draggedInputs.find(([, input]) => {
const accepted = [input.type, ...(input.accepts || [])];
return areSocketsCompatible(edgeOutputSocketType, accepted);
});
@@ -209,7 +209,7 @@ export class GraphManager extends EventEmitter<{
const draggedOutputs = draggedNode.state.type.outputs ?? [];
// Optimization: Pre-calculate parents to avoid cycles
const parentIds = new Set(this.getParentsOfNode(draggedNode).map(n => n.id));
const parentIds = new SvelteSet(this.getParentsOfNode(draggedNode).map(n => n.id));
return this.edges.filter((edge) => {
const [fromNode, fromSocketIdx, toNode, toSocketKey] = edge;
@@ -266,7 +266,7 @@ export class GraphManager extends EventEmitter<{
}
private _init(graph: Graph) {
const nodes = new Map(
const nodes = new SvelteMap(
graph.nodes.map((node) => {
const nodeType = this.registry.getNode(node.type);
const n = node as NodeInstance;
@@ -310,11 +310,11 @@ export class GraphManager extends EventEmitter<{
logger.info('loading graph', { nodes: graph.nodes, edges: graph.edges, id: graph.id });
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
const nodeIds = Array.from(new SvelteSet([...graph.nodes.map((n) => n.type)]));
await this.registry.load(nodeIds);
// Fetch all nodes from all collections of the loaded nodes
const allCollections = new Set<`${string}/${string}`>();
const allCollections = new SvelteSet<`${string}/${string}`>();
for (const id of nodeIds) {
const [user, collection] = id.split('/');
allCollections.add(`${user}/${collection}`);
@@ -354,7 +354,7 @@ export class GraphManager extends EventEmitter<{
for (const type of types) {
if (type.inputs) {
for (const key in type.inputs) {
let settingId = type.inputs[key].setting;
const settingId = type.inputs[key].setting;
if (settingId) {
settingTypes[settingId] = {
__node_type: type.id,
@@ -409,7 +409,7 @@ export class GraphManager extends EventEmitter<{
const settingValues = this.settings;
if (nodeType.inputs) {
for (const key in nodeType.inputs) {
let settingId = nodeType.inputs[key].setting;
const settingId = nodeType.inputs[key].setting;
if (settingId) {
settingTypes[settingId] = nodeType.inputs[key];
if (
@@ -507,7 +507,7 @@ export class GraphManager extends EventEmitter<{
createGraph(nodes: NodeInstance[], edges: [number, number, number, string][]) {
// map old ids to new ids
const idMap = new Map<number, number>();
const idMap = new SvelteMap<number, number>();
let startId = this.createNodeId();
@@ -731,7 +731,7 @@ export class GraphManager extends EventEmitter<{
// if index is a string, we are an input looking for outputs
if (typeof index === 'string') {
// filter out self and child nodes
const children = new Set(this.getChildren(node).map((n) => n.id));
const children = new SvelteSet(this.getChildren(node).map((n) => n.id));
const nodes = this.getAllNodes().filter(
(n) => n.id !== node.id && !children.has(n.id)
);
@@ -752,13 +752,13 @@ export class GraphManager extends EventEmitter<{
// if index is a number, we are an output looking for inputs
// filter out self and parent nodes
const parents = new Set(this.getParentsOfNode(node).map((n) => n.id));
const parents = new SvelteSet(this.getParentsOfNode(node).map((n) => n.id));
const nodes = this.getAllNodes().filter(
(n) => n.id !== node.id && !parents.has(n.id)
);
// get edges from this socket
const edges = new Map(
const edges = new SvelteMap(
this.getEdgesFromNode(node)
.filter((e) => e[1] === index)
.map((e) => [e[2].id, e[3]])

View File

@@ -1,6 +1,6 @@
import type { NodeInstance, Socket } from '@nodarium/types';
import { getContext, setContext } from 'svelte';
import { SvelteSet } from 'svelte/reactivity';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import type { OrthographicCamera, Vector3 } from 'three';
import type { GraphManager } from './graph-manager.svelte';
@@ -54,7 +54,7 @@ export class GraphState {
height = $state(100);
hoveredEdgeId = $state<string | null>(null);
edges = new Map<string, EdgeData>();
edges = new SvelteMap<string, EdgeData>();
wrapper = $state<HTMLDivElement>(null!);
rect: DOMRect = $derived(
@@ -100,7 +100,7 @@ export class GraphState {
hoveredSocket = $state<Socket | null>(null);
possibleSockets = $state<Socket[]>([]);
possibleSocketIds = $derived(
new Set(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`))
new SvelteSet(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`))
);
getEdges() {
@@ -155,7 +155,6 @@ export class GraphState {
return 4;
} else if (z > 11) {
return 2;
} else {
}
return 1;
}
@@ -193,7 +192,7 @@ export class GraphState {
(p) =>
p !== 'seed'
&& node?.inputs
&& !('setting' in node?.inputs?.[p])
&& !(node?.inputs?.[p] !== undefined && 'setting' in node.inputs[p])
&& node.inputs[p].hidden !== true
).length;
this.nodeHeightCache[nodeTypeId] = height;
@@ -294,8 +293,8 @@ export class GraphState {
getNodeIdFromEvent(event: MouseEvent) {
let clickedNodeId = -1;
let mx = event.clientX - this.rect.x;
let my = event.clientY - this.rect.y;
const mx = event.clientX - this.rect.x;
const my = event.clientY - this.rect.y;
if (event.button === 0) {
// check if the clicked element is a node

View File

@@ -74,7 +74,7 @@
const output = newNode.state?.type?.outputs?.find((out) => {
if (socketType?.type === out) return true;
if (socketType?.accepts?.includes(out as any)) return true;
if ((socketType?.accepts as string[])?.includes(out)) return true;
return false;
});
@@ -172,7 +172,7 @@
/>
{/if}
{#each graph.edges as edge}
{#each graph.edges as edge (edge)}
{@const [x1, y1, x2, y2] = getEdgePosition(edge)}
<EdgeEl
id={graph.getEdgeId(edge)}

View File

@@ -1,29 +1,25 @@
<script lang="ts">
import type { Graph, NodeInstance, NodeRegistry } from "@nodarium/types";
import GraphEl from "./Graph.svelte";
import { GraphManager } from "../graph-manager.svelte";
import { createKeyMap } from "$lib/helpers/createKeyMap";
import {
GraphState,
setGraphManager,
setGraphState,
} from "../graph-state.svelte";
import { setupKeymaps } from "../keymaps";
import { createKeyMap } from '$lib/helpers/createKeyMap';
import type { Graph, NodeInstance, NodeRegistry } from '@nodarium/types';
import { GraphManager } from '../graph-manager.svelte';
import { GraphState, setGraphManager, setGraphState } from '../graph-state.svelte';
import { setupKeymaps } from '../keymaps';
import GraphEl from './Graph.svelte';
type Props = {
graph?: Graph;
registry: NodeRegistry;
settings?: Record<string, any>;
settings?: Record<string, unknown>;
activeNode?: NodeInstance;
showGrid?: boolean;
snapToGrid?: boolean;
showHelp?: boolean;
settingTypes?: Record<string, any>;
settingTypes?: Record<string, unknown>;
onsave?: (save: Graph) => void;
onresult?: (result: any) => void;
onresult?: (result: unknown) => void;
};
let {
@@ -36,11 +32,12 @@
showHelp = $bindable(false),
settingTypes = $bindable(),
onsave,
onresult,
onresult
}: Props = $props();
export const keymap = createKeyMap([]);
// svelte-ignore state_referenced_locally
export const manager = new GraphManager(registry);
setGraphManager(manager);
@@ -70,14 +67,14 @@
}
});
manager.on("settings", (_settings) => {
manager.on('settings', (_settings) => {
settingTypes = { ...settingTypes, ..._settings.types };
settings = _settings.values;
});
manager.on("result", (result) => onresult?.(result));
manager.on('result', (result) => onresult?.(result));
manager.on("save", (save) => onsave?.(save));
manager.on('save', (save) => onsave?.(save));
$effect(() => {
if (graph) {

View File

@@ -1,30 +1,30 @@
import { appSettings } from "$lib/settings/app-settings.svelte";
import { Color, LinearSRGBColorSpace } from "three";
import { appSettings } from '$lib/settings/app-settings.svelte';
import { Color, LinearSRGBColorSpace } from 'three';
const variables = [
"layer-0",
"layer-1",
"layer-2",
"layer-3",
"outline",
"active",
"selected",
"edge",
'layer-0',
'layer-1',
'layer-2',
'layer-3',
'outline',
'active',
'selected',
'edge'
] as const;
function getColor(variable: (typeof variables)[number]) {
const style = getComputedStyle(document.body.parentElement!);
let color = style.getPropertyValue(`--${variable}`);
const color = style.getPropertyValue(`--${variable}`);
return new Color().setStyle(color, LinearSRGBColorSpace);
}
export const colors = Object.fromEntries(
variables.map((v) => [v, getColor(v)]),
variables.map((v) => [v, getColor(v)])
) as Record<(typeof variables)[number], Color>;
$effect.root(() => {
$effect(() => {
if (!appSettings.value.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}`);

View File

@@ -6,7 +6,7 @@ export class FileDropEventManager {
constructor(
private graph: GraphManager,
private state: GraphState
) { }
) {}
handleFileDrop(event: DragEvent) {
event.preventDefault();
@@ -17,19 +17,21 @@ export class FileDropEventManager {
let my = event.clientY - this.state.rect.y;
if (nodeId) {
let nodeOffsetX = event.dataTransfer.getData('data/node-offset-x');
let nodeOffsetY = event.dataTransfer.getData('data/node-offset-y');
const nodeOffsetX = event.dataTransfer.getData('data/node-offset-x');
const nodeOffsetY = event.dataTransfer.getData('data/node-offset-y');
if (nodeOffsetX && nodeOffsetY) {
mx += parseInt(nodeOffsetX);
my += parseInt(nodeOffsetY);
}
let props = {};
let rawNodeProps = event.dataTransfer.getData('data/node-props');
const rawNodeProps = event.dataTransfer.getData('data/node-props');
if (rawNodeProps) {
try {
props = JSON.parse(rawNodeProps);
} catch (e) { }
} catch (e) {
console.error('Failed to parse node dropped', e);
}
}
const pos = this.state.projectScreenToWorld(mx, my);
@@ -48,7 +50,7 @@ export class FileDropEventManager {
reader.onload = async (e) => {
const buffer = e.target?.result;
if (buffer?.constructor === ArrayBuffer) {
const nodeType = await this.graph.registry.register(buffer);
const nodeType = await this.graph.registry.register(nodeId, buffer);
this.graph.createNode({
type: nodeType.id,

View File

@@ -7,7 +7,7 @@ export class EdgeInteractionManager {
constructor(
private graph: GraphManager,
private state: GraphState
) { }
) {}
private MIN_DISTANCE = 3;
@@ -85,7 +85,14 @@ export class EdgeInteractionManager {
const pointAy = edge.points[i].z + edge.y1;
const pointBx = edge.points[i + DENSITY].x + edge.x1;
const pointBy = edge.points[i + DENSITY].z + edge.y1;
const distance = distanceFromPointToSegment(pointAx, pointAy, pointBx, pointBy, mouseX, mouseY);
const distance = distanceFromPointToSegment(
pointAx,
pointAy,
pointBx,
pointBy,
mouseX,
mouseY
);
if (distance < this.MIN_DISTANCE) {
if (distance < hoveredEdgeDistance) {
hoveredEdgeDistance = distance;

View File

@@ -177,8 +177,8 @@ export class MouseEventManager {
}
}
let mx = event.clientX - this.state.rect.x;
let my = event.clientY - this.state.rect.y;
const mx = event.clientX - this.state.rect.x;
const my = event.clientY - this.state.rect.y;
this.state.mouseDown = [mx, my];
this.state.cameraDown[0] = this.state.cameraPosition[0];
@@ -242,8 +242,8 @@ export class MouseEventManager {
}
handleWindowMouseMove(event: MouseEvent) {
let mx = event.clientX - this.state.rect.x;
let my = event.clientY - this.state.rect.y;
const mx = event.clientX - this.state.rect.x;
const my = event.clientY - this.state.rect.y;
this.state.mousePosition = this.state.projectScreenToWorld(mx, my);
this.state.hoveredNodeId = this.state.getNodeIdFromEvent(event);
@@ -352,9 +352,9 @@ export class MouseEventManager {
// here we are handling panning of camera
this.state.isPanning = true;
let newX = this.state.cameraDown[0]
const newX = this.state.cameraDown[0]
- (mx - this.state.mouseDown[0]) / this.state.cameraPosition[2];
let newY = this.state.cameraDown[1]
const newY = this.state.cameraDown[1]
- (my - this.state.mouseDown[1]) / this.state.cameraPosition[2];
this.state.cameraPosition[0] = newX;
@@ -392,6 +392,7 @@ export class MouseEventManager {
/ zoomRatio;
this.state.cameraPosition[1] = this.state.mousePosition[1]
- (this.state.mousePosition[1] - this.state.cameraPosition[1])
/ zoomRatio, this.state.cameraPosition[2] = newZoom;
/ zoomRatio;
this.state.cameraPosition[2] = newZoom;
}
}

View File

@@ -1,11 +1,11 @@
import throttle from "$lib/helpers/throttle";
import throttle from '$lib/helpers/throttle';
type EventMap = Record<string, unknown>;
type EventKey<T extends EventMap> = string & keyof T;
type EventReceiver<T> = (params: T, stuff?: Record<string, unknown>) => unknown;
export default class EventEmitter<
T extends EventMap = { [key: string]: unknown },
T extends EventMap = { [key: string]: unknown }
> {
index = 0;
public eventMap: T = {} as T;
@@ -32,11 +32,11 @@ export default class EventEmitter<
public on<K extends EventKey<T>>(
event: K,
cb: EventReceiver<T[K]>,
throttleTimer = 0,
throttleTimer = 0
) {
if (throttleTimer > 0) cb = throttle(cb, throttleTimer);
const cbs = Object.assign(this.cbs, {
[event]: [...(this.cbs[event] || []), cb],
[event]: [...(this.cbs[event] || []), cb]
});
this.cbs = cbs;
@@ -54,10 +54,10 @@ export default class EventEmitter<
*/
public once<K extends EventKey<T>>(
event: K,
cb: EventReceiver<T[K]>,
cb: EventReceiver<T[K]>
): () => void {
const cbsOnce = Object.assign(this.cbsOnce, {
[event]: [...(this.cbsOnce[event] || []), cb],
[event]: [...(this.cbsOnce[event] || []), cb]
});
this.cbsOnce = cbsOnce;

View File

@@ -36,7 +36,8 @@ export function createNodePath({
aspectRatio = 1
} = {}) {
return `M0,${cornerTop}
${cornerTop
${
cornerTop
? ` V${cornerTop}
Q0,0 ${cornerTop * aspectRatio},0
H${100 - cornerTop * aspectRatio}
@@ -45,40 +46,37 @@ export function createNodePath({
: ` V0
H100
`
}
}
V${y - height / 2}
${rightBump
${
rightBump
? ` C${100 - depth},${y - height / 2} ${100 - depth},${y + height / 2} 100,${y + height / 2}`
: ` H100`
}
${cornerBottom
}
${
cornerBottom
? ` V${100 - cornerBottom}
Q100,100 ${100 - cornerBottom * aspectRatio},100
H${cornerBottom * aspectRatio}
Q0,100 0,${100 - cornerBottom}
`
: `${leftBump ? `V100 H0` : `V100`}`
}
${leftBump
? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${y - height / 2}`
}
${
leftBump
? ` V${y + height / 2} C${depth},${y + height / 2} ${depth},${y - height / 2} 0,${
y - height / 2
}`
: ` H0`
}
}
Z`.replace(/\s+/g, ' ');
}
export const debounce = (fn: Function, ms = 300) => {
let timeoutId: ReturnType<typeof setTimeout>;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
};
export const clone: <T>(v: T) => T = 'structedClone' in globalThis
? globalThis.structuredClone
: (obj) => JSON.parse(JSON.stringify(obj));
export function withSubComponents<A, B extends Record<string, any>>(
export function withSubComponents<A, B extends Record<string, unknown>>(
component: A,
subcomponents: B
): A & B {

View File

@@ -1,15 +1,14 @@
import { writable, type Writable } from "svelte/store";
import { type Writable, writable } from 'svelte/store';
function isStore(v: unknown): v is Writable<unknown> {
return v !== null && typeof v === "object" && "subscribe" in v && "set" in v;
return v !== null && typeof v === 'object' && 'subscribe' in v && 'set' in v;
}
const storeIds: Map<string, ReturnType<typeof createLocalStore>> = new Map();
const HAS_LOCALSTORAGE = "localStorage" in globalThis;
const HAS_LOCALSTORAGE = 'localStorage' in globalThis;
function createLocalStore<T>(key: string, initialValue: T | Writable<T>) {
let store: Writable<T>;
if (HAS_LOCALSTORAGE) {
@@ -36,18 +35,15 @@ function createLocalStore<T>(key: string, initialValue: T | Writable<T>) {
subscribe: store.subscribe,
set: store.set,
update: store.update
}
};
}
export default function localStore<T>(key: string, initialValue: T | Writable<T>): Writable<T> {
if (storeIds.has(key)) return storeIds.get(key) as Writable<T>;
const store = createLocalStore(key, initialValue)
const store = createLocalStore(key, initialValue);
storeIds.set(key, store);
return store
return store;
}

View File

@@ -1,21 +1,21 @@
import { create, type Delta } from "jsondiffpatch";
import type { Graph } from "@nodarium/types";
import { clone } from "./helpers/index.js";
import { createLogger } from "@nodarium/utils";
import type { Graph } from '@nodarium/types';
import { createLogger } from '@nodarium/utils';
import { create, type Delta } from 'jsondiffpatch';
import { clone } from './helpers/index.js';
const diff = create({
objectHash: function (obj, index) {
objectHash: function(obj, index) {
if (obj === null) return obj;
if ("id" in obj) return obj.id as string;
if ("_id" in obj) return obj._id as string;
if ('id' in obj) return obj.id as string;
if ('_id' in obj) return obj._id as string;
if (Array.isArray(obj)) {
return obj.join("-");
return obj.join('-');
}
return "$$index:" + index;
},
return '$$index:' + index;
}
});
const log = createLogger("history");
const log = createLogger('history');
log.mute();
export class HistoryManager {
@@ -26,7 +26,7 @@ export class HistoryManager {
private opts = {
debounce: 400,
maxHistory: 100,
maxHistory: 100
};
constructor({ maxHistory = 100, debounce = 100 } = {}) {
@@ -40,12 +40,12 @@ export class HistoryManager {
if (!this.state) {
this.state = clone(state);
this.initialState = this.state;
log.log("initial state saved");
log.log('initial state saved');
} else {
const newState = state;
const delta = diff.diff(this.state, newState);
if (delta) {
log.log("saving state");
log.log('saving state');
// Add the delta to history
if (this.index < this.history.length - 1) {
// Clear the history after the current index if new changes are made
@@ -61,7 +61,7 @@ export class HistoryManager {
}
this.state = newState;
} else {
log.log("no changes");
log.log('no changes');
}
}
}
@@ -75,7 +75,7 @@ export class HistoryManager {
undo() {
if (this.index === -1 && this.initialState) {
log.log("reached start, loading initial state");
log.log('reached start, loading initial state');
return clone(this.initialState);
} else {
const delta = this.history[this.index];
@@ -95,7 +95,7 @@ export class HistoryManager {
this.state = nextState;
return clone(nextState);
} else {
log.log("reached end");
log.log('reached end');
}
}
}

View File

@@ -1,13 +1,13 @@
<script lang="ts">
import type { NodeInstance } from "@nodarium/types";
import { getGraphState } from "../graph-state.svelte";
import { T } from "@threlte/core";
import { type Mesh } from "three";
import NodeFrag from "./Node.frag";
import NodeVert from "./Node.vert";
import NodeHtml from "./NodeHTML.svelte";
import { colors } from "../graph/colors.svelte";
import { appSettings } from "$lib/settings/app-settings.svelte";
import { appSettings } from '$lib/settings/app-settings.svelte';
import type { NodeInstance } from '@nodarium/types';
import { T } from '@threlte/core';
import { type Mesh } from 'three';
import { getGraphState } from '../graph-state.svelte';
import { colors } from '../graph/colors.svelte';
import NodeFrag from './Node.frag';
import NodeVert from './Node.vert';
import NodeHtml from './NodeHTML.svelte';
const graphState = getGraphState();
@@ -21,12 +21,12 @@
const isActive = $derived(graphState.activeNodeId === node.id);
const isSelected = $derived(graphState.selectedNodes.has(node.id));
const strokeColor = $derived(
appSettings.value.theme &&
(isSelected
appSettings.value.theme
&& (isSelected
? colors.selected
: isActive
? colors.active
: colors.outline),
? colors.active
: colors.outline)
);
let meshRef: Mesh | undefined = $state();
@@ -55,12 +55,12 @@
fragmentShader={NodeFrag}
transparent
uniforms={{
uColorBright: { value: colors["layer-2"] },
uColorDark: { value: colors["layer-1"] },
uColorBright: { value: colors['layer-2'] },
uColorDark: { value: colors['layer-1'] },
uStrokeColor: { value: colors.outline.clone() },
uStrokeWidth: { value: 1.0 },
uWidth: { value: 20 },
uHeight: { value: height },
uHeight: { value: height }
}}
uniforms.uStrokeColor.value={strokeColor.clone()}
uniforms.uStrokeWidth.value={(7 - z) / 3}

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import type { NodeInstance } from "@nodarium/types";
import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte";
import { getGraphState } from "../graph-state.svelte";
import type { NodeInstance } from '@nodarium/types';
import { getGraphState } from '../graph-state.svelte';
import NodeHeader from './NodeHeader.svelte';
import NodeParameter from './NodeParameter.svelte';
let ref: HTMLDivElement;
@@ -10,7 +10,7 @@
type Props = {
node: NodeInstance;
position?: "absolute" | "fixed" | "relative";
position?: 'absolute' | 'fixed' | 'relative';
isActive?: boolean;
isSelected?: boolean;
inView?: boolean;
@@ -19,11 +19,11 @@
let {
node = $bindable(),
position = "absolute",
position = 'absolute',
isActive = false,
isSelected = false,
inView = true,
z = 2,
z = 2
}: Props = $props();
// If we dont have a random offset, all nodes becom visible at the same zoom level -> stuttering
@@ -31,12 +31,11 @@
const zLimit = 2 - zOffset;
const parameters = Object.entries(node.state?.type?.inputs || {}).filter(
(p) =>
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
(p) => p[1].type !== 'seed' && !('setting' in p[1]) && p[1]?.hidden !== true
);
$effect(() => {
if ("state" in node && !node.state.ref) {
if ('state' in node && !node.state.ref) {
node.state.ref = ref;
graphState?.updateNodePosition(node);
}
@@ -47,7 +46,7 @@
class="node {position}"
class:active={isActive}
style:--cz={z + zOffset}
style:display={inView && z > zLimit ? "block" : "none"}
style:display={inView && z > zLimit ? 'block' : 'none'}
class:selected={isSelected}
class:out-of-view={!inView}
data-node-id={node.id}
@@ -56,7 +55,7 @@
>
<NodeHeader {node} />
{#each parameters as [key, value], i}
{#each parameters as [key, value], i (key)}
<NodeParameter
bind:node
id={key}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { getGraphState } from "../graph-state.svelte";
import { createNodePath } from "../helpers/index.js";
import type { NodeInstance } from "@nodarium/types";
import type { NodeInstance } from '@nodarium/types';
import { getGraphState } from '../graph-state.svelte';
import { createNodePath } from '../helpers/index.js';
const graphState = getGraphState();
@@ -10,47 +10,52 @@
function handleMouseDown(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
if ("state" in node) {
if ('state' in node) {
graphState.setDownSocket?.({
node,
index: 0,
position: graphState.getSocketPosition?.(node, 0),
position: graphState.getSocketPosition?.(node, 0)
});
}
}
const cornerTop = 10;
const rightBump = !!node?.state?.type?.outputs?.length;
const rightBump = $derived(!!node?.state?.type?.outputs?.length);
const aspectRatio = 0.25;
const path = createNodePath({
depth: 5.5,
height: 34,
y: 49,
cornerTop,
rightBump,
aspectRatio,
});
const pathHover = createNodePath({
depth: 8.5,
height: 50,
y: 49,
cornerTop,
rightBump,
aspectRatio,
});
const path = $derived(
createNodePath({
depth: 5.5,
height: 34,
y: 49,
cornerTop,
rightBump,
aspectRatio
})
);
const pathHover = $derived(
createNodePath({
depth: 8.5,
height: 50,
y: 49,
cornerTop,
rightBump,
aspectRatio
})
);
</script>
<div class="wrapper" data-node-id={node.id} data-node-type={node.type}>
<div class="content">
{node.type.split("/").pop()}
{node.type.split('/').pop()}
</div>
<div
class="click-target"
role="button"
tabindex="0"
onmousedown={handleMouseDown}
></div>
>
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
@@ -62,8 +67,7 @@
--hover-path: path("${pathHover}");
`}
>
<path vector-effect="non-scaling-stroke" stroke="white" stroke-width="0.1"
></path>
<path vector-effect="non-scaling-stroke" stroke="white" stroke-width="0.1"></path>
</svg>
</div>
@@ -104,9 +108,7 @@
svg path {
stroke-width: 0.2px;
transition:
d 0.3s ease,
fill 0.3s ease;
transition: d 0.3s ease, fill 0.3s ease;
fill: var(--layer-2);
stroke: var(--stroke);
stroke-width: var(--stroke-width);

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type { NodeInstance, NodeInput } from "@nodarium/types";
import { Input } from "@nodarium/ui";
import type { GraphManager } from "../graph-manager.svelte";
import type { NodeInput, NodeInstance } from '@nodarium/types';
import { Input } from '@nodarium/ui';
import type { GraphManager } from '../graph-manager.svelte';
type Props = {
node: NodeInstance;
@@ -16,17 +16,18 @@
input,
id,
elementId = `input-${Math.random().toString(36).substring(7)}`,
graph,
graph
}: Props = $props();
function getDefaultValue() {
if (node?.props?.[id] !== undefined) return node?.props?.[id] as number;
if ("value" in input && input?.value !== undefined)
if ('value' in input && input?.value !== undefined) {
return input?.value as number;
if (input.type === "boolean") return 0;
if (input.type === "float") return 0.5;
if (input.type === "integer") return 0;
if (input.type === "select") return 0;
}
if (input.type === 'boolean') return 0;
if (input.type === 'float') return 0.5;
if (input.type === 'integer') return 0;
if (input.type === 'select') return 0;
return 0;
}

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import type { NodeInput, NodeInstance } from "@nodarium/types";
import { createNodePath } from "../helpers";
import NodeInputEl from "./NodeInput.svelte";
import { getGraphManager, getGraphState } from "../graph-state.svelte";
import type { NodeInput, NodeInstance } from '@nodarium/types';
import { getGraphManager, getGraphState } from '../graph-state.svelte';
import { createNodePath } from '../helpers';
import NodeInputEl from './NodeInput.svelte';
type Props = {
node: NodeInstance;
@@ -15,9 +15,9 @@
let { node = $bindable(), input, id, isLast }: Props = $props();
const inputType = node?.state?.type?.inputs?.[id]!;
const inputType = $derived(node?.state?.type?.inputs?.[id]);
const socketId = `${node.id}-${id}`;
const socketId = $derived(`${node.id}-${id}`);
const graphState = getGraphState();
const graphId = graph?.id;
@@ -30,38 +30,44 @@
graphState.setDownSocket({
node,
index: id,
position: graphState.getSocketPosition?.(node, id),
position: graphState.getSocketPosition?.(node, id)
});
}
const leftBump = node.state?.type?.inputs?.[id].internal !== true;
const cornerBottom = isLast ? 5 : 0;
const leftBump = $derived(node.state?.type?.inputs?.[id].internal !== true);
const cornerBottom = $derived(isLast ? 5 : 0);
const aspectRatio = 0.5;
const path = createNodePath({
depth: 7,
height: 20,
y: 50.5,
cornerBottom,
leftBump,
aspectRatio,
});
const pathDisabled = createNodePath({
depth: 6,
height: 18,
y: 50.5,
cornerBottom,
leftBump,
aspectRatio,
});
const pathHover = createNodePath({
depth: 8,
height: 25,
y: 50.5,
cornerBottom,
leftBump,
aspectRatio,
});
const path = $derived(
createNodePath({
depth: 7,
height: 20,
y: 50.5,
cornerBottom,
leftBump,
aspectRatio
})
);
const pathDisabled = $derived(
createNodePath({
depth: 6,
height: 18,
y: 50.5,
cornerBottom,
leftBump,
aspectRatio
})
);
const pathHover = $derived(
createNodePath({
depth: 8,
height: 25,
y: 50.5,
cornerBottom,
leftBump,
aspectRatio
})
);
</script>
<div
@@ -72,16 +78,14 @@
>
{#key id && graphId}
<div class="content" class:disabled={graph?.inputSockets?.has(socketId)}>
{#if inputType.label !== ""}
<label for={elementId} title={input.description}
>{input.label || id}</label
>
{#if inputType?.label !== ''}
<label for={elementId} title={input.description}>{input.label || id}</label>
{/if}
<span
class="absolute i-[tabler--help-circle] size-4 block top-2 right-2 opacity-30"
title={JSON.stringify(input, null, 2)}
></span>
{#if inputType.external !== true}
{#if inputType?.external !== true}
<NodeInputEl {graph} {elementId} bind:node {input} {id} />
{/if}
</div>
@@ -94,7 +98,8 @@
onmousedown={handleMouseDown}
role="button"
tabindex="0"
></div>
>
</div>
{/if}
{/key}
@@ -169,9 +174,7 @@
}
svg path {
transition:
d 0.3s ease,
fill 0.3s ease;
transition: d 0.3s ease, fill 0.3s ease;
fill: var(--layer-1);
stroke: var(--stroke);
stroke-width: var(--stroke-width);