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/src/nodes/util.js CHANGED
@@ -1,134 +1,241 @@
1
+ // Duration each exec edge stays active during sequential animation (ms)
2
+ const STEP_DURATION = 620;
3
+
1
4
  export function registerUtilNodes(registry) {
2
- registry.register("util/Trigger", {
3
- title: "Trigger",
4
- size: { w: 140, h: 80 },
5
- outputs: [{ name: "triggered", portType: "exec" }],
6
- html: {
7
- init(node, el, { header, body }) {
8
- // Styling
9
- el.style.minWidth = "140px";
10
- el.style.backgroundColor = "#1e1e23";
11
- el.style.border = "1px solid #3a3a40";
12
- el.style.borderRadius = "8px";
13
-
14
- header.style.backgroundColor = "#2a2a31";
15
- header.style.borderBottom = "1px solid #444";
16
- header.style.color = "#eee";
17
- header.style.fontSize = "12px";
18
- header.textContent = "Trigger";
19
-
20
- body.style.padding = "12px";
21
- body.style.display = "flex";
22
- body.style.alignItems = "center";
23
- body.style.justifyContent = "center";
24
- body.style.minHeight = "32px"; // Ensure consistent body height
25
-
26
- const button = document.createElement("button");
27
- button.textContent = "Fire!";
28
- Object.assign(button.style, {
29
- padding: "8px 16px",
30
- background: "#4a9eff",
31
- border: "none",
32
- borderRadius: "4px",
33
- color: "#fff",
34
- fontSize: "14px",
35
- fontWeight: "bold",
36
- cursor: "pointer",
37
- pointerEvents: "auto",
38
- });
39
-
40
- button.addEventListener("click", (e) => {
41
- e.stopPropagation();
42
- e.preventDefault();
43
-
44
- node.state.triggered = true;
45
-
46
- // Access controller and runner from window.editor
47
- const editor = window.editor;
48
- if (!editor || !editor.controller || !editor.runner) {
49
- console.error("[Trigger] Editor, controller, or runner not found");
50
- return;
51
- }
52
-
53
- const controller = editor.controller;
54
- const runner = editor.runner;
55
-
56
- // Execute connected nodes using runner
57
- const result = runner.runOnce(node.id, 0);
58
- const connectedEdges = result.connectedEdges;
59
-
60
- // Set active edges on controller (will be rendered on port canvas)
61
- controller.activeEdges = connectedEdges;
62
-
63
- // Show animation
64
- const startTime = performance.now();
65
- const animationDuration = 500;
66
-
67
- const animate = () => {
68
- const elapsed = performance.now() - startTime;
69
- if (elapsed < animationDuration) {
70
- controller.render();
71
- requestAnimationFrame(animate);
72
- } else {
73
- controller.activeEdges = new Set();
74
- controller.render();
75
- node.state.triggered = false;
76
- }
77
- };
78
-
79
- animate();
80
- });
81
-
82
- body.appendChild(button);
83
- },
84
- },
85
- onExecute(node, { setOutput }) {
86
- if (node.state.triggered) {
87
- console.log("[Trigger] Outputting triggered: true");
88
- setOutput("triggered", true);
5
+ registry.register("util/Trigger", {
6
+ title: "Trigger",
7
+ color: "#f7cb4d", // event (amber)
8
+ size: { w: 140, h: 100 },
9
+ outputs: [{ name: "exec", portType: "exec" }],
10
+ html: {
11
+ init(node, el, { body }) {
12
+ el.classList.add("node-overlay");
13
+
14
+ body.style.display = "flex";
15
+ body.style.alignItems = "center";
16
+ body.style.justifyContent = "center";
17
+
18
+ const button = document.createElement("button");
19
+ button.className = "premium-button";
20
+ button.textContent = "Execute";
21
+ button.style.width = "100%";
22
+ button.style.textTransform = "uppercase";
23
+ button.style.letterSpacing = "1px";
24
+ button.style.marginTop = "22px"; // Push below exec port label (port bottom ~y=50)
25
+
26
+ button.addEventListener("click", (e) => {
27
+ e.stopPropagation();
28
+ e.preventDefault();
29
+
30
+ if (node.state._firing) return;
31
+
32
+ const editor = window.editor;
33
+ if (!editor?.controller || !editor?.runner) return;
34
+
35
+ const controller = editor.controller;
36
+ const runner = editor.runner;
37
+
38
+ node.state.triggered = true;
39
+ node.state._firing = true;
40
+
41
+ // If in Step Mode, just prepare the plan and wait for manual clicks
42
+ if (runner.executionMode === "step") {
43
+ runner.startStepping(node.id);
44
+ node.state.triggered = false;
45
+ node.state._firing = false;
46
+ return;
47
+ }
48
+
49
+ // Active state styling for Run mode
50
+ button.style.borderColor = "#4f62c0";
51
+ button.style.color = "#7080d8";
52
+ button.style.background = "rgba(79,98,192,0.12)";
53
+
54
+ const { connectedEdges, execEdgeOrder } = runner.runOnce(node.id, 0);
55
+
56
+ controller.activeEdgeTimes = new Map();
57
+
58
+ if (execEdgeOrder.length > 0) {
59
+ // Sequential: animate one exec edge at a time
60
+ const startTime = performance.now();
61
+ const totalDuration = execEdgeOrder.length * STEP_DURATION + 80;
62
+
63
+ const animate = () => {
64
+ const now = performance.now();
65
+ const elapsed = now - startTime;
66
+ const step = Math.floor(elapsed / STEP_DURATION);
67
+
68
+ const activeNow = new Set();
69
+ const activeNodeNow = new Set();
70
+ if (step < execEdgeOrder.length) {
71
+ const edgeId = execEdgeOrder[step];
72
+ activeNow.add(edgeId);
73
+ if (!controller.activeEdgeTimes.has(edgeId)) {
74
+ controller.activeEdgeTimes.set(edgeId, startTime + step * STEP_DURATION);
75
+ }
76
+ // Highlight the destination node of this exec edge
77
+ const edge = runner.graph.edges.get(edgeId);
78
+ if (edge?.toNode) activeNodeNow.add(edge.toNode);
79
+ }
80
+
81
+ controller.activeEdges = activeNow;
82
+ controller.activeNodes = activeNodeNow;
83
+ controller.render();
84
+
85
+ if (elapsed < totalDuration) {
86
+ requestAnimationFrame(animate);
87
+ } else {
88
+ _resetTrigger(controller, node, button);
89
+ }
90
+ };
91
+ requestAnimationFrame(animate);
92
+ } else if (connectedEdges.size > 0) {
93
+ // Fallback: all data edges at once
94
+ const startTime = performance.now();
95
+ const totalDuration = STEP_DURATION;
96
+ const now = performance.now();
97
+ for (const id of connectedEdges) {
98
+ controller.activeEdgeTimes.set(id, now);
89
99
  }
90
- },
91
- });
92
-
93
- registry.register("util/Watch", {
94
- title: "Watch",
95
- inputs: [{ name: "value", portType: "data", datatype: "any" }],
96
- onExecute(node, { getInput }) {
97
- const value = getInput("value");
98
- console.log("[Watch] onExecute called, value:", value);
99
- },
100
- });
101
-
102
- registry.register("util/Print", {
103
- title: "Print",
104
- inputs: [
105
- { name: "", portType: "exec" },
106
- { name: "value", portType: "data", datatype: "any" },
107
- ],
108
- outputs: [{ name: "", portType: "exec" }],
109
- onExecute(node, { getInput, setOutput }) {
110
- const value = getInput("value");
111
- console.log("[Print]", value);
112
- setOutput("", true);
113
- },
114
- });
115
-
116
- registry.register("util/Timer", {
117
- title: "Timer",
118
- inputs: [
119
- { name: "", portType: "exec" },
120
- { name: "delay (ms)", portType: "data", datatype: "number" },
121
- ],
122
- outputs: [{ name: "", portType: "exec" }],
123
- async onExecute(node, { getInput, setOutput }) {
124
- const delay = getInput("delay (ms)") || 0;
125
- await new Promise((resolve) => {
126
- setTimeout(() => {
127
- console.log("[Timer] Triggered after", delay, "ms");
128
- resolve();
129
- }, delay);
130
- });
131
- setOutput("", true);
132
- },
133
- });
100
+
101
+ const animate = () => {
102
+ controller.activeEdges = connectedEdges;
103
+ controller.render();
104
+ if (performance.now() - startTime < totalDuration) {
105
+ requestAnimationFrame(animate);
106
+ } else {
107
+ _resetTrigger(controller, node, button);
108
+ }
109
+ };
110
+ requestAnimationFrame(animate);
111
+ } else {
112
+ _resetTrigger(controller, node, button);
113
+ }
114
+ });
115
+
116
+ body.appendChild(button);
117
+ el._btn = button;
118
+ },
119
+ },
120
+ onExecute(node, { setOutput }) {
121
+ if (node.state.triggered) {
122
+ setOutput("exec", true);
123
+ }
124
+ },
125
+ });
126
+
127
+ registry.register("util/Watch", {
128
+ title: "Watch",
129
+ color: "#10b981", // info (emerald)
130
+ size: { w: 180, h: 130 },
131
+ inputs: [
132
+ { name: "exec", portType: "exec" },
133
+ { name: "value", portType: "data", datatype: "any" },
134
+ ],
135
+ outputs: [
136
+ { name: "exec", portType: "exec" },
137
+ { name: "value", portType: "data", datatype: "any" },
138
+ ],
139
+ onCreate(node) {
140
+ node.state.displayValue = "---";
141
+ },
142
+ html: {
143
+ init(node, el, { body }) {
144
+ el.classList.add("node-overlay");
145
+ body.style.display = "flex";
146
+ body.style.alignItems = "center";
147
+ body.style.justifyContent = "center";
148
+ body.style.paddingTop = "44px"; // Push display below both port rows (y=44, y=64)
149
+
150
+ const container = document.createElement("div");
151
+ container.className = "watch-display";
152
+ container.style.width = "90%";
153
+ container.style.padding = "10px";
154
+ container.style.background = "rgba(0,0,0,0.2)";
155
+ container.style.borderRadius = "4px";
156
+ container.style.border = "1px solid rgba(255,255,255,0.05)";
157
+ container.style.textAlign = "center";
158
+ container.style.fontFamily = "'JetBrains Mono', 'Fira Code', monospace";
159
+ container.style.fontSize = "12px";
160
+ container.style.color = "#10b981";
161
+ container.style.textShadow = "0 0 10px rgba(16,185,129,0.3)";
162
+ container.textContent = node.state.displayValue || "---";
163
+
164
+ body.appendChild(container);
165
+ node._display = container;
166
+ },
167
+ },
168
+ onExecute(node, { getInput, setOutput }) {
169
+ const val = getInput("value");
170
+ console.log("[Watch] onExecute called, value:", val);
171
+
172
+ // Format value for display
173
+ let display = "---";
174
+ if (val !== undefined && val !== null) {
175
+ display = typeof val === "object" ? JSON.stringify(val) : String(val);
176
+ }
177
+
178
+ node.state.displayValue = display;
179
+ if (node._display) {
180
+ node._display.textContent = display;
181
+ // Add a brief glow effect
182
+ node._display.style.color = "#ffffff";
183
+ setTimeout(() => {
184
+ if (node._display) node._display.style.color = "#10b981";
185
+ }, 100);
186
+ }
187
+
188
+ // Pass through
189
+ setOutput("exec", true); // exec signal
190
+ setOutput("value", val); // data value
191
+ },
192
+ });
193
+
194
+ registry.register("util/Print", {
195
+ title: "Print",
196
+ color: "#10b981", // info (emerald)
197
+ size: { w: 140 },
198
+ inputs: [
199
+ { name: "", portType: "exec" },
200
+ { name: "value", portType: "data", datatype: "any" },
201
+ ],
202
+ outputs: [{ name: "", portType: "exec" }],
203
+ onExecute(node, { getInput, setOutput }) {
204
+ const value = getInput("value");
205
+ console.log("[Print]", value);
206
+ setOutput("", true);
207
+ },
208
+ });
209
+
210
+ registry.register("util/Timer", {
211
+ title: "Timer",
212
+ color: "#7baaf7", // event (amber)
213
+ size: { w: 140 },
214
+ inputs: [
215
+ { name: "", portType: "exec" },
216
+ { name: "delay (ms)", portType: "data", datatype: "number" },
217
+ ],
218
+ outputs: [{ name: "", portType: "exec" }],
219
+ async onExecute(node, { getInput, setOutput }) {
220
+ const delay = getInput("delay (ms)") || 0;
221
+ await new Promise((resolve) => {
222
+ setTimeout(() => {
223
+ resolve();
224
+ }, delay);
225
+ });
226
+ setOutput("", true);
227
+ },
228
+ });
229
+ }
230
+
231
+ function _resetTrigger(controller, node, button) {
232
+ controller.activeEdges = new Set();
233
+ controller.activeEdgeTimes = new Map();
234
+ controller.activeNodes = new Set();
235
+ controller.render();
236
+ node.state.triggered = false;
237
+ node.state._firing = false;
238
+ button.style.borderColor = "#383858";
239
+ button.style.color = "#8888aa";
240
+ button.style.background = "transparent";
134
241
  }
@@ -4,113 +4,98 @@
4
4
  */
5
5
 
6
6
  export function registerValueNodes(registry) {
7
- // Number Node
8
- registry.register("value/Number", {
9
- title: "Number",
10
- size: { w: 140, h: 60 },
11
- outputs: [{ name: "value", portType: "data", datatype: "number" }],
12
- onCreate(node) {
13
- node.state.value = 0;
14
- },
15
- onExecute(node, { setOutput }) {
16
- console.log("[Number] Outputting value:", node.state.value ?? 0);
17
- setOutput("value", node.state.value ?? 0);
18
- },
19
- html: {
20
- init(node, el, { header, body }) {
21
- el.style.backgroundColor = "#1e1e24";
22
- el.style.border = "1px solid #444";
23
- el.style.borderRadius = "8px";
7
+ // Number Node
8
+ registry.register("value/Number", {
9
+ title: "Number",
10
+ color: "#3b82f6", // data (blue)
11
+ size: { w: 140, h: 90 },
12
+ outputs: [{ name: "value", portType: "data", datatype: "number" }],
13
+ onCreate(node) {
14
+ node.state.value = 0;
15
+ },
16
+ onExecute(node, { setOutput }) {
17
+ console.log("[Number] Outputting value:", node.state.value ?? 0);
18
+ setOutput("value", node.state.value ?? 0);
19
+ },
20
+ html: {
21
+ init(node, el, { body }) {
22
+ el.classList.add("node-overlay");
24
23
 
25
- header.style.backgroundColor = "#2a2a31";
26
- header.style.borderBottom = "1px solid #444";
27
- header.style.color = "#eee";
28
- header.style.fontSize = "12px";
29
- header.textContent = "Number";
24
+ body.style.display = "flex";
25
+ body.style.alignItems = "flex-start"; // Don't center vertically — push to top of padded area
26
+ body.style.justifyContent = "center";
27
+ body.style.paddingTop = "26px"; // Push input below value port (port bottom ~y=50)
30
28
 
31
- body.style.padding = "12px";
32
- body.style.display = "flex";
33
- body.style.alignItems = "center";
34
- body.style.justifyContent = "center";
29
+ const input = document.createElement("input");
30
+ input.className = "premium-input";
31
+ input.type = "number";
32
+ input.style.textAlign = "center";
33
+ input.value = node.state.value ?? 0;
35
34
 
36
- const input = document.createElement("input");
37
- input.type = "number";
38
- input.value = node.state.value ?? 0;
39
- Object.assign(input.style, {
40
- width: "100%",
41
- padding: "6px",
42
- background: "#141417",
43
- border: "1px solid #444",
44
- borderRadius: "4px",
45
- color: "#fff",
46
- fontSize: "14px",
47
- textAlign: "center",
48
- pointerEvents: "auto",
49
- });
35
+ input.addEventListener("change", (e) => {
36
+ node.state.value = parseFloat(e.target.value) || 0;
37
+ });
50
38
 
51
- input.addEventListener("change", (e) => {
52
- node.state.value = parseFloat(e.target.value) || 0;
53
- });
39
+ input.addEventListener("mousedown", (e) => e.stopPropagation());
40
+ input.addEventListener("keydown", (e) => e.stopPropagation());
54
41
 
55
- input.addEventListener("mousedown", (e) => e.stopPropagation());
56
- input.addEventListener("keydown", (e) => e.stopPropagation());
42
+ body.appendChild(input);
43
+ },
44
+ update(_node, _el, _opts) {
45
+ // Selection is handled by the canvas renderer
46
+ },
47
+ },
48
+ onDraw(node, { ctx }) {
49
+ // const { x, y } = node.computed;
50
+ // ctx.fillStyle = "#8f8";
51
+ // ctx.font = "14px sans-serif";
52
+ // ctx.textAlign = "center";
53
+ // ctx.fillText(String(node.state.value ?? 0), x + 70, y + 42);
54
+ },
55
+ });
57
56
 
58
- body.appendChild(input);
59
- },
60
- update(node, el, { header, selected }) {
61
- el.style.borderColor = selected ? "#6cf" : "#444";
62
- header.style.backgroundColor = selected ? "#3a4a5a" : "#2a2a31";
63
- },
64
- },
65
- onDraw(node, { ctx }) {
66
- const { x, y } = node.computed;
67
- ctx.fillStyle = "#8f8";
68
- ctx.font = "14px sans-serif";
69
- ctx.textAlign = "center";
70
- ctx.fillText(String(node.state.value ?? 0), x + 70, y + 42);
71
- },
72
- });
57
+ // String Node
58
+ registry.register("value/String", {
59
+ title: "String",
60
+ color: "#3b82f6", // data (blue)
61
+ size: { w: 160 },
62
+ outputs: [{ name: "value", datatype: "string" }],
63
+ onCreate(node) {
64
+ node.state.value = "Hello";
65
+ },
66
+ onExecute(node, { setOutput }) {
67
+ setOutput("value", node.state.value ?? "");
68
+ },
69
+ onDraw(node, { ctx }) {
70
+ const { x, y } = node.computed;
71
+ ctx.fillStyle = "#8f8";
72
+ ctx.font = "12px sans-serif";
73
+ ctx.textAlign = "center";
74
+ const text = String(node.state.value ?? "");
75
+ const displayText = text.length > 15 ? text.substring(0, 15) + "..." : text;
76
+ ctx.fillText(displayText, x + 80, y + 42);
77
+ },
78
+ });
73
79
 
74
- // String Node
75
- registry.register("value/String", {
76
- title: "String",
77
- size: { w: 160, h: 60 },
78
- outputs: [{ name: "value", datatype: "string" }],
79
- onCreate(node) {
80
- node.state.value = "Hello";
81
- },
82
- onExecute(node, { setOutput }) {
83
- setOutput("value", node.state.value ?? "");
84
- },
85
- onDraw(node, { ctx }) {
86
- const { x, y } = node.computed;
87
- ctx.fillStyle = "#8f8";
88
- ctx.font = "12px sans-serif";
89
- ctx.textAlign = "center";
90
- const text = String(node.state.value ?? "");
91
- const displayText = text.length > 15 ? text.substring(0, 15) + "..." : text;
92
- ctx.fillText(displayText, x + 80, y + 42);
93
- },
94
- });
95
-
96
- // Boolean Node
97
- registry.register("value/Boolean", {
98
- title: "Boolean",
99
- size: { w: 140, h: 60 },
100
- outputs: [{ name: "value", portType: "data", datatype: "boolean" }],
101
- onCreate(node) {
102
- node.state.value = true;
103
- },
104
- onExecute(node, { setOutput }) {
105
- console.log("[Boolean] Outputting value:", node.state.value ?? false);
106
- setOutput("value", node.state.value ?? false);
107
- },
108
- onDraw(node, { ctx }) {
109
- const { x, y } = node.computed;
110
- ctx.fillStyle = node.state.value ? "#8f8" : "#f88";
111
- ctx.font = "14px sans-serif";
112
- ctx.textAlign = "center";
113
- ctx.fillText(String(node.state.value), x + 70, y + 42);
114
- },
115
- });
80
+ // Boolean Node
81
+ registry.register("value/Boolean", {
82
+ title: "Boolean",
83
+ color: "#3b82f6", // data (blue)
84
+ size: { w: 140 },
85
+ outputs: [{ name: "value", portType: "data", datatype: "boolean" }],
86
+ onCreate(node) {
87
+ node.state.value = true;
88
+ },
89
+ onExecute(node, { setOutput }) {
90
+ console.log("[Boolean] Outputting value:", node.state.value ?? false);
91
+ setOutput("value", node.state.value ?? false);
92
+ },
93
+ onDraw(node, { ctx }) {
94
+ const { x, y } = node.computed;
95
+ ctx.fillStyle = node.state.value ? "#8f8" : "#f88";
96
+ ctx.font = "600 14px var(--font-main)";
97
+ ctx.textAlign = "center";
98
+ ctx.fillText(String(node.state.value), x + 70, y + 42);
99
+ },
100
+ });
116
101
  }