html-overlay-node 0.1.9 → 0.1.11
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/example.json +9 -9
- package/dist/html-overlay-node.es.js +1000 -321
- 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/dist/img/favicon.svg +1 -0
- package/index.css +436 -232
- package/package.json +1 -1
- package/readme.md +143 -440
- package/src/core/Graph.js +34 -5
- package/src/core/Runner.js +188 -54
- package/src/index.js +29 -26
- package/src/interact/Controller.js +35 -6
- package/src/nodes/core.js +55 -77
- package/src/nodes/logic.js +51 -48
- package/src/nodes/math.js +23 -8
- package/src/nodes/util.js +238 -131
- package/src/nodes/value.js +87 -102
- package/src/render/CanvasRenderer.js +465 -285
- package/src/render/HtmlOverlay.js +65 -3
- package/src/render/hitTest.js +5 -2
- package/src/ui/HelpOverlay.js +158 -0
- package/src/ui/PropertyPanel.css +58 -27
- package/src/ui/PropertyPanel.js +441 -268
package/src/nodes/core.js
CHANGED
|
@@ -7,7 +7,8 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
7
7
|
// Note Node
|
|
8
8
|
registry.register("core/Note", {
|
|
9
9
|
title: "Note",
|
|
10
|
-
|
|
10
|
+
color: "#10b981", // info (emerald)
|
|
11
|
+
size: { w: 180 },
|
|
11
12
|
inputs: [{ name: "in", datatype: "any" }],
|
|
12
13
|
outputs: [{ name: "out", datatype: "any" }],
|
|
13
14
|
onCreate(node) {
|
|
@@ -26,43 +27,27 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
26
27
|
// HTML Note Node
|
|
27
28
|
registry.register("core/HtmlNote", {
|
|
28
29
|
title: "HTML Note",
|
|
29
|
-
|
|
30
|
+
color: "#3b82f6", // data (blue)
|
|
31
|
+
size: { w: 220 },
|
|
30
32
|
inputs: [{ name: "in", datatype: "any" }],
|
|
31
33
|
outputs: [{ name: "out", datatype: "any" }],
|
|
32
34
|
|
|
33
35
|
html: {
|
|
34
|
-
init(node, el, {
|
|
35
|
-
el.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
header.textContent = "My HTML Node";
|
|
46
|
-
|
|
47
|
-
body.style.padding = "8px";
|
|
48
|
-
body.style.color = "#ccc";
|
|
49
|
-
body.style.fontSize = "12px";
|
|
50
|
-
|
|
51
|
-
const contentDiv = document.createElement("div");
|
|
52
|
-
contentDiv.textContent = "Event Name";
|
|
53
|
-
body.appendChild(contentDiv);
|
|
36
|
+
init(node, el, { body }) {
|
|
37
|
+
el.classList.add("node-overlay");
|
|
38
|
+
|
|
39
|
+
body.style.display = "flex";
|
|
40
|
+
body.style.flexDirection = "column";
|
|
41
|
+
body.style.gap = "8px";
|
|
42
|
+
|
|
43
|
+
const label = document.createElement("label");
|
|
44
|
+
label.className = "premium-label";
|
|
45
|
+
label.textContent = "Data Input";
|
|
46
|
+
body.appendChild(label);
|
|
54
47
|
|
|
55
48
|
const input = document.createElement("input");
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
padding: "4px",
|
|
59
|
-
background: "#111",
|
|
60
|
-
border: "1px solid #555",
|
|
61
|
-
color: "#fff",
|
|
62
|
-
borderRadius: "4px",
|
|
63
|
-
pointerEvents: "auto",
|
|
64
|
-
});
|
|
65
|
-
input.placeholder = "Type here...";
|
|
49
|
+
input.className = "premium-input";
|
|
50
|
+
input.placeholder = "Type message...";
|
|
66
51
|
input.addEventListener("input", (e) => {
|
|
67
52
|
node.state.text = e.target.value;
|
|
68
53
|
});
|
|
@@ -72,10 +57,8 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
72
57
|
el._input = input;
|
|
73
58
|
},
|
|
74
59
|
|
|
75
|
-
update(node, el,
|
|
76
|
-
|
|
77
|
-
header.style.backgroundColor = selected ? "#3a4a5a" : "#333";
|
|
78
|
-
|
|
60
|
+
update(node, el, _opts) {
|
|
61
|
+
// Selection is handled by the canvas renderer
|
|
79
62
|
if (el._input.value !== (node.state.text || "")) {
|
|
80
63
|
el._input.value = node.state.text || "";
|
|
81
64
|
}
|
|
@@ -93,46 +76,33 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
93
76
|
|
|
94
77
|
// Todo List Node (HTML Overlay)
|
|
95
78
|
registry.register("core/TodoNode", {
|
|
96
|
-
title: "
|
|
79
|
+
title: "Task list",
|
|
80
|
+
color: "#10b981", // info (emerald)
|
|
97
81
|
size: { w: 240, h: 300 },
|
|
98
82
|
inputs: [{ name: "in", datatype: "any" }],
|
|
99
83
|
outputs: [{ name: "out", datatype: "any" }],
|
|
100
84
|
html: {
|
|
101
|
-
init(node, el, {
|
|
102
|
-
el.
|
|
103
|
-
el.style.borderRadius = "8px";
|
|
104
|
-
el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.5)";
|
|
105
|
-
el.style.border = "1px solid #333";
|
|
106
|
-
|
|
107
|
-
header.style.backgroundColor = "#2a2a31";
|
|
108
|
-
header.style.padding = "8px";
|
|
109
|
-
header.style.fontWeight = "bold";
|
|
110
|
-
header.style.color = "#e9e9ef";
|
|
111
|
-
header.textContent = node.title;
|
|
85
|
+
init(node, el, { body }) {
|
|
86
|
+
el.classList.add("node-overlay");
|
|
112
87
|
|
|
113
88
|
body.style.display = "flex";
|
|
114
89
|
body.style.flexDirection = "column";
|
|
115
|
-
|
|
116
|
-
|
|
90
|
+
|
|
91
|
+
const label = document.createElement("label");
|
|
92
|
+
label.className = "premium-label";
|
|
93
|
+
label.textContent = "New Task";
|
|
94
|
+
body.appendChild(label);
|
|
117
95
|
|
|
118
96
|
const inputRow = document.createElement("div");
|
|
119
|
-
Object.assign(inputRow.style, { display: "flex", gap: "
|
|
97
|
+
Object.assign(inputRow.style, { display: "flex", gap: "6px", marginBottom: "12px" });
|
|
120
98
|
|
|
121
99
|
const input = document.createElement("input");
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
border: "1px solid #444", background: "#141417", color: "#fff",
|
|
125
|
-
pointerEvents: "auto",
|
|
126
|
-
});
|
|
127
|
-
input.placeholder = "Add task...";
|
|
100
|
+
input.className = "premium-input";
|
|
101
|
+
input.placeholder = "What needs to be done?";
|
|
128
102
|
|
|
129
103
|
const addBtn = document.createElement("button");
|
|
130
|
-
addBtn.
|
|
131
|
-
|
|
132
|
-
padding: "0 12px", cursor: "pointer", background: "#4f5b66",
|
|
133
|
-
color: "#fff", border: "none", borderRadius: "4px",
|
|
134
|
-
pointerEvents: "auto",
|
|
135
|
-
});
|
|
104
|
+
addBtn.className = "premium-button";
|
|
105
|
+
addBtn.textContent = "Add";
|
|
136
106
|
|
|
137
107
|
inputRow.append(input, addBtn);
|
|
138
108
|
|
|
@@ -162,9 +132,8 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
162
132
|
|
|
163
133
|
el._refs = { list };
|
|
164
134
|
},
|
|
165
|
-
update(node, el,
|
|
166
|
-
|
|
167
|
-
|
|
135
|
+
update(node, el, _opts) {
|
|
136
|
+
// Selection is handled by the canvas renderer
|
|
168
137
|
const { list } = el._refs;
|
|
169
138
|
const todos = node.state.todos || [];
|
|
170
139
|
|
|
@@ -173,14 +142,17 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
173
142
|
const li = document.createElement("li");
|
|
174
143
|
Object.assign(li.style, {
|
|
175
144
|
display: "flex", alignItems: "center", padding: "6px 0",
|
|
176
|
-
borderBottom: "1px solid
|
|
145
|
+
borderBottom: "1px solid rgba(255,255,255,0.03)"
|
|
177
146
|
});
|
|
178
147
|
|
|
179
148
|
const chk = document.createElement("input");
|
|
180
149
|
chk.type = "checkbox";
|
|
181
150
|
chk.checked = todo.done;
|
|
182
|
-
chk.style
|
|
183
|
-
|
|
151
|
+
Object.assign(chk.style, {
|
|
152
|
+
marginRight: "8px",
|
|
153
|
+
accentColor: "#5568d0",
|
|
154
|
+
pointerEvents: "auto",
|
|
155
|
+
});
|
|
184
156
|
chk.onchange = () => {
|
|
185
157
|
todo.done = chk.checked;
|
|
186
158
|
hooks.emit("node:updated", node);
|
|
@@ -190,16 +162,20 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
190
162
|
const span = document.createElement("span");
|
|
191
163
|
span.textContent = todo.text;
|
|
192
164
|
span.style.flex = "1";
|
|
165
|
+
span.style.fontSize = "11px";
|
|
193
166
|
span.style.textDecoration = todo.done ? "line-through" : "none";
|
|
194
|
-
span.style.color = todo.done ? "#
|
|
167
|
+
span.style.color = todo.done ? "#404060" : "#8888a8";
|
|
195
168
|
|
|
196
169
|
const del = document.createElement("button");
|
|
197
170
|
del.textContent = "×";
|
|
198
171
|
Object.assign(del.style, {
|
|
199
|
-
background: "none", border: "none", color: "#
|
|
200
|
-
cursor: "pointer", fontSize: "
|
|
172
|
+
background: "none", border: "none", color: "#4a3a4a",
|
|
173
|
+
cursor: "pointer", fontSize: "14px",
|
|
201
174
|
pointerEvents: "auto",
|
|
175
|
+
transition: "color 0.12s ease",
|
|
202
176
|
});
|
|
177
|
+
del.addEventListener("mouseover", () => { del.style.color = "#ff4d4d"; });
|
|
178
|
+
del.addEventListener("mouseout", () => { del.style.color = "#4a3a4a"; });
|
|
203
179
|
del.onclick = () => {
|
|
204
180
|
node.state.todos = node.state.todos.filter((t) => t.id !== todo.id);
|
|
205
181
|
hooks.emit("node:updated", node);
|
|
@@ -222,13 +198,15 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
222
198
|
// Group Node
|
|
223
199
|
registry.register("core/Group", {
|
|
224
200
|
title: "Group",
|
|
201
|
+
color: "#475569", // group (slate)
|
|
225
202
|
size: { w: 240, h: 160 },
|
|
226
203
|
onDraw(node, { ctx, theme, renderer }) {
|
|
227
204
|
const { x, y, w, h } = node.computed;
|
|
228
205
|
const headerH = 24;
|
|
229
|
-
const color = node.state.color || "#39424e";
|
|
230
|
-
const bgAlpha = 0.
|
|
206
|
+
const color = node.state.color || node.color || "#39424e";
|
|
207
|
+
const bgAlpha = 0.4;
|
|
231
208
|
const textColor = theme.text || "#e9e9ef";
|
|
209
|
+
const r = 4; // Groups can be slightly softer but still sharp
|
|
232
210
|
|
|
233
211
|
const rgba = (hex, a) => {
|
|
234
212
|
const c = hex.replace("#", "");
|
|
@@ -260,12 +238,12 @@ export function registerCoreNodes(registry, hooks) {
|
|
|
260
238
|
};
|
|
261
239
|
|
|
262
240
|
ctx.fillStyle = rgba(color, bgAlpha);
|
|
263
|
-
roundRect(ctx, x, y, w, h,
|
|
241
|
+
roundRect(ctx, x, y, w, h, r);
|
|
264
242
|
ctx.fill();
|
|
265
243
|
|
|
266
|
-
ctx.fillStyle = rgba(color, 0.
|
|
244
|
+
ctx.fillStyle = rgba(color, 0.2);
|
|
267
245
|
ctx.beginPath();
|
|
268
|
-
ctx.roundRect(x, y, w, headerH, [
|
|
246
|
+
ctx.roundRect(x, y, w, headerH, [r, r, 0, 0]);
|
|
269
247
|
ctx.fill();
|
|
270
248
|
|
|
271
249
|
// Use screen-coordinate text rendering for consistent scale
|
package/src/nodes/logic.js
CHANGED
|
@@ -4,54 +4,57 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export function registerLogicNodes(registry) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
7
|
+
// AND Node
|
|
8
|
+
registry.register("logic/AND", {
|
|
9
|
+
title: "AND",
|
|
10
|
+
color: "#a855f7", // logic (purple)
|
|
11
|
+
size: { w: 120 },
|
|
12
|
+
inputs: [
|
|
13
|
+
{ name: "exec", portType: "exec" },
|
|
14
|
+
{ name: "a", portType: "data", datatype: "boolean" },
|
|
15
|
+
{ name: "b", portType: "data", datatype: "boolean" },
|
|
16
|
+
],
|
|
17
|
+
outputs: [
|
|
18
|
+
{ name: "exec", portType: "exec" },
|
|
19
|
+
{ name: "result", portType: "data", datatype: "boolean" },
|
|
20
|
+
],
|
|
21
|
+
onExecute(node, { getInput, setOutput }) {
|
|
22
|
+
const a = getInput("a") ?? false;
|
|
23
|
+
const b = getInput("b") ?? false;
|
|
24
|
+
console.log("[AND] Inputs - a:", a, "b:", b);
|
|
25
|
+
const result = a && b;
|
|
26
|
+
console.log("[AND] Result:", result);
|
|
27
|
+
setOutput("result", result);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
31
|
+
// OR Node
|
|
32
|
+
registry.register("logic/OR", {
|
|
33
|
+
title: "OR",
|
|
34
|
+
color: "#a855f7", // logic (purple)
|
|
35
|
+
size: { w: 120 },
|
|
36
|
+
inputs: [
|
|
37
|
+
{ name: "a", datatype: "boolean" },
|
|
38
|
+
{ name: "b", datatype: "boolean" },
|
|
39
|
+
],
|
|
40
|
+
outputs: [{ name: "result", datatype: "boolean" }],
|
|
41
|
+
onExecute(node, { getInput, setOutput }) {
|
|
42
|
+
const a = getInput("a") ?? false;
|
|
43
|
+
const b = getInput("b") ?? false;
|
|
44
|
+
setOutput("result", a || b);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
48
|
+
// NOT Node
|
|
49
|
+
registry.register("logic/NOT", {
|
|
50
|
+
title: "NOT",
|
|
51
|
+
color: "#a855f7", // logic (purple)
|
|
52
|
+
size: { w: 120 },
|
|
53
|
+
inputs: [{ name: "in", datatype: "boolean" }],
|
|
54
|
+
outputs: [{ name: "out", datatype: "boolean" }],
|
|
55
|
+
onExecute(node, { getInput, setOutput }) {
|
|
56
|
+
const val = getInput("in") ?? false;
|
|
57
|
+
setOutput("out", !val);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
57
60
|
}
|
package/src/nodes/math.js
CHANGED
|
@@ -7,7 +7,8 @@ export function registerMathNodes(registry) {
|
|
|
7
7
|
// Add Node
|
|
8
8
|
registry.register("math/Add", {
|
|
9
9
|
title: "Add",
|
|
10
|
-
|
|
10
|
+
color: "#f43f5e", // math (rose)
|
|
11
|
+
size: { w: 140 },
|
|
11
12
|
inputs: [
|
|
12
13
|
{ name: "exec", portType: "exec" },
|
|
13
14
|
{ name: "a", portType: "data", datatype: "number" },
|
|
@@ -22,18 +23,24 @@ export function registerMathNodes(registry) {
|
|
|
22
23
|
node.state.b = 0;
|
|
23
24
|
},
|
|
24
25
|
onExecute(node, { getInput, setOutput }) {
|
|
25
|
-
const a = getInput("a") ?? 0;
|
|
26
|
-
const b = getInput("b") ?? 0;
|
|
26
|
+
const a = getInput("a") ?? node.state.a ?? 0;
|
|
27
|
+
const b = getInput("b") ?? node.state.b ?? 0;
|
|
27
28
|
const result = a + b;
|
|
29
|
+
// Sync state so PropertyPanel shows actual values
|
|
30
|
+
node.state.a = a;
|
|
31
|
+
node.state.b = b;
|
|
32
|
+
node.state.result = result;
|
|
28
33
|
console.log("[Add] a:", a, "b:", b, "result:", result);
|
|
29
34
|
setOutput("result", result);
|
|
35
|
+
setOutput("exec", true);
|
|
30
36
|
},
|
|
31
37
|
});
|
|
32
38
|
|
|
33
39
|
// Subtract Node
|
|
34
40
|
registry.register("math/Subtract", {
|
|
35
41
|
title: "Subtract",
|
|
36
|
-
|
|
42
|
+
color: "#f43f5e", // math (rose)
|
|
43
|
+
size: { w: 140 },
|
|
37
44
|
inputs: [
|
|
38
45
|
{ name: "a", datatype: "number" },
|
|
39
46
|
{ name: "b", datatype: "number" },
|
|
@@ -49,7 +56,8 @@ export function registerMathNodes(registry) {
|
|
|
49
56
|
// Multiply Node
|
|
50
57
|
registry.register("math/Multiply", {
|
|
51
58
|
title: "Multiply",
|
|
52
|
-
|
|
59
|
+
color: "#f43f5e", // math (rose)
|
|
60
|
+
size: { w: 140 },
|
|
53
61
|
inputs: [
|
|
54
62
|
{ name: "exec", portType: "exec" },
|
|
55
63
|
{ name: "a", portType: "data", datatype: "number" },
|
|
@@ -60,18 +68,25 @@ export function registerMathNodes(registry) {
|
|
|
60
68
|
{ name: "result", portType: "data", datatype: "number" },
|
|
61
69
|
],
|
|
62
70
|
onExecute(node, { getInput, setOutput }) {
|
|
63
|
-
const a = getInput("a") ?? 0;
|
|
64
|
-
const b = getInput("b") ?? 0;
|
|
71
|
+
const a = getInput("a") ?? node.state?.a ?? 0;
|
|
72
|
+
const b = getInput("b") ?? node.state?.b ?? 0;
|
|
65
73
|
const result = a * b;
|
|
74
|
+
if (node.state) {
|
|
75
|
+
node.state.a = a;
|
|
76
|
+
node.state.b = b;
|
|
77
|
+
node.state.result = result;
|
|
78
|
+
}
|
|
66
79
|
console.log("[Multiply] a:", a, "b:", b, "result:", result);
|
|
67
80
|
setOutput("result", result);
|
|
81
|
+
setOutput("exec", true);
|
|
68
82
|
},
|
|
69
83
|
});
|
|
70
84
|
|
|
71
85
|
// Divide Node
|
|
72
86
|
registry.register("math/Divide", {
|
|
73
87
|
title: "Divide",
|
|
74
|
-
|
|
88
|
+
color: "#f43f5e", // math (rose)
|
|
89
|
+
size: { w: 140 },
|
|
75
90
|
inputs: [
|
|
76
91
|
{ name: "a", datatype: "number" },
|
|
77
92
|
{ name: "b", datatype: "number" },
|