chore: move some more components to svelte 5
Some checks failed
Deploy to GitHub Pages / build_site (push) Has been cancelled
Some checks failed
Deploy to GitHub Pages / build_site (push) Has been cancelled
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { GraphManager } from "./graph-manager.js";
|
import type { GraphManager } from "./graph-manager.svelte";
|
||||||
import { HTML } from "@threlte/extras";
|
import { HTML } from "@threlte/extras";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type { NodeType } from "@nodes/types";
|
import type { NodeType } from "@nodes/types";
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement;
|
||||||
let value: string = "";
|
let value: string = "";
|
||||||
let activeNodeId: NodeType = "";
|
let activeNodeId: NodeType | undefined = undefined;
|
||||||
|
|
||||||
const allNodes = graph.getNodeDefinitions();
|
const allNodes = graph.getNodeDefinitions();
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
$: nodes = value === "" ? allNodes : filterNodes();
|
$: nodes = value === "" ? allNodes : filterNodes();
|
||||||
$: if (nodes) {
|
$: if (nodes) {
|
||||||
if (activeNodeId === "") {
|
if (activeNodeId === undefined) {
|
||||||
activeNodeId = nodes[0].id;
|
activeNodeId = nodes[0].id;
|
||||||
} else if (nodes.length) {
|
} else if (nodes.length) {
|
||||||
const node = nodes.find((node) => node.id === activeNodeId);
|
const node = nodes.find((node) => node.id === activeNodeId);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import BackgroundVert from "./Background.vert";
|
import BackgroundVert from "./Background.vert";
|
||||||
import BackgroundFrag from "./Background.frag";
|
import BackgroundFrag from "./Background.frag";
|
||||||
import { colors } from "../graph/colors.svelte";
|
import { colors } from "../graph/colors.svelte";
|
||||||
import { Color } from "three";
|
|
||||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -42,10 +41,10 @@
|
|||||||
value: [0, 1, 0],
|
value: [0, 1, 0],
|
||||||
},
|
},
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
value: colors["layer-0"].clone(),
|
value: colors["layer-0"],
|
||||||
},
|
},
|
||||||
lineColor: {
|
lineColor: {
|
||||||
value: colors["outline"].clone(),
|
value: colors["outline"],
|
||||||
},
|
},
|
||||||
zoomLimits: {
|
zoomLimits: {
|
||||||
value: [2, 50],
|
value: [2, 50],
|
||||||
@@ -55,9 +54,8 @@
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
uniforms.camPos.value={cameraPosition}
|
uniforms.camPos.value={cameraPosition}
|
||||||
uniforms.backgroundColor.value={appSettings.theme &&
|
uniforms.backgroundColor.value={appSettings.theme && colors["layer-0"]}
|
||||||
colors["layer-0"].clone()}
|
uniforms.lineColor.value={appSettings.theme && colors["outline"]}
|
||||||
uniforms.lineColor.value={appSettings.theme && colors["outline"].clone()}
|
|
||||||
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||||
uniforms.dimensions.value={[width, height]}
|
uniforms.dimensions.value={[width, height]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ import type {
|
|||||||
Node,
|
Node,
|
||||||
NodeInput,
|
NodeInput,
|
||||||
NodeRegistry,
|
NodeRegistry,
|
||||||
|
NodeType,
|
||||||
Socket,
|
Socket,
|
||||||
} from "@nodes/types";
|
} from "@nodes/types";
|
||||||
import { fastHashString } from "@nodes/utils";
|
import { fastHashString } from "@nodes/utils";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { SvelteMap } from "svelte/reactivity";
|
||||||
import EventEmitter from "./helpers/EventEmitter.js";
|
import EventEmitter from "./helpers/EventEmitter";
|
||||||
import { createLogger } from "./helpers/index.js";
|
import { createLogger } from "./helpers/index";
|
||||||
import throttle from "./helpers/throttle.js";
|
import throttle from "./helpers/throttle";
|
||||||
import { HistoryManager } from "./history-manager.js";
|
import { HistoryManager } from "./history-manager";
|
||||||
|
|
||||||
const logger = createLogger("graph-manager");
|
const logger = createLogger("graph-manager");
|
||||||
|
|
||||||
logger.mute();
|
logger.mute();
|
||||||
|
|
||||||
const clone =
|
const clone =
|
||||||
@@ -40,24 +40,28 @@ export class GraphManager extends EventEmitter<{
|
|||||||
values: Record<string, unknown>;
|
values: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
}> {
|
}> {
|
||||||
status: Writable<"loading" | "idle" | "error"> = writable("loading");
|
status = $state<"loading" | "idle" | "error">();
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|
||||||
graph: Graph = { id: 0, nodes: [], edges: [] };
|
graph: Graph = { id: 0, nodes: [], edges: [] };
|
||||||
id = writable(0);
|
id = $state(0);
|
||||||
|
|
||||||
private _nodes: Map<number, Node> = new Map();
|
nodes = new SvelteMap<number, Node>();
|
||||||
nodes: Writable<Map<number, Node>> = writable(new Map());
|
|
||||||
|
|
||||||
private _edges: Edge[] = [];
|
edges = $state<Edge[]>([]);
|
||||||
edges: Writable<Edge[]> = writable([]);
|
|
||||||
|
|
||||||
settingTypes: Record<string, NodeInput> = {};
|
settingTypes: Record<string, NodeInput> = {};
|
||||||
settings: Record<string, unknown> = {};
|
settings = $state<Record<string, unknown>>();
|
||||||
|
|
||||||
currentUndoGroup: number | null = null;
|
currentUndoGroup: number | null = null;
|
||||||
|
|
||||||
inputSockets: Writable<Set<string>> = writable(new Set());
|
inputSockets = $derived.by(() => {
|
||||||
|
const s = new Set<string>();
|
||||||
|
for (const edge of this.edges) {
|
||||||
|
s.add(`${edge[2].id}-${edge[3]}`);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
|
||||||
history: HistoryManager = new HistoryManager();
|
history: HistoryManager = new HistoryManager();
|
||||||
execute = throttle(() => {
|
execute = throttle(() => {
|
||||||
@@ -67,28 +71,17 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
constructor(public registry: NodeRegistry) {
|
constructor(public registry: NodeRegistry) {
|
||||||
super();
|
super();
|
||||||
this.nodes.subscribe((nodes) => {
|
|
||||||
this._nodes = nodes;
|
|
||||||
});
|
|
||||||
this.edges.subscribe((edges) => {
|
|
||||||
this._edges = edges;
|
|
||||||
const s = new Set<string>();
|
|
||||||
for (const edge of edges) {
|
|
||||||
s.add(`${edge[2].id}-${edge[3]}`);
|
|
||||||
}
|
|
||||||
this.inputSockets.set(s);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(): Graph {
|
serialize(): Graph {
|
||||||
logger.group("serializing graph");
|
logger.group("serializing graph");
|
||||||
const nodes = Array.from(this._nodes.values()).map((node) => ({
|
const nodes = Array.from(this.nodes.values()).map((node) => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
position: [...node.position],
|
position: [...node.position],
|
||||||
type: node.type,
|
type: node.type,
|
||||||
props: node.props,
|
props: node.props,
|
||||||
})) as Node[];
|
})) as Node[];
|
||||||
const edges = this._edges.map((edge) => [
|
const edges = this.edges.map((edge) => [
|
||||||
edge[0].id,
|
edge[0].id,
|
||||||
edge[1],
|
edge[1],
|
||||||
edge[2].id,
|
edge[2].id,
|
||||||
@@ -101,12 +94,12 @@ export class GraphManager extends EventEmitter<{
|
|||||||
edges,
|
edges,
|
||||||
};
|
};
|
||||||
logger.groupEnd();
|
logger.groupEnd();
|
||||||
|
return clone($state.snapshot(serialized));
|
||||||
return clone(serialized);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lastSettingsHash = 0;
|
private lastSettingsHash = 0;
|
||||||
setSettings(settings: Record<string, unknown>) {
|
setSettings(settings: Record<string, unknown>) {
|
||||||
|
console.log("GraphManager.setSettings", settings);
|
||||||
let hash = fastHashString(JSON.stringify(settings));
|
let hash = fastHashString(JSON.stringify(settings));
|
||||||
if (hash === this.lastSettingsHash) return;
|
if (hash === this.lastSettingsHash) return;
|
||||||
this.lastSettingsHash = hash;
|
this.lastSettingsHash = hash;
|
||||||
@@ -141,7 +134,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
const children = node.tmp?.children || [];
|
const children = node.tmp?.children || [];
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (nodes.includes(child)) {
|
if (nodes.includes(child)) {
|
||||||
const edge = this._edges.find(
|
const edge = this.edges.find(
|
||||||
(e) => e[0].id === node.id && e[2].id === child.id,
|
(e) => e[0].id === node.id && e[2].id === child.id,
|
||||||
);
|
);
|
||||||
if (edge) {
|
if (edge) {
|
||||||
@@ -188,8 +181,12 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return [from, edge[1], to, edge[3]] as Edge;
|
return [from, edge[1], to, edge[3]] as Edge;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.edges.set(edges);
|
this.edges = [...edges];
|
||||||
this.nodes.set(nodes);
|
|
||||||
|
this.nodes.clear();
|
||||||
|
for (const [id, node] of nodes) {
|
||||||
|
this.nodes.set(id, node);
|
||||||
|
}
|
||||||
|
|
||||||
this.execute();
|
this.execute();
|
||||||
}
|
}
|
||||||
@@ -199,8 +196,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
this.status.set("loading");
|
this.status = "loading";
|
||||||
this.id.set(graph.id);
|
this.id = graph.id;
|
||||||
|
|
||||||
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
||||||
await this.registry.load(nodeIds);
|
await this.registry.load(nodeIds);
|
||||||
@@ -209,7 +206,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
const nodeType = this.registry.getNode(node.type);
|
const nodeType = this.registry.getNode(node.type);
|
||||||
if (!nodeType) {
|
if (!nodeType) {
|
||||||
logger.error(`Node type not found: ${node.type}`);
|
logger.error(`Node type not found: ${node.type}`);
|
||||||
this.status.set("error");
|
this.status = "error";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
@@ -254,7 +251,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
this.status.set("idle");
|
this.status = "idle";
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
||||||
@@ -262,18 +259,18 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAllNodes() {
|
getAllNodes() {
|
||||||
return Array.from(this._nodes.values());
|
return Array.from(this.nodes.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode(id: number) {
|
getNode(id: number) {
|
||||||
return this._nodes.get(id);
|
return this.nodes.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeType(id: string) {
|
getNodeType(id: string) {
|
||||||
return this.registry.getNode(id);
|
return this.registry.getNode(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNode(id: string) {
|
async loadNode(id: NodeType) {
|
||||||
await this.registry.load([id]);
|
await this.registry.load([id]);
|
||||||
const nodeType = this.registry.getNode(id);
|
const nodeType = this.registry.getNode(id);
|
||||||
|
|
||||||
@@ -287,7 +284,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
if (settingId) {
|
if (settingId) {
|
||||||
settingTypes[settingId] = nodeType.inputs[key];
|
settingTypes[settingId] = nodeType.inputs[key];
|
||||||
if (
|
if (
|
||||||
settingValues[settingId] === undefined &&
|
settingValues &&
|
||||||
|
settingValues?.[settingId] === undefined &&
|
||||||
"value" in nodeType.inputs[key]
|
"value" in nodeType.inputs[key]
|
||||||
) {
|
) {
|
||||||
settingValues[settingId] = nodeType.inputs[key].value;
|
settingValues[settingId] = nodeType.inputs[key].value;
|
||||||
@@ -297,6 +295,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.settings = settingValues;
|
this.settings = settingValues;
|
||||||
|
console.log("GraphManager.setSettings", settingValues);
|
||||||
this.settingTypes = settingTypes;
|
this.settingTypes = settingTypes;
|
||||||
this.emit("settings", { types: settingTypes, values: settingValues });
|
this.emit("settings", { types: settingTypes, values: settingValues });
|
||||||
}
|
}
|
||||||
@@ -331,8 +330,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeNode(node: Node, { restoreEdges = false } = {}) {
|
removeNode(node: Node, { restoreEdges = false } = {}) {
|
||||||
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]) {
|
||||||
this.removeEdge(edge, { applyDeletion: false });
|
this.removeEdge(edge, { applyDeletion: false });
|
||||||
}
|
}
|
||||||
@@ -355,18 +354,13 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.edges.set(this._edges);
|
this.nodes.delete(node.id);
|
||||||
|
|
||||||
this.nodes.update((nodes) => {
|
|
||||||
nodes.delete(node.id);
|
|
||||||
return nodes;
|
|
||||||
});
|
|
||||||
this.execute();
|
this.execute();
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
createNodeId() {
|
createNodeId() {
|
||||||
const max = Math.max(...this._nodes.keys());
|
const max = Math.max(0, ...this.nodes.keys());
|
||||||
return max + 1;
|
return max + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,13 +400,11 @@ export class GraphManager extends EventEmitter<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
this._nodes.set(node.id, node);
|
this.nodes.set(node.id, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._edges.push(..._edges);
|
this.edges.push(..._edges);
|
||||||
|
|
||||||
this.nodes.set(this._nodes);
|
|
||||||
this.edges.set(this._edges);
|
|
||||||
this.save();
|
this.save();
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
@@ -440,10 +432,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
props,
|
props,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.nodes.update((nodes) => {
|
this.nodes.set(node.id, node);
|
||||||
nodes.set(node.id, node);
|
|
||||||
return nodes;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
@@ -480,7 +469,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const edgeToBeReplaced = this._edges.find(
|
const edgeToBeReplaced = this.edges.find(
|
||||||
(e) => e[2].id === to.id && e[3] === toSocket,
|
(e) => e[2].id === to.id && e[3] === toSocket,
|
||||||
);
|
);
|
||||||
if (edgeToBeReplaced) {
|
if (edgeToBeReplaced) {
|
||||||
@@ -488,9 +477,9 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (applyUpdate) {
|
if (applyUpdate) {
|
||||||
this._edges.push([from, fromSocket, to, toSocket]);
|
this.edges.push([from, fromSocket, to, toSocket]);
|
||||||
} else {
|
} else {
|
||||||
this._edges.push([from, fromSocket, to, toSocket]);
|
this.edges.push([from, fromSocket, to, toSocket]);
|
||||||
}
|
}
|
||||||
|
|
||||||
from.tmp = from.tmp || {};
|
from.tmp = from.tmp || {};
|
||||||
@@ -502,7 +491,6 @@ export class GraphManager extends EventEmitter<{
|
|||||||
to.tmp.parents.push(from);
|
to.tmp.parents.push(from);
|
||||||
|
|
||||||
if (applyUpdate) {
|
if (applyUpdate) {
|
||||||
this.edges.set(this._edges);
|
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
this.execute();
|
this.execute();
|
||||||
@@ -630,7 +618,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
const id2 = edge[2].id;
|
const id2 = edge[2].id;
|
||||||
const sid2 = edge[3];
|
const sid2 = edge[3];
|
||||||
|
|
||||||
const _edge = this._edges.find(
|
const _edge = this.edges.find(
|
||||||
(e) =>
|
(e) =>
|
||||||
e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2,
|
e[0].id === id0 && e[1] === sid0 && e[2].id === id2 && e[3] === sid2,
|
||||||
);
|
);
|
||||||
@@ -651,18 +639,16 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (applyDeletion) {
|
if (applyDeletion) {
|
||||||
this.edges.update((edges) => {
|
this.edges = this.edges.filter((e) => e !== _edge);
|
||||||
return edges.filter((e) => e !== _edge);
|
|
||||||
});
|
|
||||||
this.execute();
|
this.execute();
|
||||||
this.save();
|
this.save();
|
||||||
} else {
|
} else {
|
||||||
this._edges = this._edges.filter((e) => e !== _edge);
|
this.edges = this.edges.filter((e) => e !== _edge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdgesToNode(node: Node) {
|
getEdgesToNode(node: Node) {
|
||||||
return this._edges
|
return this.edges
|
||||||
.filter((edge) => edge[2].id === node.id)
|
.filter((edge) => edge[2].id === node.id)
|
||||||
.map((edge) => {
|
.map((edge) => {
|
||||||
const from = this.getNode(edge[0].id);
|
const from = this.getNode(edge[0].id);
|
||||||
@@ -674,7 +660,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
getEdgesFromNode(node: Node) {
|
getEdgesFromNode(node: Node) {
|
||||||
return this._edges
|
return this.edges
|
||||||
.filter((edge) => edge[0].id === node.id)
|
.filter((edge) => edge[0].id === node.id)
|
||||||
.map((edge) => {
|
.map((edge) => {
|
||||||
const from = this.getNode(edge[0].id);
|
const from = this.getNode(edge[0].id);
|
||||||
@@ -1,73 +1,67 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Node, NodeType, Socket } from "@nodes/types";
|
||||||
|
import { GraphSchema } from "@nodes/types";
|
||||||
|
import { getContext, onMount, setContext } from "svelte";
|
||||||
|
import type { OrthographicCamera } from "three";
|
||||||
|
import { createKeyMap } from "../../helpers/createKeyMap";
|
||||||
|
import AddMenu from "../AddMenu.svelte";
|
||||||
|
import Background from "../background/Background.svelte";
|
||||||
|
import BoxSelection from "../BoxSelection.svelte";
|
||||||
|
import Camera from "../Camera.svelte";
|
||||||
|
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
||||||
import {
|
import {
|
||||||
animate,
|
animate,
|
||||||
lerp,
|
lerp,
|
||||||
snapToGrid as snapPointToGrid,
|
snapToGrid as snapPointToGrid,
|
||||||
} from "../helpers/index.js";
|
} from "../helpers/index.js";
|
||||||
import type { OrthographicCamera } from "three";
|
|
||||||
import Background from "../background/Background.svelte";
|
|
||||||
import { getContext, onMount, setContext } from "svelte";
|
|
||||||
import Camera from "../Camera.svelte";
|
|
||||||
import GraphView from "./GraphView.svelte";
|
import GraphView from "./GraphView.svelte";
|
||||||
import type { Node, NodeId, Node as NodeType, Socket } from "@nodes/types";
|
|
||||||
import { GraphSchema } from "@nodes/types";
|
|
||||||
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
|
||||||
import { getGraphState } from "./state.svelte";
|
import { getGraphState } from "./state.svelte";
|
||||||
import { createKeyMap } from "../../helpers/createKeyMap";
|
|
||||||
import BoxSelection from "../BoxSelection.svelte";
|
|
||||||
import AddMenu from "../AddMenu.svelte";
|
|
||||||
|
|
||||||
import HelpView from "../HelpView.svelte";
|
|
||||||
import FileSaver from "file-saver";
|
|
||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import { getGraphManager } from "./context.js";
|
import FileSaver from "file-saver";
|
||||||
|
import HelpView from "../HelpView.svelte";
|
||||||
|
import { getGraphManager } from "./context";
|
||||||
|
|
||||||
|
const graph = getGraphManager();
|
||||||
const graphState = getGraphState();
|
const graphState = getGraphState();
|
||||||
|
|
||||||
export let snapToGrid = true;
|
const {
|
||||||
export let showGrid = true;
|
snapToGrid = $bindable(true),
|
||||||
export let showHelp = false;
|
showGrid = $bindable(true),
|
||||||
|
showHelp = $bindable(false),
|
||||||
|
} = $props();
|
||||||
|
|
||||||
const keymap = getContext<ReturnType<typeof createKeyMap>>("keymap");
|
const keymap = getContext<ReturnType<typeof createKeyMap>>("keymap");
|
||||||
|
|
||||||
const manager = getGraphManager();
|
let wrapper = $state<HTMLDivElement>(null!);
|
||||||
|
|
||||||
const status = manager.status;
|
const rect: DOMRect = $derived(
|
||||||
const nodes = manager.nodes;
|
wrapper ? wrapper.getBoundingClientRect() : new DOMRect(0, 0, 0, 0),
|
||||||
const edges = manager.edges;
|
);
|
||||||
|
let width = $derived(rect?.width ?? 100);
|
||||||
|
let height = $derived(rect?.height ?? 100);
|
||||||
|
|
||||||
let wrapper: HTMLDivElement;
|
let camera = $state<OrthographicCamera>(null!);
|
||||||
let rect: DOMRect;
|
|
||||||
$: rect =
|
|
||||||
wrapper && width
|
|
||||||
? wrapper.getBoundingClientRect()
|
|
||||||
: ({ x: 0, y: 0, width: 0, height: 0 } as DOMRect);
|
|
||||||
|
|
||||||
let camera: OrthographicCamera;
|
|
||||||
const minZoom = 1;
|
const minZoom = 1;
|
||||||
const maxZoom = 40;
|
const maxZoom = 40;
|
||||||
let mousePosition = [0, 0];
|
let mousePosition = $state([0, 0]);
|
||||||
let mouseDown: null | [number, number] = null;
|
let mouseDown = $state<[number, number] | null>(null);
|
||||||
let mouseDownId = -1;
|
let mouseDownId = -1;
|
||||||
let boxSelection = false;
|
let boxSelection = $state(false);
|
||||||
const cameraDown = [0, 0];
|
const cameraDown = [0, 0];
|
||||||
let cameraPosition: [number, number, number] = [0, 0, 4];
|
let cameraPosition: [number, number, number] = $state([0, 0, 4]);
|
||||||
let addMenuPosition: [number, number] | null = null;
|
let addMenuPosition = $state<[number, number] | null>(null);
|
||||||
let clipboard: null | {
|
let clipboard: null | {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
edges: [number, number, number, string][];
|
edges: [number, number, number, string][];
|
||||||
} = null;
|
} = null;
|
||||||
|
|
||||||
let width = rect?.width ?? 100;
|
const cameraBounds = $derived([
|
||||||
let height = rect?.height ?? 100;
|
|
||||||
|
|
||||||
let cameraBounds = [-1000, 1000, -1000, 1000];
|
|
||||||
$: cameraBounds = [
|
|
||||||
cameraPosition[0] - width / cameraPosition[2] / 2,
|
cameraPosition[0] - width / cameraPosition[2] / 2,
|
||||||
cameraPosition[0] + width / cameraPosition[2] / 2,
|
cameraPosition[0] + width / cameraPosition[2] / 2,
|
||||||
cameraPosition[1] - height / cameraPosition[2] / 2,
|
cameraPosition[1] - height / cameraPosition[2] / 2,
|
||||||
cameraPosition[1] + height / cameraPosition[2] / 2,
|
cameraPosition[1] + height / cameraPosition[2] / 2,
|
||||||
];
|
]);
|
||||||
function setCameraTransform(
|
function setCameraTransform(
|
||||||
x = cameraPosition[0],
|
x = cameraPosition[0],
|
||||||
y = cameraPosition[1],
|
y = cameraPosition[1],
|
||||||
@@ -82,7 +76,7 @@
|
|||||||
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
|
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodePosition(node: NodeType) {
|
function updateNodePosition(node: Node) {
|
||||||
if (node?.tmp?.ref && node?.tmp?.mesh) {
|
if (node?.tmp?.ref && node?.tmp?.mesh) {
|
||||||
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
||||||
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
|
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
|
||||||
@@ -96,6 +90,7 @@
|
|||||||
delete node.tmp.x;
|
delete node.tmp.x;
|
||||||
delete node.tmp.y;
|
delete node.tmp.y;
|
||||||
}
|
}
|
||||||
|
graph.edges = [...graph.edges];
|
||||||
} else {
|
} else {
|
||||||
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
||||||
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
||||||
@@ -112,7 +107,7 @@
|
|||||||
if (nodeTypeId in nodeHeightCache) {
|
if (nodeTypeId in nodeHeightCache) {
|
||||||
return nodeHeightCache[nodeTypeId];
|
return nodeHeightCache[nodeTypeId];
|
||||||
}
|
}
|
||||||
const node = manager.getNodeType(nodeTypeId);
|
const node = graph.getNodeType(nodeTypeId);
|
||||||
if (!node?.inputs) {
|
if (!node?.inputs) {
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
@@ -131,7 +126,7 @@
|
|||||||
}
|
}
|
||||||
setContext("getNodeHeight", getNodeHeight);
|
setContext("getNodeHeight", getNodeHeight);
|
||||||
|
|
||||||
setContext("isNodeInView", (node: NodeType) => {
|
setContext("isNodeInView", (node: Node) => {
|
||||||
const height = getNodeHeight(node.type);
|
const height = getNodeHeight(node.type);
|
||||||
const width = 20;
|
const width = 20;
|
||||||
return (
|
return (
|
||||||
@@ -162,7 +157,7 @@
|
|||||||
// we are going to check if we clicked on a node by coordinates
|
// we are going to check if we clicked on a node by coordinates
|
||||||
if (clickedNodeId === -1) {
|
if (clickedNodeId === -1) {
|
||||||
const [downX, downY] = projectScreenToWorld(mx, my);
|
const [downX, downY] = projectScreenToWorld(mx, my);
|
||||||
for (const node of $nodes.values()) {
|
for (const node of graph.nodes.values()) {
|
||||||
const x = node.position[0];
|
const x = node.position[0];
|
||||||
const y = node.position[1];
|
const y = node.position[1];
|
||||||
const height = getNodeHeight(node.type);
|
const height = getNodeHeight(node.type);
|
||||||
@@ -183,13 +178,13 @@
|
|||||||
|
|
||||||
// remove existing edge
|
// remove existing edge
|
||||||
if (typeof index === "string") {
|
if (typeof index === "string") {
|
||||||
const edges = manager.getEdgesToNode(node);
|
const edges = graph.getEdgesToNode(node);
|
||||||
for (const edge of edges) {
|
for (const edge of edges) {
|
||||||
if (edge[3] === index) {
|
if (edge[3] === index) {
|
||||||
node = edge[0];
|
node = edge[0];
|
||||||
index = edge[1];
|
index = edge[1];
|
||||||
position = getSocketPosition(node, index);
|
position = getSocketPosition(node, index);
|
||||||
manager.removeEdge(edge);
|
graph.removeEdge(edge);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +197,7 @@
|
|||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
|
|
||||||
graphState.possibleSockets = manager
|
graphState.possibleSockets = graph
|
||||||
.getPossibleSockets(graphState.activeSocket)
|
.getPossibleSockets(graphState.activeSocket)
|
||||||
.map(([node, index]) => {
|
.map(([node, index]) => {
|
||||||
return {
|
return {
|
||||||
@@ -227,7 +222,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSocketPosition(
|
function getSocketPosition(
|
||||||
node: NodeType,
|
node: Node,
|
||||||
index: string | number,
|
index: string | number,
|
||||||
): [number, number] {
|
): [number, number] {
|
||||||
if (typeof index === "number") {
|
if (typeof index === "number") {
|
||||||
@@ -294,7 +289,7 @@
|
|||||||
const x2 = Math.max(mouseD[0], mousePosition[0]);
|
const x2 = Math.max(mouseD[0], mousePosition[0]);
|
||||||
const y1 = Math.min(mouseD[1], mousePosition[1]);
|
const y1 = Math.min(mouseD[1], mousePosition[1]);
|
||||||
const y2 = Math.max(mouseD[1], mousePosition[1]);
|
const y2 = Math.max(mouseD[1], mousePosition[1]);
|
||||||
for (const node of $nodes.values()) {
|
for (const node of graph.nodes.values()) {
|
||||||
if (!node?.tmp) continue;
|
if (!node?.tmp) continue;
|
||||||
const x = node.position[0];
|
const x = node.position[0];
|
||||||
const y = node.position[1];
|
const y = node.position[1];
|
||||||
@@ -310,7 +305,7 @@
|
|||||||
|
|
||||||
// here we are handling dragging of nodes
|
// here we are handling dragging of nodes
|
||||||
if (graphState.activeNodeId !== -1 && mouseDownId !== -1) {
|
if (graphState.activeNodeId !== -1 && mouseDownId !== -1) {
|
||||||
const node = manager.getNode(graphState.activeNodeId);
|
const node = graph.getNode(graphState.activeNodeId);
|
||||||
if (!node || event.buttons !== 1) return;
|
if (!node || event.buttons !== 1) return;
|
||||||
|
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
@@ -341,7 +336,7 @@
|
|||||||
|
|
||||||
if (graphState.selectedNodes?.size) {
|
if (graphState.selectedNodes?.size) {
|
||||||
for (const nodeId of graphState.selectedNodes) {
|
for (const nodeId of graphState.selectedNodes) {
|
||||||
const n = manager.getNode(nodeId);
|
const n = graph.getNode(nodeId);
|
||||||
if (!n?.tmp) continue;
|
if (!n?.tmp) continue;
|
||||||
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||||
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
||||||
@@ -354,7 +349,6 @@
|
|||||||
|
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
|
|
||||||
$edges = $edges;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,10 +430,10 @@
|
|||||||
graphState.activeNodeId = clickedNodeId;
|
graphState.activeNodeId = clickedNodeId;
|
||||||
// select the node
|
// select the node
|
||||||
} else if (event.shiftKey) {
|
} else if (event.shiftKey) {
|
||||||
const activeNode = manager.getNode(graphState.activeNodeId);
|
const activeNode = graph.getNode(graphState.activeNodeId);
|
||||||
const newNode = manager.getNode(clickedNodeId);
|
const newNode = graph.getNode(clickedNodeId);
|
||||||
if (activeNode && newNode) {
|
if (activeNode && newNode) {
|
||||||
const edge = manager.getNodesBetween(activeNode, newNode);
|
const edge = graph.getNodesBetween(activeNode, newNode);
|
||||||
if (edge) {
|
if (edge) {
|
||||||
graphState.selectedNodes.clear();
|
graphState.selectedNodes.clear();
|
||||||
for (const node of edge) {
|
for (const node of edge) {
|
||||||
@@ -456,7 +450,7 @@
|
|||||||
boxSelection = true;
|
boxSelection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = manager.getNode(graphState.activeNodeId);
|
const node = graph.getNode(graphState.activeNodeId);
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
node.tmp.downX = node.position[0];
|
node.tmp.downX = node.position[0];
|
||||||
@@ -464,7 +458,7 @@
|
|||||||
|
|
||||||
if (graphState.selectedNodes) {
|
if (graphState.selectedNodes) {
|
||||||
for (const nodeId of graphState.selectedNodes) {
|
for (const nodeId of graphState.selectedNodes) {
|
||||||
const n = manager.getNode(nodeId);
|
const n = graph.getNode(nodeId);
|
||||||
if (!n) continue;
|
if (!n) continue;
|
||||||
n.tmp = n.tmp || {};
|
n.tmp = n.tmp || {};
|
||||||
n.tmp.downX = n.position[0];
|
n.tmp.downX = n.position[0];
|
||||||
@@ -480,10 +474,10 @@
|
|||||||
graphState.activeNodeId,
|
graphState.activeNodeId,
|
||||||
...(graphState.selectedNodes?.values() || []),
|
...(graphState.selectedNodes?.values() || []),
|
||||||
]
|
]
|
||||||
.map((id) => manager.getNode(id))
|
.map((id) => graph.getNode(id))
|
||||||
.filter(Boolean) as Node[];
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
const _edges = manager.getEdgesBetweenNodes(_nodes);
|
const _edges = graph.getEdgesBetweenNodes(_nodes);
|
||||||
|
|
||||||
_nodes = _nodes.map((_node) => {
|
_nodes = _nodes.map((_node) => {
|
||||||
const node = globalThis.structuredClone({
|
const node = globalThis.structuredClone({
|
||||||
@@ -514,7 +508,7 @@
|
|||||||
})
|
})
|
||||||
.filter(Boolean) as Node[];
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
const newNodes = manager.createGraph(_nodes, clipboard.edges);
|
const newNodes = graph.createGraph(_nodes, clipboard.edges);
|
||||||
graphState.selectedNodes.clear();
|
graphState.selectedNodes.clear();
|
||||||
for (const node of newNodes) {
|
for (const node of newNodes) {
|
||||||
graphState.selectedNodes.add(node.id);
|
graphState.selectedNodes.add(node.id);
|
||||||
@@ -527,9 +521,9 @@
|
|||||||
key: "l",
|
key: "l",
|
||||||
description: "Select linked nodes",
|
description: "Select linked nodes",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const activeNode = manager.getNode(graphState.activeNodeId);
|
const activeNode = graph.getNode(graphState.activeNodeId);
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
const nodes = manager.getLinkedNodes(activeNode);
|
const nodes = graph.getLinkedNodes(activeNode);
|
||||||
graphState.selectedNodes.clear();
|
graphState.selectedNodes.clear();
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
graphState.selectedNodes.add(node.id);
|
graphState.selectedNodes.add(node.id);
|
||||||
@@ -542,7 +536,8 @@
|
|||||||
key: "?",
|
key: "?",
|
||||||
description: "Toggle Help",
|
description: "Toggle Help",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
showHelp = !showHelp;
|
// TODO: fix this
|
||||||
|
// showHelp = !showHelp;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -586,12 +581,12 @@
|
|||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
|
|
||||||
const average = [0, 0];
|
const average = [0, 0];
|
||||||
for (const node of $nodes.values()) {
|
for (const node of graph.nodes.values()) {
|
||||||
average[0] += node.position[0];
|
average[0] += node.position[0];
|
||||||
average[1] += node.position[1];
|
average[1] += node.position[1];
|
||||||
}
|
}
|
||||||
average[0] = average[0] ? average[0] / $nodes.size : 0;
|
average[0] = average[0] ? average[0] / graph.nodes.size : 0;
|
||||||
average[1] = average[1] ? average[1] / $nodes.size : 0;
|
average[1] = average[1] ? average[1] / graph.nodes.size : 0;
|
||||||
|
|
||||||
const camX = cameraPosition[0];
|
const camX = cameraPosition[0];
|
||||||
const camY = cameraPosition[1];
|
const camY = cameraPosition[1];
|
||||||
@@ -617,7 +612,7 @@
|
|||||||
description: "Select all nodes",
|
description: "Select all nodes",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
for (const node of $nodes.keys()) {
|
for (const node of graph.nodes.keys()) {
|
||||||
graphState.selectedNodes.add(node);
|
graphState.selectedNodes.add(node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -629,8 +624,8 @@
|
|||||||
description: "Undo",
|
description: "Undo",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
manager.undo();
|
graph.undo();
|
||||||
for (const node of $nodes.values()) {
|
for (const node of graph.nodes.values()) {
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -641,8 +636,8 @@
|
|||||||
ctrl: true,
|
ctrl: true,
|
||||||
description: "Redo",
|
description: "Redo",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
manager.redo();
|
graph.redo();
|
||||||
for (const node of $nodes.values()) {
|
for (const node of graph.nodes.values()) {
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -654,7 +649,7 @@
|
|||||||
description: "Save",
|
description: "Save",
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const state = manager.serialize();
|
const state = graph.serialize();
|
||||||
const blob = new Blob([JSON.stringify(state)], {
|
const blob = new Blob([JSON.stringify(state)], {
|
||||||
type: "application/json;charset=utf-8",
|
type: "application/json;charset=utf-8",
|
||||||
});
|
});
|
||||||
@@ -667,24 +662,24 @@
|
|||||||
description: "Delete selected nodes",
|
description: "Delete selected nodes",
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
manager.startUndoGroup();
|
graph.startUndoGroup();
|
||||||
if (graphState.activeNodeId !== -1) {
|
if (graphState.activeNodeId !== -1) {
|
||||||
const node = manager.getNode(graphState.activeNodeId);
|
const node = graph.getNode(graphState.activeNodeId);
|
||||||
if (node) {
|
if (node) {
|
||||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
graphState.activeNodeId = -1;
|
graphState.activeNodeId = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (graphState.selectedNodes) {
|
if (graphState.selectedNodes) {
|
||||||
for (const nodeId of graphState.selectedNodes) {
|
for (const nodeId of graphState.selectedNodes) {
|
||||||
const node = manager.getNode(nodeId);
|
const node = graph.getNode(nodeId);
|
||||||
if (node) {
|
if (node) {
|
||||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
graph.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graphState.clearSelection();
|
graphState.clearSelection();
|
||||||
}
|
}
|
||||||
manager.saveUndoGroup();
|
graph.saveUndoGroup();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -692,7 +687,7 @@
|
|||||||
isPanning = false;
|
isPanning = false;
|
||||||
if (!mouseDown) return;
|
if (!mouseDown) return;
|
||||||
|
|
||||||
const activeNode = manager.getNode(graphState.activeNodeId);
|
const activeNode = graph.getNode(graphState.activeNodeId);
|
||||||
|
|
||||||
const clickedNodeId = getNodeIdFromEvent(event);
|
const clickedNodeId = getNodeIdFromEvent(event);
|
||||||
|
|
||||||
@@ -724,9 +719,9 @@
|
|||||||
}
|
}
|
||||||
const nodes = [
|
const nodes = [
|
||||||
...[...(graphState.selectedNodes?.values() || [])].map((id) =>
|
...[...(graphState.selectedNodes?.values() || [])].map((id) =>
|
||||||
manager.getNode(id),
|
graph.getNode(id),
|
||||||
),
|
),
|
||||||
] as NodeType[];
|
] as Node[];
|
||||||
|
|
||||||
const vec = [
|
const vec = [
|
||||||
activeNode.position[0] - (activeNode?.tmp.x || 0),
|
activeNode.position[0] - (activeNode?.tmp.x || 0),
|
||||||
@@ -758,16 +753,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$edges = $edges;
|
|
||||||
});
|
});
|
||||||
manager.save();
|
graph.save();
|
||||||
} else if (graphState.hoveredSocket && graphState.activeSocket) {
|
} else if (graphState.hoveredSocket && graphState.activeSocket) {
|
||||||
if (
|
if (
|
||||||
typeof graphState.hoveredSocket.index === "number" &&
|
typeof graphState.hoveredSocket.index === "number" &&
|
||||||
typeof graphState.activeSocket.index === "string"
|
typeof graphState.activeSocket.index === "string"
|
||||||
) {
|
) {
|
||||||
manager.createEdge(
|
graph.createEdge(
|
||||||
graphState.hoveredSocket.node,
|
graphState.hoveredSocket.node,
|
||||||
graphState.hoveredSocket.index || 0,
|
graphState.hoveredSocket.index || 0,
|
||||||
graphState.activeSocket.node,
|
graphState.activeSocket.node,
|
||||||
@@ -777,14 +770,14 @@
|
|||||||
typeof graphState.activeSocket.index == "number" &&
|
typeof graphState.activeSocket.index == "number" &&
|
||||||
typeof graphState.hoveredSocket.index === "string"
|
typeof graphState.hoveredSocket.index === "string"
|
||||||
) {
|
) {
|
||||||
manager.createEdge(
|
graph.createEdge(
|
||||||
graphState.activeSocket.node,
|
graphState.activeSocket.node,
|
||||||
graphState.activeSocket.index || 0,
|
graphState.activeSocket.index || 0,
|
||||||
graphState.hoveredSocket.node,
|
graphState.hoveredSocket.node,
|
||||||
graphState.hoveredSocket.index,
|
graphState.hoveredSocket.index,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
manager.save();
|
graph.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if camera moved
|
// check if camera moved
|
||||||
@@ -807,9 +800,9 @@
|
|||||||
addMenuPosition = null;
|
addMenuPosition = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isPanning = false;
|
let isPanning = $state(false);
|
||||||
let isDragging = false;
|
let isDragging = $state(false);
|
||||||
let hoveredNodeId = -1;
|
let hoveredNodeId = $state(-1);
|
||||||
|
|
||||||
function handleMouseLeave() {
|
function handleMouseLeave() {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
@@ -820,7 +813,7 @@
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
if (!event.dataTransfer) return;
|
if (!event.dataTransfer) return;
|
||||||
const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
|
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType;
|
||||||
let mx = event.clientX - rect.x;
|
let mx = event.clientX - rect.x;
|
||||||
let my = event.clientY - rect.y;
|
let my = event.clientY - rect.y;
|
||||||
|
|
||||||
@@ -841,8 +834,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pos = projectScreenToWorld(mx, my);
|
const pos = projectScreenToWorld(mx, my);
|
||||||
manager.registry.load([nodeId]).then(() => {
|
graph.registry.load([nodeId]).then(() => {
|
||||||
manager.createNode({
|
graph.createNode({
|
||||||
type: nodeId,
|
type: nodeId,
|
||||||
props,
|
props,
|
||||||
position: pos,
|
position: pos,
|
||||||
@@ -856,9 +849,9 @@
|
|||||||
reader.onload = async (e) => {
|
reader.onload = async (e) => {
|
||||||
const buffer = e.target?.result;
|
const buffer = e.target?.result;
|
||||||
if (buffer?.constructor === ArrayBuffer) {
|
if (buffer?.constructor === ArrayBuffer) {
|
||||||
const nodeType = await manager.registry.register(buffer);
|
const nodeType = await graph.registry.register(buffer);
|
||||||
|
|
||||||
manager.createNode({
|
graph.createNode({
|
||||||
type: nodeType.id,
|
type: nodeType.id,
|
||||||
props: {},
|
props: {},
|
||||||
position: projectScreenToWorld(mx, my),
|
position: projectScreenToWorld(mx, my),
|
||||||
@@ -869,10 +862,10 @@
|
|||||||
} else if (file.type === "application/json") {
|
} else if (file.type === "application/json") {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const buffer = e.target?.result as Buffer;
|
const buffer = e.target?.result as ArrayBuffer;
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
const state = GraphSchema.parse(JSON.parse(buffer.toString()));
|
const state = GraphSchema.parse(JSON.parse(buffer.toString()));
|
||||||
manager.load(state);
|
graph.load(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
@@ -908,10 +901,10 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} />
|
<svelte:window onmousemove={handleMouseMove} onmouseup={handleMouseUp} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
on:wheel={handleMouseScroll}
|
onwheel={handleMouseScroll}
|
||||||
bind:this={wrapper}
|
bind:this={wrapper}
|
||||||
class="graph-wrapper"
|
class="graph-wrapper"
|
||||||
class:is-panning={isPanning}
|
class:is-panning={isPanning}
|
||||||
@@ -921,21 +914,21 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
bind:clientWidth={width}
|
bind:clientWidth={width}
|
||||||
bind:clientHeight={height}
|
bind:clientHeight={height}
|
||||||
on:dragenter={handleDragEnter}
|
ondragenter={handleDragEnter}
|
||||||
on:dragover={handlerDragOver}
|
ondragover={handlerDragOver}
|
||||||
on:dragexit={handleDragEnd}
|
ondragexit={handleDragEnd}
|
||||||
on:drop={handleDrop}
|
ondrop={handleDrop}
|
||||||
on:mouseleave={handleMouseLeave}
|
onmouseleave={handleMouseLeave}
|
||||||
on:keydown={keymap.handleKeyboardEvent}
|
onkeydown={keymap.handleKeyboardEvent}
|
||||||
on:mousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="application/wasm,application/json"
|
accept="application/wasm,application/json"
|
||||||
id="drop-zone"
|
id="drop-zone"
|
||||||
disabled={!isDragging}
|
disabled={!isDragging}
|
||||||
on:dragend={handleDragEnd}
|
ondragend={handleDragEnd}
|
||||||
on:dragleave={handleDragEnd}
|
ondragleave={handleDragEnd}
|
||||||
/>
|
/>
|
||||||
<label for="drop-zone"></label>
|
<label for="drop-zone"></label>
|
||||||
|
|
||||||
@@ -958,9 +951,9 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $status === "idle"}
|
{#if graph.status === "idle"}
|
||||||
{#if addMenuPosition}
|
{#if addMenuPosition}
|
||||||
<AddMenu bind:position={addMenuPosition} graph={manager} />
|
<AddMenu bind:position={addMenuPosition} {graph} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if graphState.activeSocket}
|
{#if graphState.activeSocket}
|
||||||
@@ -974,17 +967,17 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<GraphView {nodes} {edges} {cameraPosition} />
|
<GraphView nodes={graph.nodes} edges={graph.edges} {cameraPosition} />
|
||||||
{:else if $status === "loading"}
|
{:else if graph.status === "loading"}
|
||||||
<span>Loading</span>
|
<span>Loading</span>
|
||||||
{:else if $status === "error"}
|
{:else if graph.status === "error"}
|
||||||
<span>Error</span>
|
<span>Error</span>
|
||||||
{/if}
|
{/if}
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if showHelp}
|
{#if showHelp}
|
||||||
<HelpView registry={manager.registry} />
|
<HelpView registry={graph.registry} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -4,14 +4,13 @@
|
|||||||
import Edge from "../edges/Edge.svelte";
|
import Edge from "../edges/Edge.svelte";
|
||||||
import Node from "../node/Node.svelte";
|
import Node from "../node/Node.svelte";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
import { getGraphState } from "./state.svelte";
|
import { getGraphState } from "./state.svelte";
|
||||||
import { useThrelte } from "@threlte/core";
|
import { useThrelte } from "@threlte/core";
|
||||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodes: Writable<Map<number, NodeType>>;
|
nodes: Map<number, NodeType>;
|
||||||
edges: Writable<EdgeType[]>;
|
edges: EdgeType[];
|
||||||
cameraPosition: [number, number, number];
|
cameraPosition: [number, number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -24,6 +23,8 @@
|
|||||||
invalidate();
|
invalidate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => console.log({ nodes }));
|
||||||
|
|
||||||
const graphState = getGraphState();
|
const graphState = getGraphState();
|
||||||
|
|
||||||
const isNodeInView = getContext<(n: NodeType) => boolean>("isNodeInView");
|
const isNodeInView = getContext<(n: NodeType) => boolean>("isNodeInView");
|
||||||
@@ -33,14 +34,26 @@
|
|||||||
"getSocketPosition",
|
"getSocketPosition",
|
||||||
);
|
);
|
||||||
|
|
||||||
function getEdgePosition(edge: EdgeType) {
|
const edgePositions = $derived(
|
||||||
const pos1 = getSocketPosition(edge[0], edge[1]);
|
edges.map((edge) => {
|
||||||
const pos2 = getSocketPosition(edge[2], edge[3]);
|
const fromNode = nodes.get(edge[0].id);
|
||||||
return [pos1[0], pos1[1], pos2[0], pos2[1]];
|
const toNode = nodes.get(edge[2].id);
|
||||||
}
|
|
||||||
|
// This check is important because nodes might not be there during some transitions.
|
||||||
|
if (!fromNode || !toNode) {
|
||||||
|
return [0, 0, 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos1 = getSocketPosition(fromNode, edge[1]);
|
||||||
|
const pos2 = getSocketPosition(toNode, edge[3]);
|
||||||
|
return [pos1[0], pos1[1], pos2[0], pos2[1]];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const nodeArray = $derived(Array.from(nodes.values()));
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
for (const node of $nodes.values()) {
|
for (const node of nodes.values()) {
|
||||||
if (node?.tmp?.ref) {
|
if (node?.tmp?.ref) {
|
||||||
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
|
||||||
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
|
||||||
@@ -49,9 +62,8 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each $edges as edge (`${edge[0].id}-${edge[1]}-${edge[2].id}-${edge[3]}`)}
|
{#each edgePositions as edge (`${edge.join("-")}`)}
|
||||||
{@const pos = getEdgePosition(edge)}
|
{@const [x1, y1, x2, y2] = edge}
|
||||||
{@const [x1, y1, x2, y2] = pos}
|
|
||||||
<Edge
|
<Edge
|
||||||
z={cameraPosition[2]}
|
z={cameraPosition[2]}
|
||||||
from={{
|
from={{
|
||||||
@@ -74,9 +86,9 @@
|
|||||||
style:transform={`scale(${cameraPosition[2] * 0.1})`}
|
style:transform={`scale(${cameraPosition[2] * 0.1})`}
|
||||||
class:hovering-sockets={graphState.activeSocket}
|
class:hovering-sockets={graphState.activeSocket}
|
||||||
>
|
>
|
||||||
{#each $nodes.values() as node (node.id)}
|
{#each nodeArray as node, i (node.id)}
|
||||||
<Node
|
<Node
|
||||||
{node}
|
bind:node={nodeArray[i]}
|
||||||
inView={cameraPosition && isNodeInView(node)}
|
inView={cameraPosition && isNodeInView(node)}
|
||||||
z={cameraPosition[2]}
|
z={cameraPosition[2]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Graph, Node, NodeRegistry } from "@nodes/types";
|
import type { Graph, Node, NodeRegistry } from "@nodes/types";
|
||||||
import GraphEl from "./Graph.svelte";
|
import GraphEl from "./Graph.svelte";
|
||||||
import { GraphManager } from "../graph-manager.js";
|
import { GraphManager } from "../graph-manager.svelte";
|
||||||
import { setContext } from "svelte";
|
import { setContext } from "svelte";
|
||||||
import { debounce } from "$lib/helpers";
|
import { debounce } from "$lib/helpers";
|
||||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
@@ -53,13 +53,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSettings = debounce((s) => {
|
const updateSettings = debounce((s: Record<string, any>) => {
|
||||||
manager.setSettings(s);
|
manager.setSettings(s);
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (settingTypes && settings) {
|
if (settingTypes && settings) {
|
||||||
updateSettings($state.snapshot(settings));
|
updateSettings(settings);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { GraphManager } from "../graph-manager.js";
|
import type { GraphManager } from "../graph-manager.svelte";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
export function getGraphManager(): GraphManager {
|
export function getGraphManager(): GraphManager {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Socket } from "@nodes/types";
|
import type { Socket } from "@nodes/types";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
import { SvelteSet } from "svelte/reactivity";
|
||||||
|
|
||||||
export function getGraphState() {
|
export function getGraphState() {
|
||||||
return getContext<GraphState>("graphState");
|
return getContext<GraphState>("graphState");
|
||||||
@@ -12,11 +12,10 @@ export class GraphState {
|
|||||||
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[]>([]);
|
||||||
possibleSocketIds = $derived(new Set(
|
possibleSocketIds = $derived(
|
||||||
this.possibleSockets.map((s) => `${s.node.id}-${s.index}`),
|
new Set(this.possibleSockets.map((s) => `${s.node.id}-${s.index}`)),
|
||||||
));
|
);
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.selectedNodes.clear();
|
this.selectedNodes.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
inView: boolean;
|
inView: boolean;
|
||||||
z: number;
|
z: number;
|
||||||
};
|
};
|
||||||
const { node, inView, z }: Props = $props();
|
let { node = $bindable(), inView, z }: Props = $props();
|
||||||
|
|
||||||
const isActive = $derived(graphState.activeNodeId === node.id);
|
const isActive = $derived(graphState.activeNodeId === node.id);
|
||||||
const isSelected = $derived(graphState.selectedNodes.has(node.id));
|
const isSelected = $derived(graphState.selectedNodes.has(node.id));
|
||||||
@@ -41,13 +41,11 @@
|
|||||||
const height = getNodeHeight?.(node.type);
|
const height = getNodeHeight?.(node.type);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
node.tmp = node.tmp || {};
|
|
||||||
node.tmp.mesh = meshRef;
|
node.tmp.mesh = meshRef;
|
||||||
updateNodePosition?.(node);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
node.tmp = node.tmp || {};
|
if (!node.tmp) node.tmp = {};
|
||||||
node.tmp.mesh = meshRef;
|
node.tmp.mesh = meshRef;
|
||||||
updateNodePosition?.(node);
|
updateNodePosition?.(node);
|
||||||
});
|
});
|
||||||
@@ -79,4 +77,4 @@
|
|||||||
/>
|
/>
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
<NodeHtml {node} {inView} {isActive} {isSelected} {z} />
|
<NodeHtml bind:node {inView} {isActive} {isSelected} {z} />
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
{#each parameters as [key, value], i}
|
{#each parameters as [key, value], i}
|
||||||
<NodeParameter
|
<NodeParameter
|
||||||
bind:node
|
{node}
|
||||||
id={key}
|
id={key}
|
||||||
input={value}
|
input={value}
|
||||||
isLast={i == parameters.length - 1}
|
isLast={i == parameters.length - 1}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import type { Node, Socket } from "@nodes/types";
|
import type { Node, Socket } from "@nodes/types";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
export let node: Node;
|
const { node = $bindable<Node>() } = $props();
|
||||||
|
|
||||||
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
||||||
const getSocketPosition =
|
const getSocketPosition =
|
||||||
@@ -33,14 +33,14 @@
|
|||||||
rightBump,
|
rightBump,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
const pathDisabled = createNodePath({
|
// const pathDisabled = createNodePath({
|
||||||
depth: 0,
|
// depth: 0,
|
||||||
height: 15,
|
// height: 15,
|
||||||
y: 50,
|
// y: 50,
|
||||||
cornerTop,
|
// cornerTop,
|
||||||
rightBump,
|
// rightBump,
|
||||||
aspectRatio,
|
// aspectRatio,
|
||||||
});
|
// });
|
||||||
const pathHover = createNodePath({
|
const pathHover = createNodePath({
|
||||||
depth: 8.5,
|
depth: 8.5,
|
||||||
height: 50,
|
height: 50,
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
class="click-target"
|
class="click-target"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:mousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
></div>
|
></div>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { node = $bindable(), input, id, isLast }: Props = $props();
|
const { node, input, id, isLast }: Props = $props();
|
||||||
|
|
||||||
const inputType = node?.tmp?.type?.inputs?.[id]!;
|
const inputType = node?.tmp?.type?.inputs?.[id]!;
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
const graph = getGraphManager();
|
const graph = getGraphManager();
|
||||||
const graphState = getGraphState();
|
const graphState = getGraphState();
|
||||||
const graphId = graph?.id;
|
const graphId = graph?.id;
|
||||||
const inputSockets = graph?.inputSockets;
|
|
||||||
|
|
||||||
const elementId = `input-${Math.random().toString(36).substring(7)}`;
|
const elementId = `input-${Math.random().toString(36).substring(7)}`;
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@
|
|||||||
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={graph.inputSockets?.has(socketId)}>
|
||||||
{#if inputType.label !== ""}
|
{#if inputType.label !== ""}
|
||||||
<label for={elementId}>{input.label || id}</label>
|
<label for={elementId}>{input.label || id}</label>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
|
|
||||||
let index = -1;
|
let index = -1;
|
||||||
let wrapper: HTMLDivElement;
|
let wrapper: HTMLDivElement;
|
||||||
@@ -9,7 +8,7 @@
|
|||||||
index = getContext<() => number>("registerCell")();
|
index = getContext<() => number>("registerCell")();
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizes = getContext<Writable<string[]>>("sizes");
|
const sizes = getContext<string[]>("sizes");
|
||||||
|
|
||||||
let downSizes: string[] = [];
|
let downSizes: string[] = [];
|
||||||
let downWidth = 0;
|
let downWidth = 0;
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
let startX = 0;
|
let startX = 0;
|
||||||
|
|
||||||
function handleMouseDown(event: MouseEvent) {
|
function handleMouseDown(event: MouseEvent) {
|
||||||
downSizes = [...$sizes];
|
downSizes = [...sizes];
|
||||||
mouseDown = true;
|
mouseDown = true;
|
||||||
startX = event.clientX;
|
startX = event.clientX;
|
||||||
downWidth = wrapper.getBoundingClientRect().width;
|
downWidth = wrapper.getBoundingClientRect().width;
|
||||||
@@ -26,8 +25,7 @@
|
|||||||
function handleMouseMove(event: MouseEvent) {
|
function handleMouseMove(event: MouseEvent) {
|
||||||
if (mouseDown) {
|
if (mouseDown) {
|
||||||
const width = downWidth + startX - event.clientX;
|
const width = downWidth + startX - event.clientX;
|
||||||
$sizes[index] = `${width}px`;
|
sizes[index] = `${width}px`;
|
||||||
$sizes = $sizes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext, getContext } from "svelte";
|
import { setContext, getContext } from "svelte";
|
||||||
import localStore from "$lib/helpers/localStore";
|
import { localState } from "$lib/helpers/localState.svelte";
|
||||||
|
|
||||||
const gridId = getContext<string>("grid-id") || "grid-0";
|
const gridId = getContext<string>("grid-id") || "grid-0";
|
||||||
let sizes = localStore<string[]>(gridId, []);
|
let sizes = localState<string[]>(gridId, []);
|
||||||
|
|
||||||
|
const { children } = $props();
|
||||||
|
|
||||||
|
console.log("RowChildren", children);
|
||||||
|
|
||||||
let registerIndex = 0;
|
let registerIndex = 0;
|
||||||
setContext("registerCell", function () {
|
setContext("registerCell", function () {
|
||||||
let index = registerIndex;
|
let index = registerIndex;
|
||||||
registerIndex++;
|
registerIndex++;
|
||||||
if (registerIndex > $sizes.length) {
|
if (registerIndex > sizes.length) {
|
||||||
$sizes = [...$sizes, "1fr"];
|
sizes = [...sizes, "1fr"];
|
||||||
}
|
}
|
||||||
return index;
|
return index;
|
||||||
});
|
});
|
||||||
|
|
||||||
setContext("sizes", sizes);
|
setContext("sizes", sizes);
|
||||||
|
|
||||||
$: cols = $sizes.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" ");
|
const cols = $derived(
|
||||||
|
sizes.map((size, i) => `${i > 0 ? "1px " : ""}` + size).join(" "),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper" style={`grid-template-columns: ${cols};`}>
|
<div class="wrapper" style={`grid-template-columns: ${cols};`}>
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export function localState<T>(key: string, defaultValue: T): T {
|
export function localState<T>(key: string, defaultValue: T): T {
|
||||||
const stored = localStorage.getItem(key)
|
const stored = localStorage.getItem(key);
|
||||||
const state = $state(stored ? JSON.parse(stored) : defaultValue)
|
const state = $state(stored ? JSON.parse(stored) : defaultValue);
|
||||||
$effect.root(() => {
|
$effect.root(() => {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const value = $state.snapshot(state);
|
const value = $state.snapshot(state);
|
||||||
|
|||||||
@@ -7,11 +7,15 @@
|
|||||||
import type { PerspectiveCamera, Vector3Tuple } from "three";
|
import type { PerspectiveCamera, Vector3Tuple } from "three";
|
||||||
import type { OrbitControls as OrbitControlsType } from "three/examples/jsm/controls/OrbitControls.js";
|
import type { OrbitControls as OrbitControlsType } from "three/examples/jsm/controls/OrbitControls.js";
|
||||||
|
|
||||||
let camera: PerspectiveCamera;
|
let camera = $state<PerspectiveCamera>();
|
||||||
let controls: OrbitControlsType;
|
let controls = $state<OrbitControlsType>();
|
||||||
|
|
||||||
export let center: Vector3;
|
type Props = {
|
||||||
export let centerCamera: boolean = true;
|
center: Vector3;
|
||||||
|
centerCamera: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { center, centerCamera }: Props = $props();
|
||||||
|
|
||||||
const cameraTransform = localStore<{
|
const cameraTransform = localStore<{
|
||||||
camera: Vector3Tuple;
|
camera: Vector3Tuple;
|
||||||
@@ -22,7 +26,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function saveCameraState() {
|
function saveCameraState() {
|
||||||
if (!camera) return;
|
if (!camera || !controls) return;
|
||||||
let cPos = camera.position.toArray();
|
let cPos = camera.position.toArray();
|
||||||
let tPos = controls.target.toArray();
|
let tPos = controls.target.toArray();
|
||||||
// check if tPos is NaN or tPos is NaN
|
// check if tPos is NaN or tPos is NaN
|
||||||
@@ -35,6 +39,7 @@
|
|||||||
|
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
const task = useTask(() => {
|
const task = useTask(() => {
|
||||||
|
if (!controls) return;
|
||||||
let length = center.clone().sub(controls.target).length();
|
let length = center.clone().sub(controls.target).length();
|
||||||
if (length < 0.01 || !centerCamera) {
|
if (length < 0.01 || !centerCamera) {
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
@@ -47,22 +52,24 @@
|
|||||||
});
|
});
|
||||||
task.stop();
|
task.stop();
|
||||||
|
|
||||||
$: if (
|
$effect(() => {
|
||||||
center &&
|
if (
|
||||||
controls &&
|
center &&
|
||||||
centerCamera &&
|
controls &&
|
||||||
(center.x !== controls.target.x ||
|
centerCamera &&
|
||||||
center.y !== controls.target.y ||
|
(center.x !== controls.target.x ||
|
||||||
center.z !== controls.target.z) &&
|
center.y !== controls.target.y ||
|
||||||
!isRunning
|
center.z !== controls.target.z) &&
|
||||||
) {
|
!isRunning
|
||||||
isRunning = true;
|
) {
|
||||||
task.start();
|
isRunning = true;
|
||||||
}
|
task.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
controls.target.fromArray($cameraTransform.target);
|
controls?.target.fromArray($cameraTransform.target);
|
||||||
controls.update();
|
controls?.update();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import { localState } from "$lib/helpers/localState.svelte";
|
import { localState } from "$lib/helpers/localState.svelte";
|
||||||
import type { NodeInput } from "@nodes/types";
|
import type { NodeInput } from "@nodes/types";
|
||||||
|
|
||||||
const themes = ["dark", "light", "catppuccin", "solarized", "high-contrast", "nord", "dracula"];
|
const themes = [
|
||||||
|
"dark",
|
||||||
|
"light",
|
||||||
|
"catppuccin",
|
||||||
|
"solarized",
|
||||||
|
"high-contrast",
|
||||||
|
"nord",
|
||||||
|
"dracula",
|
||||||
|
];
|
||||||
|
|
||||||
export const AppSettingTypes = {
|
export const AppSettingTypes = {
|
||||||
theme: {
|
theme: {
|
||||||
@@ -18,28 +26,33 @@ export const AppSettingTypes = {
|
|||||||
centerCamera: {
|
centerCamera: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Center Camera",
|
label: "Center Camera",
|
||||||
value: true
|
value: true,
|
||||||
},
|
},
|
||||||
nodeInterface: {
|
nodeInterface: {
|
||||||
title: "Node Interface",
|
title: "Node Interface",
|
||||||
showNodeGrid: {
|
showNodeGrid: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Show Grid",
|
label: "Show Grid",
|
||||||
value: true
|
value: true,
|
||||||
},
|
},
|
||||||
snapToGrid: {
|
snapToGrid: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Snap to Grid",
|
label: "Snap to Grid",
|
||||||
value: true
|
value: true,
|
||||||
},
|
},
|
||||||
showHelp: {
|
showHelp: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Show Help",
|
label: "Show Help",
|
||||||
value: false
|
value: false,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
title: "Debug",
|
title: "Debug",
|
||||||
|
amount: {
|
||||||
|
type: "number",
|
||||||
|
label: "Amount",
|
||||||
|
value: 4,
|
||||||
|
},
|
||||||
wireframe: {
|
wireframe: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Wireframe",
|
label: "Wireframe",
|
||||||
@@ -81,30 +94,30 @@ export const AppSettingTypes = {
|
|||||||
type: "integer",
|
type: "integer",
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 15,
|
max: 15,
|
||||||
value: 4
|
value: 4,
|
||||||
},
|
},
|
||||||
loadGrid: {
|
loadGrid: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load Grid"
|
label: "Load Grid",
|
||||||
},
|
},
|
||||||
loadTree: {
|
loadTree: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load Tree"
|
label: "Load Tree",
|
||||||
},
|
},
|
||||||
lottaFaces: {
|
lottaFaces: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load 'lots of faces'"
|
label: "Load 'lots of faces'",
|
||||||
},
|
},
|
||||||
lottaNodes: {
|
lottaNodes: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load 'lots of nodes'"
|
label: "Load 'lots of nodes'",
|
||||||
},
|
},
|
||||||
lottaNodesAndFaces: {
|
lottaNodesAndFaces: {
|
||||||
type: "button",
|
type: "button",
|
||||||
label: "Load 'lots of nodes and faces'"
|
label: "Load 'lots of nodes and faces'",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type IsInputDefinition<T> = T extends NodeInput ? T : never;
|
type IsInputDefinition<T> = T extends NodeInput ? T : never;
|
||||||
@@ -113,30 +126,29 @@ type HasTitle = { title: string };
|
|||||||
type Widen<T> = T extends boolean
|
type Widen<T> = T extends boolean
|
||||||
? boolean
|
? boolean
|
||||||
: T extends number
|
: T extends number
|
||||||
? number
|
? number
|
||||||
: T extends string
|
: T extends string
|
||||||
? string
|
? string
|
||||||
: T;
|
: T;
|
||||||
|
|
||||||
|
|
||||||
type ExtractSettingsValues<T> = {
|
type ExtractSettingsValues<T> = {
|
||||||
-readonly [K in keyof T]: T[K] extends HasTitle
|
-readonly [K in keyof T]: T[K] extends HasTitle
|
||||||
? ExtractSettingsValues<Omit<T[K], 'title'>>
|
? ExtractSettingsValues<Omit<T[K], "title">>
|
||||||
: T[K] extends IsInputDefinition<T[K]>
|
: T[K] extends IsInputDefinition<T[K]>
|
||||||
? T[K] extends { value: infer V }
|
? T[K] extends { value: infer V }
|
||||||
? Widen<V>
|
? Widen<V>
|
||||||
: never
|
: never
|
||||||
: T[K] extends Record<string, any>
|
: T[K] extends Record<string, any>
|
||||||
? ExtractSettingsValues<T[K]>
|
? ExtractSettingsValues<T[K]>
|
||||||
: never;
|
: never;
|
||||||
};
|
};
|
||||||
|
|
||||||
function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
|
function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
|
||||||
const result = {} as any;
|
const result = {} as any;
|
||||||
for (const key in settings) {
|
for (const key in settings) {
|
||||||
const value = settings[key];
|
const value = settings[key];
|
||||||
if (value && typeof value === 'object') {
|
if (value && typeof value === "object") {
|
||||||
if ('value' in value) {
|
if ("value" in value) {
|
||||||
result[key] = value.value;
|
result[key] = value.value;
|
||||||
} else {
|
} else {
|
||||||
result[key] = settingsToStore(value);
|
result[key] = settingsToStore(value);
|
||||||
@@ -146,7 +158,10 @@ function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appSettings = localState("app-settings", settingsToStore(AppSettingTypes));
|
export let appSettings = localState(
|
||||||
|
"app-settings",
|
||||||
|
settingsToStore(AppSettingTypes),
|
||||||
|
);
|
||||||
|
|
||||||
$effect.root(() => {
|
$effect.root(() => {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node, NodeInput } from "@nodes/types";
|
import type { Node, NodeInput } from "@nodes/types";
|
||||||
import NestedSettings from "$lib/settings/NestedSettings.svelte";
|
import NestedSettings from "$lib/settings/NestedSettings.svelte";
|
||||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
manager: GraphManager;
|
manager: GraphManager;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "@nodes/types";
|
import type { Node } from "@nodes/types";
|
||||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
|
||||||
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
|
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -26,8 +26,6 @@
|
|||||||
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/sidebar/panels/BenchmarkPanel.svelte";
|
import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte";
|
||||||
import { debounceAsyncFunction } from "$lib/helpers";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
let performanceStore = createPerformanceStore();
|
let performanceStore = createPerformanceStore();
|
||||||
|
|
||||||
@@ -45,14 +43,15 @@
|
|||||||
let activeNode = $state<Node | undefined>(undefined);
|
let activeNode = $state<Node | undefined>(undefined);
|
||||||
let scene = $state<Group>(null!);
|
let scene = $state<Group>(null!);
|
||||||
|
|
||||||
let graph = localStorage.getItem("graph")
|
let graph = $state(
|
||||||
? JSON.parse(localStorage.getItem("graph")!)
|
localStorage.getItem("graph")
|
||||||
: templates.defaultPlant;
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
|
: templates.defaultPlant,
|
||||||
|
);
|
||||||
|
|
||||||
let graphInterface = $state<ReturnType<typeof GraphInterface>>(null!);
|
let graphInterface = $state<ReturnType<typeof GraphInterface>>(null!);
|
||||||
let viewerComponent = $state<ReturnType<typeof Viewer>>();
|
let viewerComponent = $state<ReturnType<typeof Viewer>>();
|
||||||
const manager = $derived(graphInterface?.manager);
|
const manager = $derived(graphInterface?.manager);
|
||||||
const managerStatus = $derived(manager?.status);
|
|
||||||
|
|
||||||
async function randomGenerate() {
|
async function randomGenerate() {
|
||||||
if (!manager) return;
|
if (!manager) return;
|
||||||
@@ -69,74 +68,86 @@
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let graphSettings = $state<Record<string, any>>({});
|
let graphSettings = $state<Record<string, any>>({});
|
||||||
let graphSettingTypes = $state({
|
type BooleanSchema = {
|
||||||
|
[key: string]: {
|
||||||
|
type: "boolean";
|
||||||
|
value: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let graphSettingTypes = $state<BooleanSchema>({
|
||||||
randomSeed: { type: "boolean", value: false },
|
randomSeed: { type: "boolean", value: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleUpdate = debounceAsyncFunction(
|
let runIndex = 0;
|
||||||
async (g: Graph, s: Record<string, any> = graphSettings) => {
|
const handleUpdate = async (
|
||||||
performanceStore.startRun();
|
g: Graph,
|
||||||
try {
|
s: Record<string, any> = graphSettings,
|
||||||
let a = performance.now();
|
) => {
|
||||||
const graphResult = await runtime.execute(g, $state.snapshot(s));
|
runIndex++;
|
||||||
let b = performance.now();
|
performanceStore.startRun();
|
||||||
|
try {
|
||||||
|
let a = performance.now();
|
||||||
|
const graphResult = await runtime.execute(
|
||||||
|
$state.snapshot(g),
|
||||||
|
$state.snapshot(s),
|
||||||
|
);
|
||||||
|
let b = performance.now();
|
||||||
|
|
||||||
if (appSettings.debug.useWorker) {
|
if (appSettings.debug.useWorker) {
|
||||||
let perfData = await runtime.getPerformanceData();
|
let perfData = await runtime.getPerformanceData();
|
||||||
let lastRun = perfData?.at(-1);
|
let lastRun = perfData?.at(-1);
|
||||||
if (lastRun?.total) {
|
if (lastRun?.total) {
|
||||||
lastRun.runtime = lastRun.total;
|
lastRun.runtime = lastRun.total;
|
||||||
delete lastRun.total;
|
delete lastRun.total;
|
||||||
performanceStore.mergeData(lastRun);
|
performanceStore.mergeData(lastRun);
|
||||||
performanceStore.addPoint(
|
performanceStore.addPoint(
|
||||||
"worker-transfer",
|
"worker-transfer",
|
||||||
b - a - lastRun.runtime[0],
|
b - a - lastRun.runtime[0],
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewerComponent?.update(graphResult);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("errors", error);
|
|
||||||
} finally {
|
|
||||||
performanceStore.stopRun();
|
|
||||||
}
|
}
|
||||||
},
|
viewerComponent?.update(graphResult);
|
||||||
);
|
} catch (error) {
|
||||||
|
console.log("errors", error);
|
||||||
|
} finally {
|
||||||
|
performanceStore.stopRun();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// $ if (AppSettings) {
|
$effect(() => {
|
||||||
// //@ts-ignore
|
//@ts-ignore
|
||||||
// AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
||||||
// graph = templates.grid($AppSettings.amount, $AppSettings.amount);
|
graph = templates.grid(
|
||||||
// };
|
appSettings.debug.amount.value,
|
||||||
// //@ts-ignore
|
appSettings.debug.amount.value,
|
||||||
// AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
);
|
||||||
// graph = templates.tree($AppSettings.amount);
|
};
|
||||||
// };
|
//@ts-ignore
|
||||||
// //@ts-ignore
|
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
||||||
// AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
graph = templates.tree(appSettings.debug.amount.value);
|
||||||
// graph = templates.lottaFaces;
|
};
|
||||||
// };
|
//@ts-ignore
|
||||||
// //@ts-ignore
|
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
||||||
// AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
graph = templates.lottaFaces;
|
||||||
// graph = templates.lottaNodes;
|
};
|
||||||
// };
|
//@ts-ignore
|
||||||
// //@ts-ignore
|
AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
||||||
// AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
graph = templates.lottaNodes;
|
||||||
// graph = templates.lottaNodesAndFaces;
|
};
|
||||||
// };
|
//@ts-ignore
|
||||||
// }
|
AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
||||||
|
graph = templates.lottaNodesAndFaces;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
function handleSave(graph: Graph) {
|
function handleSave(graph: Graph) {
|
||||||
localStorage.setItem("graph", JSON.stringify(graph));
|
localStorage.setItem("graph", JSON.stringify(graph));
|
||||||
}
|
}
|
||||||
onMount(() => {
|
|
||||||
handleUpdate(graph);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document on:keydown={applicationKeymap.handleKeyboardEvent} />
|
<svelte:document onkeydown={applicationKeymap.handleKeyboardEvent} />
|
||||||
<div class="wrapper manager-{$managerStatus}">
|
|
||||||
|
<div class="wrapper manager-{manager?.status}">
|
||||||
<header></header>
|
<header></header>
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
@@ -148,93 +159,91 @@
|
|||||||
/>
|
/>
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
{#key graph}
|
<GraphInterface
|
||||||
<GraphInterface
|
{graph}
|
||||||
bind:this={graphInterface}
|
bind:this={graphInterface}
|
||||||
{graph}
|
registry={nodeRegistry}
|
||||||
registry={nodeRegistry}
|
showGrid={appSettings.nodeInterface.showNodeGrid}
|
||||||
showGrid={appSettings.nodeInterface.showNodeGrid}
|
snapToGrid={appSettings.nodeInterface.snapToGrid}
|
||||||
snapToGrid={appSettings.nodeInterface.snapToGrid}
|
bind:activeNode
|
||||||
bind:activeNode
|
bind:showHelp={appSettings.nodeInterface.showHelp}
|
||||||
bind:showHelp={appSettings.nodeInterface.showHelp}
|
bind:settings={graphSettings}
|
||||||
bind:settings={graphSettings}
|
bind:settingTypes={graphSettingTypes}
|
||||||
bind:settingTypes={graphSettingTypes}
|
onresult={(result) => handleUpdate(result)}
|
||||||
onresult={(result) => handleUpdate(result)}
|
onsave={(graph) => handleSave(graph)}
|
||||||
onsave={(graph) => handleSave(graph)}
|
/>
|
||||||
/>
|
<Sidebar>
|
||||||
<Sidebar>
|
<Panel id="general" title="General" icon="i-tabler-settings">
|
||||||
<Panel id="general" title="General" icon="i-tabler-settings">
|
<NestedSettings
|
||||||
<NestedSettings
|
id="general"
|
||||||
id="general"
|
value={appSettings}
|
||||||
value={appSettings}
|
type={AppSettingTypes}
|
||||||
type={AppSettingTypes}
|
/>
|
||||||
/>
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="shortcuts"
|
||||||
id="shortcuts"
|
title="Keyboard Shortcuts"
|
||||||
title="Keyboard Shortcuts"
|
icon="i-tabler-keyboard"
|
||||||
icon="i-tabler-keyboard"
|
>
|
||||||
>
|
<Keymap
|
||||||
<Keymap
|
keymaps={[
|
||||||
keymaps={[
|
{ keymap: applicationKeymap, title: "Application" },
|
||||||
{ keymap: applicationKeymap, title: "Application" },
|
{ keymap: graphInterface.keymap, title: "Node-Editor" },
|
||||||
{ keymap: graphInterface.keymap, title: "Node-Editor" },
|
]}
|
||||||
]}
|
/>
|
||||||
/>
|
</Panel>
|
||||||
</Panel>
|
<Panel id="exports" title="Exporter" icon="i-tabler-package-export">
|
||||||
<Panel id="exports" title="Exporter" icon="i-tabler-package-export">
|
<ExportSettings {scene} />
|
||||||
<ExportSettings {scene} />
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="node-store"
|
||||||
id="node-store"
|
classes="text-green-400"
|
||||||
classes="text-green-400"
|
title="Node Store"
|
||||||
title="Node Store"
|
icon="i-tabler-database"
|
||||||
icon="i-tabler-database"
|
>
|
||||||
>
|
<NodeStore registry={nodeRegistry} />
|
||||||
<NodeStore registry={nodeRegistry} />
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="performance"
|
||||||
id="performance"
|
title="Performance"
|
||||||
title="Performance"
|
classes="text-red-400"
|
||||||
classes="text-red-400"
|
hidden={!appSettings.debug.showPerformancePanel}
|
||||||
hidden={!appSettings.debug.showPerformancePanel}
|
icon="i-tabler-brand-speedtest"
|
||||||
icon="i-tabler-brand-speedtest"
|
>
|
||||||
>
|
{#if $performanceStore}
|
||||||
{#if $performanceStore}
|
<PerformanceViewer data={$performanceStore} />
|
||||||
<PerformanceViewer data={$performanceStore} />
|
{/if}
|
||||||
{/if}
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="benchmark"
|
||||||
id="benchmark"
|
title="Benchmark"
|
||||||
title="Benchmark"
|
classes="text-red-400"
|
||||||
classes="text-red-400"
|
hidden={!appSettings.debug.showBenchmarkPanel}
|
||||||
hidden={!appSettings.debug.showBenchmarkPanel}
|
icon="i-tabler-graph"
|
||||||
icon="i-tabler-graph"
|
>
|
||||||
>
|
<BenchmarkPanel run={randomGenerate} />
|
||||||
<BenchmarkPanel run={randomGenerate} />
|
</Panel>
|
||||||
</Panel>
|
<Panel
|
||||||
<Panel
|
id="graph-settings"
|
||||||
|
title="Graph Settings"
|
||||||
|
classes="text-blue-400"
|
||||||
|
icon="i-custom-graph"
|
||||||
|
>
|
||||||
|
<NestedSettings
|
||||||
id="graph-settings"
|
id="graph-settings"
|
||||||
title="Graph Settings"
|
type={graphSettingTypes}
|
||||||
classes="text-blue-400"
|
bind:value={graphSettings}
|
||||||
icon="i-custom-graph"
|
/>
|
||||||
>
|
</Panel>
|
||||||
<NestedSettings
|
<Panel
|
||||||
id="graph-settings"
|
id="active-node"
|
||||||
type={graphSettingTypes}
|
title="Node Settings"
|
||||||
bind:value={graphSettings}
|
classes="text-blue-400"
|
||||||
/>
|
icon="i-tabler-adjustments"
|
||||||
</Panel>
|
>
|
||||||
<Panel
|
<ActiveNodeSettings {manager} node={activeNode} />
|
||||||
id="active-node"
|
</Panel>
|
||||||
title="Node Settings"
|
</Sidebar>
|
||||||
classes="text-blue-400"
|
|
||||||
icon="i-tabler-adjustments"
|
|
||||||
>
|
|
||||||
<ActiveNodeSettings {manager} node={activeNode} />
|
|
||||||
</Panel>
|
|
||||||
</Sidebar>
|
|
||||||
{/key}
|
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
decodeFloat,
|
|
||||||
encodeFloat,
|
|
||||||
decodeNestedArray,
|
|
||||||
encodeNestedArray,
|
|
||||||
concatEncodedArrays,
|
|
||||||
} from "@nodes/utils";
|
|
||||||
|
|
||||||
console.clear();
|
|
||||||
|
|
||||||
{
|
|
||||||
const encodedPositions = new Int32Array([
|
|
||||||
encodeFloat(1.1),
|
|
||||||
encodeFloat(2.0),
|
|
||||||
encodeFloat(3.0),
|
|
||||||
encodeFloat(4.0),
|
|
||||||
encodeFloat(5.0),
|
|
||||||
encodeFloat(6.0),
|
|
||||||
encodeFloat(7.0),
|
|
||||||
encodeFloat(8.0),
|
|
||||||
encodeFloat(9.0),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create a Float32Array using the same buffer that backs the Int32Array
|
|
||||||
const floatView = new Float32Array(encodedPositions.buffer);
|
|
||||||
console.log({ encodedPositions, floatView });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
const input_a = encodeNestedArray([1, 2, 3]);
|
|
||||||
const input_b = 2;
|
|
||||||
const input_c = 89;
|
|
||||||
const input_d = encodeNestedArray([4, 5, 6]);
|
|
||||||
|
|
||||||
const output = concatNestedArrays([input_a, input_b, input_c, input_d]);
|
|
||||||
|
|
||||||
const decoded = decodeNestedArray(output);
|
|
||||||
console.log("CONCAT", [input_a, input_b, input_c, input_d]);
|
|
||||||
console.log(output);
|
|
||||||
console.log(decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
let maxError = 0;
|
|
||||||
new Array(10_000).fill(null).forEach((v, i) => {
|
|
||||||
const input = i < 5_000 ? i : Math.random() * 100;
|
|
||||||
const encoded = encodeFloat(input);
|
|
||||||
const output = decodeFloat(encoded[0], encoded[1]);
|
|
||||||
|
|
||||||
const error = Math.abs(input - output);
|
|
||||||
if (error > maxError) {
|
|
||||||
maxError = error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("DECODE FLOAT");
|
|
||||||
console.log(maxError);
|
|
||||||
console.log(encodeFloat(2.0));
|
|
||||||
console.log("----");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
console.log("Turning Int32Array into Array");
|
|
||||||
const test_size = 2_000_000;
|
|
||||||
const a = new Int32Array(test_size);
|
|
||||||
let t0 = performance.now();
|
|
||||||
for (let i = 0; i < test_size; i++) {
|
|
||||||
a[i] = Math.floor(Math.random() * 100);
|
|
||||||
}
|
|
||||||
console.log("TIME", performance.now() - t0);
|
|
||||||
t0 = performance.now();
|
|
||||||
const b = [...a.slice(0, test_size)];
|
|
||||||
console.log("TIME", performance.now() - t0);
|
|
||||||
console.log(typeof b, Array.isArray(b), b instanceof Int32Array);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false) {
|
|
||||||
// const input = [5, [6, 1], [7, 2, [5, 1]]];
|
|
||||||
// const input = [5, [], [6, []], []];
|
|
||||||
// const input = [52];
|
|
||||||
const input = [0, 0, [0, 2, 0, 128, 0, 128], 0, 128];
|
|
||||||
|
|
||||||
console.log("INPUT");
|
|
||||||
console.log(input);
|
|
||||||
|
|
||||||
let encoded = encodeNestedArray(input);
|
|
||||||
// encoded = [];
|
|
||||||
console.log("ENCODED");
|
|
||||||
console.log(encoded);
|
|
||||||
|
|
||||||
encoded = [0, 2, 1, 0, 4, 4, 2, 4, 1, 2, 2, 0, 3, 2, 3, 1, 1, 1, 1];
|
|
||||||
|
|
||||||
const decoded = decodeNestedArray(encoded);
|
|
||||||
console.log("DECODED");
|
|
||||||
console.log(decoded);
|
|
||||||
|
|
||||||
console.log("EQUALS", JSON.stringify(input) === JSON.stringify(decoded));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Graph, NodeDefinition, NodeId } from "./types";
|
import { Graph, NodeDefinition, NodeType } from "./types";
|
||||||
|
|
||||||
export interface NodeRegistry {
|
export interface NodeRegistry {
|
||||||
/**
|
/**
|
||||||
@@ -13,13 +13,13 @@ export interface NodeRegistry {
|
|||||||
* @throws An error if the nodes could not be loaded
|
* @throws An error if the nodes could not be loaded
|
||||||
* @remarks This method should be called before calling getNode or getAllNodes
|
* @remarks This method should be called before calling getNode or getAllNodes
|
||||||
*/
|
*/
|
||||||
load: (nodeIds: NodeId[]) => Promise<NodeDefinition[]>;
|
load: (nodeIds: NodeType[]) => Promise<NodeDefinition[]>;
|
||||||
/**
|
/**
|
||||||
* Get a node by id
|
* Get a node by id
|
||||||
* @param id - The id of the node to get
|
* @param id - The id of the node to get
|
||||||
* @returns The node with the given id, or undefined if no such node exists
|
* @returns The node with the given id, or undefined if no such node exists
|
||||||
*/
|
*/
|
||||||
getNode: (id: NodeId | string) => NodeDefinition | undefined;
|
getNode: (id: NodeType | string) => NodeDefinition | undefined;
|
||||||
/**
|
/**
|
||||||
* Get all nodes
|
* Get all nodes
|
||||||
* @returns An array of all nodes
|
* @returns An array of all nodes
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ export type {
|
|||||||
Node,
|
Node,
|
||||||
NodeDefinition,
|
NodeDefinition,
|
||||||
Socket,
|
Socket,
|
||||||
NodeType as NodeId,
|
NodeType,
|
||||||
Edge,
|
Edge,
|
||||||
Graph,
|
Graph,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
export { NodeSchema, GraphSchema, NodeType } from "./types";
|
export { NodeSchema, GraphSchema } from "./types";
|
||||||
export { NodeDefinitionSchema } from "./types";
|
export { NodeDefinitionSchema } from "./types";
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export type Node = {
|
|||||||
} & z.infer<typeof NodeSchema>;
|
} & z.infer<typeof NodeSchema>;
|
||||||
|
|
||||||
export const NodeDefinitionSchema = z.object({
|
export const NodeDefinitionSchema = z.object({
|
||||||
id: z.string(),
|
id: NodeTypeSchema,
|
||||||
inputs: z.record(z.string(), NodeInputSchema).optional(),
|
inputs: z.record(z.string(), NodeInputSchema).optional(),
|
||||||
outputs: z.array(z.string()).optional(),
|
outputs: z.array(z.string()).optional(),
|
||||||
meta: z
|
meta: z
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
ctrl?: boolean;
|
ctrl?: boolean;
|
||||||
shift?: boolean;
|
shift?: boolean;
|
||||||
alt?: boolean;
|
alt?: boolean;
|
||||||
key: string;
|
key: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
Reference in New Issue
Block a user