feat: initial auto connect nodes
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m35s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m35s
This commit is contained in:
@@ -1,21 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
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 { Node, NodeType } from "@nodes/types";
|
||||||
|
import { getGraphState } from "./graph/state.svelte";
|
||||||
|
import { getGraphManager } from "./graph/context";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
position: [x: number, y: number] | null;
|
position: [x: number, y: number] | null;
|
||||||
graph: GraphManager;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let { position = $bindable(), graph }: Props = $props();
|
const graph = getGraphManager();
|
||||||
|
const graphState = getGraphState();
|
||||||
|
|
||||||
|
let { position = $bindable() }: Props = $props();
|
||||||
|
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement;
|
||||||
let value = $state<string>();
|
let value = $state<string>();
|
||||||
let activeNodeId = $state<NodeType>();
|
let activeNodeId = $state<NodeType>();
|
||||||
|
|
||||||
const allNodes = graph.getNodeDefinitions();
|
const allNodes = graphState.activeSocket
|
||||||
|
? graph.getPossibleNodes(graphState.activeSocket)
|
||||||
|
: graph.getNodeDefinitions();
|
||||||
|
|
||||||
function filterNodes() {
|
function filterNodes() {
|
||||||
return allNodes.filter((node) => node.id.includes(value ?? ""));
|
return allNodes.filter((node) => node.id.includes(value ?? ""));
|
||||||
@@ -25,7 +30,7 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (nodes) {
|
if (nodes) {
|
||||||
if (activeNodeId === undefined) {
|
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);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -35,6 +40,28 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleNodeCreation(nodeType: Node["type"]) {
|
||||||
|
if (!position) return;
|
||||||
|
|
||||||
|
const newNode = graph.createNode({
|
||||||
|
type: nodeType,
|
||||||
|
position,
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const edgeInputSocket = graphState.activeSocket;
|
||||||
|
if (edgeInputSocket && newNode) {
|
||||||
|
if (typeof edgeInputSocket.index === "number") {
|
||||||
|
graph.smartConnect(edgeInputSocket.node, newNode);
|
||||||
|
} else {
|
||||||
|
graph.smartConnect(newNode, edgeInputSocket.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
graphState.activeSocket = null;
|
||||||
|
position = null;
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
@@ -57,8 +84,7 @@
|
|||||||
|
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
if (activeNodeId && position) {
|
if (activeNodeId && position) {
|
||||||
graph.createNode({ type: activeNodeId, position, props: {} });
|
handleNodeCreation(activeNodeId);
|
||||||
position = null;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,18 +121,10 @@
|
|||||||
aria-selected={node.id === activeNodeId}
|
aria-selected={node.id === activeNodeId}
|
||||||
onkeydown={(event) => {
|
onkeydown={(event) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
if (position) {
|
handleNodeCreation(node.id);
|
||||||
graph.createNode({ type: node.id, position, props: {} });
|
|
||||||
position = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onmousedown={() => {
|
|
||||||
if (position) {
|
|
||||||
graph.createNode({ type: node.id, position, props: {} });
|
|
||||||
position = null;
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onmousedown={() => handleNodeCreation(node.id)}
|
||||||
onfocus={() => {
|
onfocus={() => {
|
||||||
activeNodeId = node.id;
|
activeNodeId = node.id;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type {
|
|||||||
Edge,
|
Edge,
|
||||||
Graph,
|
Graph,
|
||||||
Node,
|
Node,
|
||||||
|
NodeDefinition,
|
||||||
NodeInput,
|
NodeInput,
|
||||||
NodeRegistry,
|
NodeRegistry,
|
||||||
NodeType,
|
NodeType,
|
||||||
@@ -15,7 +16,7 @@ import throttle from "$lib/helpers/throttle";
|
|||||||
import { HistoryManager } from "./history-manager";
|
import { HistoryManager } from "./history-manager";
|
||||||
|
|
||||||
const logger = createLogger("graph-manager");
|
const logger = createLogger("graph-manager");
|
||||||
// logger.mute();
|
logger.mute();
|
||||||
|
|
||||||
const clone =
|
const clone =
|
||||||
"structuredClone" in self
|
"structuredClone" in self
|
||||||
@@ -361,6 +362,20 @@ export class GraphManager extends EventEmitter<{
|
|||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smartConnect(from: Node, to: Node): Edge | undefined {
|
||||||
|
const inputs = Object.entries(to.tmp?.type?.inputs ?? {});
|
||||||
|
const outputs = from.tmp?.type?.outputs ?? [];
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
const [inputName, input] = inputs[0];
|
||||||
|
for (let o = 0; o < outputs.length; o++) {
|
||||||
|
const output = outputs[0];
|
||||||
|
if (input.type === output) {
|
||||||
|
return this.createEdge(from, o, to, inputName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createNodeId() {
|
createNodeId() {
|
||||||
const max = Math.max(0, ...this.nodes.keys());
|
const max = Math.max(0, ...this.nodes.keys());
|
||||||
return max + 1;
|
return max + 1;
|
||||||
@@ -370,10 +385,9 @@ export class GraphManager extends EventEmitter<{
|
|||||||
// map old ids to new ids
|
// map old ids to new ids
|
||||||
const idMap = new Map<number, number>();
|
const idMap = new Map<number, number>();
|
||||||
|
|
||||||
const startId = this.createNodeId();
|
|
||||||
|
|
||||||
nodes = nodes.map((node, i) => {
|
nodes = nodes.map((node, i) => {
|
||||||
const id = startId + i;
|
const id = this.createNodeId();
|
||||||
idMap.set(node.id, id);
|
idMap.set(node.id, id);
|
||||||
const type = this.registry.getNode(node.type);
|
const type = this.registry.getNode(node.type);
|
||||||
if (!type) {
|
if (!type) {
|
||||||
@@ -437,6 +451,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
this.nodes.set(node.id, node);
|
this.nodes.set(node.id, node);
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
createEdge(
|
createEdge(
|
||||||
@@ -445,7 +461,10 @@ export class GraphManager extends EventEmitter<{
|
|||||||
to: Node,
|
to: Node,
|
||||||
toSocket: string,
|
toSocket: string,
|
||||||
{ applyUpdate = true } = {},
|
{ applyUpdate = true } = {},
|
||||||
) {
|
): Edge | undefined {
|
||||||
|
|
||||||
|
console.log("Create Edge", from.type, fromSocket, to.type, toSocket)
|
||||||
|
|
||||||
const existingEdges = this.getEdgesToNode(to);
|
const existingEdges = this.getEdgesToNode(to);
|
||||||
|
|
||||||
// check if this exact edge already exists
|
// check if this exact edge already exists
|
||||||
@@ -464,6 +483,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || []));
|
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || []));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log({ fromSocketType, toSocket, toType: to?.tmp?.type, toSocketType });
|
||||||
|
|
||||||
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Socket types do not match: ${fromSocketType} !== ${toSocketType}`,
|
`Socket types do not match: ${fromSocketType} !== ${toSocketType}`,
|
||||||
@@ -478,11 +499,9 @@ export class GraphManager extends EventEmitter<{
|
|||||||
this.removeEdge(edgeToBeReplaced, { applyDeletion: false });
|
this.removeEdge(edgeToBeReplaced, { applyDeletion: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyUpdate) {
|
const edge = [from, fromSocket, to, toSocket] as Edge;
|
||||||
this.edges.push([from, fromSocket, to, toSocket]);
|
|
||||||
} else {
|
this.edges.push(edge);
|
||||||
this.edges.push([from, fromSocket, to, toSocket]);
|
|
||||||
}
|
|
||||||
|
|
||||||
from.tmp = from.tmp || {};
|
from.tmp = from.tmp || {};
|
||||||
from.tmp.children = from.tmp.children || [];
|
from.tmp.children = from.tmp.children || [];
|
||||||
@@ -496,6 +515,8 @@ export class GraphManager extends EventEmitter<{
|
|||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
this.execute();
|
this.execute();
|
||||||
|
|
||||||
|
return edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
@@ -547,6 +568,33 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return parents.reverse();
|
return parents.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPossibleNodes(socket: Socket): NodeDefinition[] {
|
||||||
|
const allDefinitions = this.getNodeDefinitions();
|
||||||
|
|
||||||
|
const nodeType = socket.node.tmp?.type;
|
||||||
|
if (!nodeType) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof socket.index === "string") {
|
||||||
|
// if index is a string, we are an input looking for outputs
|
||||||
|
return allDefinitions.filter(s => {
|
||||||
|
return s.outputs?.find(_s => Object
|
||||||
|
.values(nodeType?.inputs || {})
|
||||||
|
.map(s => s.type)
|
||||||
|
.includes(_s as NodeInput["type"])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// if index is a number, we are an output looking for inputs
|
||||||
|
return allDefinitions.filter(s => Object
|
||||||
|
.values(s.inputs ?? {})
|
||||||
|
.map(s => s.type)
|
||||||
|
.find(s => nodeType?.outputs?.includes(s))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
|
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
|
||||||
const nodeType = node?.tmp?.type;
|
const nodeType = node?.tmp?.type;
|
||||||
if (!nodeType) return [];
|
if (!nodeType) return [];
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
let boxSelection = $state(false);
|
let boxSelection = $state(false);
|
||||||
const cameraDown = [0, 0];
|
const cameraDown = [0, 0];
|
||||||
let cameraPosition: [number, number, number] = $state([0, 0, 4]);
|
let cameraPosition: [number, number, number] = $state([0, 0, 4]);
|
||||||
|
let edgeEndPosition = $state<[number, number] | null>();
|
||||||
let addMenuPosition = $state<[number, number] | null>(null);
|
let addMenuPosition = $state<[number, number] | null>(null);
|
||||||
let clipboard: null | {
|
let clipboard: null | {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
@@ -465,6 +466,8 @@
|
|||||||
n.tmp.downY = n.position[1];
|
n.tmp.downY = n.position[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
edgeEndPosition = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyNodes() {
|
function copyNodes() {
|
||||||
@@ -478,17 +481,15 @@
|
|||||||
.filter(Boolean) as Node[];
|
.filter(Boolean) as Node[];
|
||||||
|
|
||||||
const _edges = graph.getEdgesBetweenNodes(_nodes);
|
const _edges = graph.getEdgesBetweenNodes(_nodes);
|
||||||
|
_nodes = $state.snapshot(
|
||||||
_nodes = _nodes.map((_node) => {
|
_nodes.map((_node) => ({
|
||||||
const node = globalThis.structuredClone({
|
|
||||||
..._node,
|
..._node,
|
||||||
tmp: {
|
tmp: {
|
||||||
downX: mousePosition[0] - _node.position[0],
|
downX: mousePosition[0] - _node.position[0],
|
||||||
downY: mousePosition[1] - _node.position[1],
|
downY: mousePosition[1] - _node.position[1],
|
||||||
},
|
},
|
||||||
});
|
})),
|
||||||
return node;
|
);
|
||||||
});
|
|
||||||
|
|
||||||
clipboard = {
|
clipboard = {
|
||||||
nodes: _nodes,
|
nodes: _nodes,
|
||||||
@@ -532,6 +533,18 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "f",
|
||||||
|
description: "Smart Connect Nodes",
|
||||||
|
callback: () => {
|
||||||
|
const nodes = [...graphState.selectedNodes.values()]
|
||||||
|
.map((g) => graph.getNode(g))
|
||||||
|
.filter((n) => !!n);
|
||||||
|
const edge = graph.smartConnect(nodes[0], nodes[1]);
|
||||||
|
if (!edge) graph.smartConnect(nodes[1], nodes[0]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "?",
|
key: "?",
|
||||||
description: "Toggle Help",
|
description: "Toggle Help",
|
||||||
@@ -561,6 +574,7 @@
|
|||||||
callback: () => {
|
callback: () => {
|
||||||
graphState.activeNodeId = -1;
|
graphState.activeNodeId = -1;
|
||||||
graphState.clearSelection();
|
graphState.clearSelection();
|
||||||
|
edgeEndPosition = null;
|
||||||
(document.activeElement as HTMLElement)?.blur();
|
(document.activeElement as HTMLElement)?.blur();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -778,6 +792,16 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
graph.save();
|
graph.save();
|
||||||
|
} else if (graphState.activeSocket && event.ctrlKey) {
|
||||||
|
// Handle automatic adding of nodes on ctrl+mouseUp
|
||||||
|
edgeEndPosition = [mousePosition[0], mousePosition[1]];
|
||||||
|
|
||||||
|
if (typeof graphState.activeSocket.index === "number") {
|
||||||
|
addMenuPosition = [mousePosition[0], mousePosition[1] - 3];
|
||||||
|
} else {
|
||||||
|
addMenuPosition = [mousePosition[0] - 20, mousePosition[1] - 3];
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if camera moved
|
// check if camera moved
|
||||||
@@ -953,7 +977,7 @@
|
|||||||
|
|
||||||
{#if graph.status === "idle"}
|
{#if graph.status === "idle"}
|
||||||
{#if addMenuPosition}
|
{#if addMenuPosition}
|
||||||
<AddMenu bind:position={addMenuPosition} {graph} />
|
<AddMenu bind:position={addMenuPosition} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if graphState.activeSocket}
|
{#if graphState.activeSocket}
|
||||||
@@ -963,7 +987,10 @@
|
|||||||
x: graphState.activeSocket.position[0],
|
x: graphState.activeSocket.position[0],
|
||||||
y: graphState.activeSocket.position[1],
|
y: graphState.activeSocket.position[1],
|
||||||
}}
|
}}
|
||||||
to={{ x: mousePosition[0], y: mousePosition[1] }}
|
to={{
|
||||||
|
x: edgeEndPosition?.[0] ?? mousePosition[0],
|
||||||
|
y: edgeEndPosition?.[1] ?? mousePosition[1],
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { clone } from "./helpers/index.js";
|
|||||||
import { createLogger } from "@nodes/utils";
|
import { createLogger } from "@nodes/utils";
|
||||||
|
|
||||||
const diff = create({
|
const diff = create({
|
||||||
objectHash: function (obj, index) {
|
objectHash: function(obj, index) {
|
||||||
if (obj === null) return obj;
|
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;
|
||||||
@@ -16,7 +16,7 @@ const diff = create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const log = createLogger("history");
|
const log = createLogger("history");
|
||||||
// log.mute();
|
log.mute();
|
||||||
|
|
||||||
export class HistoryManager {
|
export class HistoryManager {
|
||||||
index: number = -1;
|
index: number = -1;
|
||||||
|
|||||||
@@ -25,7 +25,10 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr on:click={() => ($open.runtime = !$open.runtime)}>
|
<tr
|
||||||
|
style="cursor:pointer;"
|
||||||
|
on:click={() => ($open.runtime = !$open.runtime)}
|
||||||
|
>
|
||||||
<td>{$open.runtime ? "-" : "+"} runtime </td>
|
<td>{$open.runtime ? "-" : "+"} runtime </td>
|
||||||
<td>{humanizeDuration(runtime || 1000)}</td>
|
<td>{humanizeDuration(runtime || 1000)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -37,7 +40,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<tr on:click={() => ($open.fps = !$open.fps)}>
|
<tr style="cursor:pointer;" on:click={() => ($open.fps = !$open.fps)}>
|
||||||
<td>{$open.fps ? "-" : "+"} fps </td>
|
<td>{$open.fps ? "-" : "+"} fps </td>
|
||||||
<td>
|
<td>
|
||||||
{Math.floor(fps[fps.length - 1])}fps
|
{Math.floor(fps[fps.length - 1])}fps
|
||||||
@@ -74,9 +77,6 @@
|
|||||||
border: solid thin var(--outline);
|
border: solid thin var(--outline);
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
tr {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
td {
|
td {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
padding-inline: 8px;
|
padding-inline: 8px;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "@nodes/utils";
|
} from "@nodes/utils";
|
||||||
|
|
||||||
const log = createLogger("runtime-executor");
|
const log = createLogger("runtime-executor");
|
||||||
// log.mute();
|
log.mute();
|
||||||
|
|
||||||
function getValue(input: NodeInput, value?: unknown) {
|
function getValue(input: NodeInput, value?: unknown) {
|
||||||
if (value === undefined && "value" in input) {
|
if (value === undefined && "value" in input) {
|
||||||
@@ -65,7 +65,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
constructor(
|
constructor(
|
||||||
private registry: NodeRegistry,
|
private registry: NodeRegistry,
|
||||||
private cache?: SyncCache<Int32Array>,
|
private cache?: SyncCache<Int32Array>,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
private async getNodeDefinitions(graph: Graph) {
|
private async getNodeDefinitions(graph: Graph) {
|
||||||
if (this.registry.status !== "ready") {
|
if (this.registry.status !== "ready") {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
{type[key].label || key}
|
{type[key].label || key}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
{#if type[key]?.label !== false}
|
{#if type[key].label !== ""}
|
||||||
<label for={id}>{type[key].label || key}</label>
|
<label for={id}>{type[key].label || key}</label>
|
||||||
{/if}
|
{/if}
|
||||||
<Input {id} input={type[key]} bind:value={internalValue} />
|
<Input {id} input={type[key]} bind:value={internalValue} />
|
||||||
@@ -196,6 +196,10 @@
|
|||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { localState } from "$lib/helpers/localState.svelte";
|
import { localState } from "$lib/helpers/localState.svelte";
|
||||||
import type { NodeInput } from "@nodes/types";
|
|
||||||
import type { SettingsType } from ".";
|
|
||||||
|
|
||||||
const themes = [
|
const themes = [
|
||||||
"dark",
|
"dark",
|
||||||
@@ -10,7 +8,7 @@ const themes = [
|
|||||||
"high-contrast",
|
"high-contrast",
|
||||||
"nord",
|
"nord",
|
||||||
"dracula",
|
"dracula",
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
export const AppSettingTypes = {
|
export const AppSettingTypes = {
|
||||||
theme: {
|
theme: {
|
||||||
@@ -49,11 +47,6 @@ export const AppSettingTypes = {
|
|||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
title: "Debug",
|
title: "Debug",
|
||||||
amount: {
|
|
||||||
type: "number",
|
|
||||||
label: "Amount",
|
|
||||||
value: 4,
|
|
||||||
},
|
|
||||||
wireframe: {
|
wireframe: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Wireframe",
|
label: "Wireframe",
|
||||||
@@ -89,14 +82,6 @@ export const AppSettingTypes = {
|
|||||||
label: "Show Stem Lines",
|
label: "Show Stem Lines",
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
logging: {
|
|
||||||
title: "Logging",
|
|
||||||
logLevel: {
|
|
||||||
type: "select",
|
|
||||||
label: false,
|
|
||||||
options: ["info","warning","error"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stressTest: {
|
stressTest: {
|
||||||
title: "Stress Test",
|
title: "Stress Test",
|
||||||
amount: {
|
amount: {
|
||||||
@@ -127,32 +112,23 @@ export const AppSettingTypes = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const satisfies SettingsType;
|
} as const;
|
||||||
|
|
||||||
type IsInputDefinition<T> = T extends NodeInput ? T : never;
|
type SettingsToStore<T> =
|
||||||
type HasTitle = { title: string };
|
T extends { value: infer V }
|
||||||
|
? V extends readonly string[]
|
||||||
type Widen<T> = T extends boolean
|
? V[number]
|
||||||
? boolean
|
: V
|
||||||
: T extends number
|
: T extends any[]
|
||||||
? number
|
? {}
|
||||||
: T extends string
|
: T extends object
|
||||||
? string
|
? {
|
||||||
: T;
|
[K in keyof T as T[K] extends object ? K : never]:
|
||||||
|
SettingsToStore<T[K]>
|
||||||
type ExtractSettingsValues<T> = {
|
}
|
||||||
-readonly [K in keyof T]: T[K] extends HasTitle
|
|
||||||
? ExtractSettingsValues<Omit<T[K], "title">>
|
|
||||||
: T[K] extends IsInputDefinition<T[K]>
|
|
||||||
? T[K] extends { value: infer V }
|
|
||||||
? Widen<V>
|
|
||||||
: never
|
|
||||||
: T[K] extends Record<string, any>
|
|
||||||
? ExtractSettingsValues<T[K]>
|
|
||||||
: never;
|
: never;
|
||||||
};
|
|
||||||
|
|
||||||
export function settingsToStore<T>(settings: T): ExtractSettingsValues<T> {
|
export function settingsToStore<T>(settings: T): SettingsToStore<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];
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: solid thin var(--outline);
|
border-bottom: solid thin var(--outline);
|
||||||
border-left: solid thin var(--outline);
|
border-left: solid thin var(--outline);
|
||||||
background: var(--layer-0);
|
background: var(--layer-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs > button > span {
|
.tabs > button > span {
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tabs > button.active {
|
.tabs > button.active {
|
||||||
background: var(--layer-1);
|
background: var(--layer-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs > button.active span {
|
.tabs > button.active span {
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
? JSON.parse(localStorage.getItem("graph")!)
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
: templates.defaultPlant,
|
: templates.defaultPlant,
|
||||||
);
|
);
|
||||||
|
function handleSave(graph: Graph) {
|
||||||
|
localStorage.setItem("graph", JSON.stringify(graph));
|
||||||
|
}
|
||||||
|
|
||||||
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>>();
|
||||||
@@ -122,32 +125,30 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
||||||
graph = templates.grid(
|
manager.load(
|
||||||
appSettings.value.debug.amount.value,
|
templates.grid(
|
||||||
appSettings.value.debug.amount.value,
|
appSettings.value.debug.stressTest.amount,
|
||||||
|
appSettings.value.debug.stressTest.amount,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
||||||
graph = templates.tree(appSettings.value.debug.amount.value);
|
manager.load(templates.tree(appSettings.value.debug.stressTest.amount));
|
||||||
};
|
};
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
||||||
graph = templates.lottaFaces;
|
manager.load(templates.lottaFaces as unknown as Graph);
|
||||||
};
|
};
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
||||||
graph = templates.lottaNodes;
|
manager.load(templates.lottaNodes as unknown as Graph);
|
||||||
};
|
};
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
||||||
graph = templates.lottaNodesAndFaces;
|
manager.load(templates.lottaNodesAndFaces as unknown as Graph);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleSave(graph: Graph) {
|
|
||||||
localStorage.setItem("graph", JSON.stringify(graph));
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document onkeydown={applicationKeymap.handleKeyboardEvent} />
|
<svelte:document onkeydown={applicationKeymap.handleKeyboardEvent} />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import { createLogger, createWasmWrapper } from "@nodes/utils";
|
import { createLogger, createWasmWrapper } from "@nodes/utils";
|
||||||
|
|
||||||
const log = createLogger("node-registry");
|
const log = createLogger("node-registry");
|
||||||
// log.mute();
|
log.mute();
|
||||||
|
|
||||||
export class RemoteNodeRegistry implements NodeRegistry {
|
export class RemoteNodeRegistry implements NodeRegistry {
|
||||||
status: "loading" | "ready" | "error" = "loading";
|
status: "loading" | "ready" | "error" = "loading";
|
||||||
|
|||||||
Reference in New Issue
Block a user