html-overlay-node 0.1.6 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html-overlay-node.es.js +379 -802
- package/dist/html-overlay-node.es.js.map +1 -1
- package/dist/html-overlay-node.umd.js +1 -1
- package/dist/html-overlay-node.umd.js.map +1 -1
- package/package.json +9 -8
- package/src/core/Edge.js +4 -2
- package/src/core/Node.js +27 -11
- package/src/core/Runner.js +32 -18
- package/src/defaults/contextMenu.js +102 -0
- package/src/defaults/index.js +6 -0
- package/src/index.js +70 -795
- package/src/interact/ContextMenu.js +5 -1
- package/src/interact/Controller.js +66 -46
- package/src/nodes/core.js +288 -0
- package/src/nodes/index.js +42 -0
- package/src/nodes/logic.js +57 -0
- package/src/nodes/math.js +86 -0
- package/src/nodes/util.js +134 -0
- package/src/nodes/value.js +116 -0
- package/src/render/CanvasRenderer.js +180 -80
- package/src/render/HtmlOverlay.js +14 -4
- package/src/render/hitTest.js +14 -8
- package/src/utils/utils.js +4 -4
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "html-overlay-node",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "LiteGraph-style node editor with HTML overlay support",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"module": "./src/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
".": "./src/index.js",
|
|
10
|
+
"./nodes": "./src/nodes/index.js",
|
|
11
|
+
"./defaults": "./src/defaults/index.js",
|
|
12
|
+
"./index.css": "./index.css",
|
|
13
|
+
"./src/ui/PropertyPanel.css": "./src/ui/PropertyPanel.css"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
15
16
|
"dist",
|
package/src/core/Edge.js
CHANGED
|
@@ -16,8 +16,10 @@ export class Edge {
|
|
|
16
16
|
* @param {string} options.toPort - Target port ID
|
|
17
17
|
*/
|
|
18
18
|
constructor({ id, fromNode, fromPort, toNode, toPort }) {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// Allow empty strings for port names (exec ports use empty names)
|
|
20
|
+
// Only check for null/undefined
|
|
21
|
+
if (fromNode == null || fromPort == null || toNode == null || toPort == null) {
|
|
22
|
+
throw new Error("Edge requires fromNode, fromPort, toNode, and toPort (null/undefined not allowed)");
|
|
21
23
|
}
|
|
22
24
|
this.id = id ?? randomUUID();
|
|
23
25
|
this.fromNode = fromNode;
|
package/src/core/Node.js
CHANGED
|
@@ -43,28 +43,44 @@ export class Node {
|
|
|
43
43
|
* @param {string} [portType="data"] - Port type: "exec" or "data"
|
|
44
44
|
* @returns {Object} The created port
|
|
45
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* Recalculate minimum size based on ports
|
|
48
|
+
*/
|
|
49
|
+
_updateMinSize() {
|
|
50
|
+
const HEADER_HEIGHT = 28;
|
|
51
|
+
const PORT_SPACING = 24;
|
|
52
|
+
const BOTTOM_PADDING = 10;
|
|
53
|
+
|
|
54
|
+
// Calculate required height for inputs and outputs
|
|
55
|
+
const inHeight = HEADER_HEIGHT + 10 + this.inputs.length * PORT_SPACING + BOTTOM_PADDING;
|
|
56
|
+
const outHeight = HEADER_HEIGHT + 10 + this.outputs.length * PORT_SPACING + BOTTOM_PADDING;
|
|
57
|
+
|
|
58
|
+
const minHeight = Math.max(inHeight, outHeight, 60); // Minimum 60px base
|
|
59
|
+
|
|
60
|
+
if (this.size.height < minHeight) {
|
|
61
|
+
this.size.height = minHeight;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
46
65
|
addInput(name, datatype = "any", portType = "data") {
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
// ... existing validation ...
|
|
67
|
+
if (typeof name !== "string" || (portType === "data" && !name)) {
|
|
68
|
+
throw new Error("Input port name must be a string (non-empty for data ports)");
|
|
49
69
|
}
|
|
50
70
|
const port = { id: randomUUID(), name, datatype, portType, dir: "in" };
|
|
51
71
|
this.inputs.push(port);
|
|
72
|
+
this._updateMinSize();
|
|
52
73
|
return port;
|
|
53
74
|
}
|
|
54
75
|
|
|
55
|
-
/**
|
|
56
|
-
* Add an output port to this node
|
|
57
|
-
* @param {string} name - Port name
|
|
58
|
-
* @param {string} [datatype="any"] - Data type for the port
|
|
59
|
-
* @param {string} [portType="data"] - Port type: "exec" or "data"
|
|
60
|
-
* @returns {Object} The created port
|
|
61
|
-
*/
|
|
62
76
|
addOutput(name, datatype = "any", portType = "data") {
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
// ... existing validation ...
|
|
78
|
+
if (typeof name !== "string" || (portType === "data" && !name)) {
|
|
79
|
+
throw new Error("Output port name must be a string (non-empty for data ports)");
|
|
65
80
|
}
|
|
66
81
|
const port = { id: randomUUID(), name, datatype, portType, dir: "out" };
|
|
67
82
|
this.outputs.push(port);
|
|
83
|
+
this._updateMinSize();
|
|
68
84
|
return port;
|
|
69
85
|
}
|
|
70
86
|
}
|
package/src/core/Runner.js
CHANGED
|
@@ -54,6 +54,7 @@ export class Runner {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Execute connected nodes once from a starting node
|
|
57
|
+
* Uses queue-based traversal to support branching exec flows
|
|
57
58
|
* @param {string} startNodeId - ID of the node to start from
|
|
58
59
|
* @param {number} dt - Delta time
|
|
59
60
|
*/
|
|
@@ -62,14 +63,21 @@ export class Runner {
|
|
|
62
63
|
|
|
63
64
|
const executedNodes = [];
|
|
64
65
|
const allConnectedNodes = new Set();
|
|
65
|
-
|
|
66
|
+
const queue = [startNodeId];
|
|
67
|
+
const visited = new Set(); // Prevent infinite loops
|
|
68
|
+
|
|
69
|
+
// Queue-based traversal for branching execution
|
|
70
|
+
while (queue.length > 0) {
|
|
71
|
+
const currentNodeId = queue.shift();
|
|
72
|
+
|
|
73
|
+
// Skip if already executed (prevents cycles)
|
|
74
|
+
if (visited.has(currentNodeId)) continue;
|
|
75
|
+
visited.add(currentNodeId);
|
|
66
76
|
|
|
67
|
-
// Follow exec flow
|
|
68
|
-
while (currentNodeId) {
|
|
69
77
|
const node = this.graph.nodes.get(currentNodeId);
|
|
70
78
|
if (!node) {
|
|
71
79
|
console.warn(`[Runner.runOnce] Node not found: ${currentNodeId}`);
|
|
72
|
-
|
|
80
|
+
continue;
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
executedNodes.push(currentNodeId);
|
|
@@ -96,8 +104,9 @@ export class Runner {
|
|
|
96
104
|
// Execute current node
|
|
97
105
|
this.executeNode(currentNodeId, dt);
|
|
98
106
|
|
|
99
|
-
// Find next
|
|
100
|
-
|
|
107
|
+
// Find all next nodes via exec outputs and add to queue
|
|
108
|
+
const nextNodes = this.findAllNextExecNodes(currentNodeId);
|
|
109
|
+
queue.push(...nextNodes);
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
console.log("[Runner.runOnce] Executed nodes:", executedNodes.length);
|
|
@@ -115,26 +124,31 @@ export class Runner {
|
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
/**
|
|
118
|
-
* Find
|
|
127
|
+
* Find all nodes connected via exec outputs
|
|
128
|
+
* Supports multiple connections from a single exec output
|
|
119
129
|
* @param {string} nodeId - Current node ID
|
|
120
|
-
* @returns {string
|
|
130
|
+
* @returns {string[]} Array of next node IDs
|
|
121
131
|
*/
|
|
122
|
-
|
|
132
|
+
findAllNextExecNodes(nodeId) {
|
|
123
133
|
const node = this.graph.nodes.get(nodeId);
|
|
124
|
-
if (!node) return
|
|
134
|
+
if (!node) return [];
|
|
125
135
|
|
|
126
|
-
// Find exec output
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
136
|
+
// Find all exec output ports
|
|
137
|
+
const execOutputs = node.outputs.filter(p => p.portType === "exec");
|
|
138
|
+
if (execOutputs.length === 0) return [];
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
const nextNodes = [];
|
|
141
|
+
|
|
142
|
+
// Find all edges from exec outputs
|
|
143
|
+
for (const execOutput of execOutputs) {
|
|
144
|
+
for (const edge of this.graph.edges.values()) {
|
|
145
|
+
if (edge.fromNode === nodeId && edge.fromPort === execOutput.id) {
|
|
146
|
+
nextNodes.push(edge.toNode);
|
|
147
|
+
}
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
|
|
137
|
-
return
|
|
151
|
+
return nextNodes;
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
/**
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Context Menu Setup
|
|
3
|
+
*
|
|
4
|
+
* This module provides the default context menu configuration.
|
|
5
|
+
* Users can import and use this directly, modify it, or create their own.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { setupDefaultContextMenu } from "html-overlay-node/defaults";
|
|
9
|
+
* setupDefaultContextMenu(editor.contextMenu, { controller, graph, hooks });
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { RemoveNodeCmd, ChangeGroupColorCmd } from "../core/commands.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Setup default context menu items
|
|
16
|
+
* @param {ContextMenu} contextMenu - Context menu instance
|
|
17
|
+
* @param {Object} options - Configuration options
|
|
18
|
+
* @param {Controller} options.controller - Controller instance
|
|
19
|
+
* @param {Graph} options.graph - Graph instance
|
|
20
|
+
* @param {Hooks} options.hooks - Hooks instance
|
|
21
|
+
*/
|
|
22
|
+
export function setupDefaultContextMenu(contextMenu, { controller, graph, hooks }) {
|
|
23
|
+
// Add Node submenu (canvas background only)
|
|
24
|
+
// Use a function to dynamically generate node types when menu is shown
|
|
25
|
+
const getNodeTypes = () => {
|
|
26
|
+
const nodeTypes = [];
|
|
27
|
+
for (const [key, typeDef] of graph.registry.types.entries()) {
|
|
28
|
+
nodeTypes.push({
|
|
29
|
+
id: `add-${key}`,
|
|
30
|
+
label: typeDef.title || key,
|
|
31
|
+
action: () => {
|
|
32
|
+
// Get world position from context menu
|
|
33
|
+
const worldPos = contextMenu.worldPosition || { x: 100, y: 100 };
|
|
34
|
+
|
|
35
|
+
// Add node at click position
|
|
36
|
+
const node = graph.addNode(key, {
|
|
37
|
+
x: worldPos.x,
|
|
38
|
+
y: worldPos.y,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
hooks?.emit("node:updated", node);
|
|
42
|
+
controller.render(); // Update minimap and canvas
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return nodeTypes;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
contextMenu.addItem("add-node", "Add Node", {
|
|
50
|
+
condition: (target) => !target,
|
|
51
|
+
submenu: getNodeTypes, // Pass function instead of array
|
|
52
|
+
order: 5,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Delete Node (for all nodes except groups)
|
|
56
|
+
contextMenu.addItem("delete-node", "Delete Node", {
|
|
57
|
+
condition: (target) => target && target.type !== "core/Group",
|
|
58
|
+
action: (target) => {
|
|
59
|
+
const cmd = RemoveNodeCmd(graph, target);
|
|
60
|
+
controller.stack.exec(cmd);
|
|
61
|
+
hooks?.emit("node:updated", target);
|
|
62
|
+
},
|
|
63
|
+
order: 10,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Change Group Color (for groups only) - with submenu
|
|
67
|
+
const colors = [
|
|
68
|
+
{ name: "Default", color: "#39424e" },
|
|
69
|
+
{ name: "Slate", color: "#4a5568" },
|
|
70
|
+
{ name: "Gray", color: "#2d3748" },
|
|
71
|
+
{ name: "Blue", color: "#1a365d" },
|
|
72
|
+
{ name: "Green", color: "#22543d" },
|
|
73
|
+
{ name: "Red", color: "#742a2a" },
|
|
74
|
+
{ name: "Purple", color: "#44337a" },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
contextMenu.addItem("change-group-color", "Change Color", {
|
|
78
|
+
condition: (target) => target && target.type === "core/Group",
|
|
79
|
+
submenu: colors.map((colorInfo) => ({
|
|
80
|
+
id: `color-${colorInfo.color}`,
|
|
81
|
+
label: colorInfo.name,
|
|
82
|
+
color: colorInfo.color,
|
|
83
|
+
action: (target) => {
|
|
84
|
+
const currentColor = target.state.color || "#39424e";
|
|
85
|
+
const cmd = ChangeGroupColorCmd(target, currentColor, colorInfo.color);
|
|
86
|
+
controller.stack.exec(cmd);
|
|
87
|
+
hooks?.emit("node:updated", target);
|
|
88
|
+
},
|
|
89
|
+
})),
|
|
90
|
+
order: 20,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
contextMenu.addItem("delete-group", "Delete Group", {
|
|
94
|
+
condition: (target) => target && target.type === "core/Group",
|
|
95
|
+
action: (target) => {
|
|
96
|
+
const cmd = RemoveNodeCmd(graph, target);
|
|
97
|
+
controller.stack.exec(cmd);
|
|
98
|
+
hooks?.emit("node:updated", target);
|
|
99
|
+
},
|
|
100
|
+
order: 20,
|
|
101
|
+
});
|
|
102
|
+
}
|