Skip to content

Commit fc7f7c2

Browse files
committed
support registering nodes easier with classes and custom popup window
1 parent 3652293 commit fc7f7c2

File tree

14 files changed

+1266
-110
lines changed

14 files changed

+1266
-110
lines changed

apps/vps-web/src/app/custom-nodes/classes/draw-grid-node.tsx

Lines changed: 576 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
createJSXElement,
3+
FlowNode,
4+
} from '@devhelpr/visual-programming-system';
5+
import { NodeInfo } from '@devhelpr/web-flow-executor';
6+
import { BaseRectNode } from './rect-node-class';
7+
8+
export class OvalNode extends BaseRectNode {
9+
static readonly nodeTypeName = 'oval-node';
10+
static readonly nodeTitle = 'Oval node';
11+
static readonly category = 'Default test';
12+
static readonly text = 'oval';
13+
14+
render = (node: FlowNode<NodeInfo>) => {
15+
const nodeInfo = node.nodeInfo as any;
16+
console.log('render rect-node', node.width, node.height);
17+
return (
18+
<div>
19+
<div
20+
getElement={(element: HTMLElement) => {
21+
this.rectElement = element;
22+
}}
23+
class={`rounded-full justify-center items-center text-center whitespace-pre inline-flex`}
24+
style={`min-width:${node.width ?? 50}px;min-height:${
25+
node.height ?? 50
26+
}px;background:${nodeInfo?.fillColor ?? 'black'};border: ${
27+
nodeInfo?.strokeWidth ?? '2'
28+
}px ${nodeInfo?.strokeColor ?? 'white'} solid;color:${
29+
nodeInfo?.strokeColor ?? 'white'
30+
}`}
31+
spellcheck="false"
32+
blur={() => {
33+
if (this.rectElement) {
34+
if (this.rectElement.innerHTML.toString().length == 0) {
35+
// hacky solution to prevent caret being aligned to top
36+
this.rectElement.innerHTML = '&nbsp;';
37+
}
38+
}
39+
console.log('blur', this.rectElement?.textContent);
40+
if (this.node?.nodeInfo) {
41+
(this.node.nodeInfo as any).text = this.rectElement?.textContent;
42+
}
43+
this.updated();
44+
}}
45+
contentEditable={true}
46+
pointerdown={(e: PointerEvent) => {
47+
if (e.shiftKey && this.rectElement) {
48+
this.rectElement.contentEditable = 'false';
49+
}
50+
}}
51+
pointerup={() => {
52+
if (this.rectElement) {
53+
this.rectElement.contentEditable = 'true';
54+
}
55+
}}
56+
>
57+
{nodeInfo?.text ?? ''}
58+
</div>
59+
</div>
60+
);
61+
};
62+
}

apps/vps-web/src/app/custom-nodes/classes/rect-node-class.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,40 @@ import {
22
createJSXElement,
33
FlowNode,
44
IComputeResult,
5+
IDOMElement,
56
IRectNodeComponent,
7+
Rect,
8+
IFlowCanvasBase,
69
} from '@devhelpr/visual-programming-system';
7-
import { NodeInfo } from '@devhelpr/web-flow-executor';
10+
import { NodeInfo, RunCounter } from '@devhelpr/web-flow-executor';
811

9-
export class RectNode {
12+
export type CreateRunCounterContext = (
13+
isRunViaRunButton: boolean,
14+
shouldResetConnectionSlider: boolean,
15+
onFlowFinished?: () => void
16+
) => RunCounter;
17+
18+
export class BaseRectNode {
1019
nodeRenderElement: HTMLElement | undefined = undefined;
1120
rectElement: HTMLElement | undefined = undefined;
21+
canvasAppInstance: IFlowCanvasBase<NodeInfo> | undefined = undefined;
1222
id: string;
1323
node: IRectNodeComponent<NodeInfo> | undefined = undefined;
1424
updated: () => void;
25+
getSettingsPopup:
26+
| ((popupContainer: HTMLElement) => IDOMElement | undefined)
27+
| undefined = undefined;
28+
29+
rectInstance: Rect<NodeInfo> | undefined;
30+
31+
createRunCounterContext: CreateRunCounterContext | undefined = undefined;
32+
33+
static readonly nodeTypeName: string = 'rect-node';
34+
static readonly nodeTitle: string = 'Rect node';
35+
static readonly category: string = 'Default';
36+
37+
static readonly text: string = 'rect';
38+
1539
constructor(
1640
id: string,
1741
updated: () => void,
@@ -49,8 +73,8 @@ w-min h-min
4973
getElement={(element: HTMLElement) => {
5074
this.rectElement = element;
5175
}}
52-
class={`rounded flex justify-center items-center text-center whitespace-pre`}
53-
style={`width:${node.width ?? 50}px;height:${
76+
class={`rounded justify-center items-center text-center whitespace-pre inline-flex`}
77+
style={`min-width:${node.width ?? 50}px;min-height:${
5478
node.height ?? 50
5579
}px;background:${nodeInfo?.fillColor ?? 'black'};border: ${
5680
nodeInfo?.strokeWidth ?? '2'
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
.draw-grid {
2+
display: grid;
3+
gap: 1px;
4+
background-color: #e5e7eb;
5+
padding: 2px;
6+
border-radius: 8px;
7+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
8+
}
9+
10+
.draw-grid-cell {
11+
aspect-ratio: 1;
12+
background-color: white;
13+
transition: background-color 0.15s ease-out;
14+
border-radius: 1px;
15+
}
16+
17+
#clearButton {
18+
padding: 10px 20px;
19+
background-color: #3b82f6;
20+
color: white;
21+
border: none;
22+
border-radius: 6px;
23+
cursor: pointer;
24+
font-size: 16px;
25+
transition: background-color 0.2s;
26+
}
27+
28+
#clearButton:hover {
29+
background-color: #2563eb;
30+
}
31+
32+
.control-panel {
33+
position: relative;
34+
/* top: 20px;
35+
right: 20px; */
36+
background: white;
37+
padding: 20px;
38+
border-radius: 8px;
39+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
40+
width: 100%;
41+
z-index: 1000;
42+
transition: all 0.3s ease;
43+
}
44+
45+
.control-panel.collapsed {
46+
width: 44px; /* Enough width for the button (24px) + padding (10px * 2) */
47+
height: 44px; /* Same as width for symmetry */
48+
padding: 10px;
49+
background: white;
50+
}
51+
52+
.controls-container {
53+
transition: opacity 0.2s ease;
54+
padding-top: 15px;
55+
}
56+
57+
.control-group {
58+
margin-bottom: 15px;
59+
}
60+
61+
.control-group:last-child {
62+
margin-bottom: 0;
63+
}
64+
65+
.control-group label {
66+
display: block;
67+
margin-bottom: 8px;
68+
font-size: 14px;
69+
color: #333;
70+
}
71+
72+
.control-group input[type='range'] {
73+
width: 100%;
74+
height: 6px;
75+
background: #e2e8f0;
76+
border-radius: 3px;
77+
outline: none;
78+
-webkit-appearance: none;
79+
}
80+
81+
.control-group input[type='range']::-webkit-slider-thumb {
82+
-webkit-appearance: none;
83+
width: 18px;
84+
height: 18px;
85+
background: #3b82f6;
86+
border-radius: 50%;
87+
cursor: pointer;
88+
transition: background 0.2s;
89+
}
90+
91+
.control-group input[type='range']::-webkit-slider-thumb:hover {
92+
background: #2563eb;
93+
}
94+
95+
.control-group input[type='color'] {
96+
width: 100%;
97+
height: 40px;
98+
padding: 2px;
99+
border: none;
100+
border-radius: 4px;
101+
background: #f1f5f9;
102+
cursor: pointer;
103+
}
104+
105+
.control-group input[type='color']::-webkit-color-swatch-wrapper {
106+
padding: 0;
107+
}
108+
109+
.control-group input[type='color']::-webkit-color-swatch {
110+
border: none;
111+
border-radius: 4px;
112+
}
113+
114+
.control-button {
115+
width: 100%;
116+
padding: 10px;
117+
margin-top: 20px;
118+
background-color: #3b82f6;
119+
color: white;
120+
border: none;
121+
border-radius: 6px;
122+
cursor: pointer;
123+
font-size: 14px;
124+
font-weight: 500;
125+
transition: background-color 0.2s;
126+
}
127+
128+
.control-button:hover {
129+
background-color: #2563eb;
130+
}
131+
132+
:root {
133+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
134+
line-height: 1.5;
135+
font-weight: 400;
136+
137+
color-scheme: light dark;
138+
color: rgba(255, 255, 255, 0.87);
139+
background-color: #242424;
140+
141+
font-synthesis: none;
142+
text-rendering: optimizeLegibility;
143+
-webkit-font-smoothing: antialiased;
144+
-moz-osx-font-smoothing: grayscale;
145+
}
146+
147+
/* a {
148+
font-weight: 500;
149+
color: #646cff;
150+
text-decoration: inherit;
151+
}
152+
a:hover {
153+
color: #535bf2;
154+
}
155+
156+
h1 {
157+
font-size: 3.2em;
158+
line-height: 1.1;
159+
}
160+
161+
.logo {
162+
height: 6em;
163+
padding: 1.5em;
164+
will-change: filter;
165+
transition: filter 300ms;
166+
}
167+
.logo:hover {
168+
filter: drop-shadow(0 0 2em #646cffaa);
169+
}
170+
.logo.vanilla:hover {
171+
filter: drop-shadow(0 0 2em #3178c6aa);
172+
}
173+
174+
.card {
175+
padding: 2em;
176+
}
177+
178+
.read-the-docs {
179+
color: #888;
180+
}
181+
182+
button {
183+
border-radius: 8px;
184+
border: 1px solid transparent;
185+
padding: 0.6em 1.2em;
186+
font-size: 1em;
187+
font-weight: 500;
188+
font-family: inherit;
189+
background-color: #1a1a1a;
190+
cursor: pointer;
191+
transition: border-color 0.25s;
192+
}
193+
button:hover {
194+
border-color: #646cff;
195+
}
196+
button:focus,
197+
button:focus-visible {
198+
outline: 4px auto -webkit-focus-ring-color;
199+
}
200+
201+
@media (prefers-color-scheme: light) {
202+
:root {
203+
color: #213547;
204+
background-color: #ffffff;
205+
}
206+
a:hover {
207+
color: #747bff;
208+
}
209+
button {
210+
background-color: #f9f9f9;
211+
}
212+
} */
213+
214+
.color-presets {
215+
display: grid;
216+
grid-template-columns: repeat(8, 1fr);
217+
gap: 4px;
218+
margin-top: 8px;
219+
padding: 6px;
220+
background: rgba(0, 0, 0, 0.03);
221+
border-radius: 6px;
222+
}
223+
224+
.color-preset-button {
225+
width: 100%;
226+
aspect-ratio: 1;
227+
border: none;
228+
border-radius: 4px;
229+
cursor: pointer;
230+
transition: transform 0.2s ease, box-shadow 0.2s ease;
231+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
232+
padding: 0;
233+
min-width: 0;
234+
background-color: currentColor;
235+
margin: 0;
236+
}
237+
238+
.color-preset-button:hover {
239+
transform: translateY(-1px);
240+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
241+
}
242+
243+
.color-preset-button:active {
244+
transform: translateY(0);
245+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
246+
}
247+
248+
#colorPicker {
249+
width: 100%;
250+
height: 40px;
251+
padding: 4px;
252+
border: none;
253+
border-radius: 6px;
254+
background: rgba(255, 255, 255, 0.05);
255+
cursor: pointer;
256+
}
257+
258+
.collapse-button {
259+
position: absolute;
260+
top: 10px;
261+
right: 10px;
262+
width: 24px;
263+
height: 24px;
264+
padding: 0;
265+
border: none;
266+
border-radius: 4px;
267+
background: rgba(0, 0, 0, 0.1);
268+
color: #333;
269+
font-size: 18px;
270+
line-height: 24px;
271+
text-align: center;
272+
cursor: pointer;
273+
transition: background-color 0.2s;
274+
z-index: 1;
275+
}
276+
277+
.control-panel.collapsed .collapse-button {
278+
top: 50%;
279+
right: 50%;
280+
transform: translate(50%, -50%);
281+
}

0 commit comments

Comments
 (0)