Skip to content

Commit 4a78a47

Browse files
committed
Added 'Switch' node with case-based branching and UI controls
1 parent a30a0eb commit 4a78a47

File tree

6 files changed

+392
-69
lines changed

6 files changed

+392
-69
lines changed

TODO.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,4 @@
77
- [ ] Copy node
88
- [ ] Delete node
99

10-
- [ ] Node labeling
11-
12-
- [ ] Add new node types:
13-
- [ ] Switch
10+
- [ ] Node labeling

src/CodeGenerator.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,71 @@ class CodeGenerator {
183183

184184
this.nodeOutputs.set(node.id, resultVar);
185185
break;
186+
case 'Switch':
187+
const switchValue = this.getNodeInputValue(node, 1);
188+
if (switchValue === undefined) {
189+
this.addLine('// Warning: Switch node is missing input value');
190+
return;
191+
}
192+
193+
const ignoreCase = node.properties.ignoreCase;
194+
const cases = node.properties.cases || [];
195+
196+
if (ignoreCase) {
197+
this.addLine(`switch (${switchValue}.toString().toLowerCase()) {`);
198+
} else {
199+
this.addLine(`switch (${switchValue}) {`);
200+
}
201+
this.indentLevel++;
202+
203+
// Generate case statements
204+
cases.forEach((caseObj, index) => {
205+
const caseValue = ignoreCase ? caseObj.value.toLowerCase() : caseObj.value;
206+
this.addLine(`case ${JSON.stringify(caseValue)}:`);
207+
this.indentLevel++;
208+
209+
// Find and generate code for the case branch
210+
const caseEdge = this.edges.find(edge =>
211+
edge.start.nodeId === node.id &&
212+
edge.start.index === index + 1 && // +1 because index 0 is the default output
213+
!edge.start.isInput
214+
);
215+
216+
if (caseEdge) {
217+
const nextNode = this.nodes.find(n => n.id === caseEdge.end.nodeId);
218+
if (nextNode) {
219+
this.generateNodeCodeSequence(nextNode);
220+
}
221+
}
222+
223+
this.addLine('break;');
224+
this.indentLevel--;
225+
});
226+
227+
// Default case
228+
this.addLine('default:');
229+
this.indentLevel++;
230+
231+
// Find and generate code for the default branch
232+
const defaultEdge = this.edges.find(edge =>
233+
edge.start.nodeId === node.id &&
234+
edge.start.index === 0 &&
235+
!edge.start.isInput
236+
);
237+
238+
if (defaultEdge) {
239+
const defaultNode = this.nodes.find(n => n.id === defaultEdge.end.nodeId);
240+
if (defaultNode) {
241+
this.generateNodeCodeSequence(defaultNode);
242+
}
243+
}
244+
245+
this.addLine('break;');
246+
this.indentLevel--;
247+
248+
this.indentLevel--;
249+
this.addLine('}');
250+
break;
186251
default:
187252
this.addLine(`// TODO: Implement ${node.type}`);
188253
}

src/components/GraphInspector.js

Lines changed: 102 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,98 @@ const GraphInspector = ({
3636
const node = selectedNodes[0];
3737
const nodeType = nodeTypes[node.type];
3838

39+
const renderPropertyInput = (property) => {
40+
switch (property.type) {
41+
case 'select':
42+
return (
43+
<select
44+
value={node.properties[property.name] || property.default}
45+
onChange={(e) => updateNodeProperty(property.name, e.target.value)}
46+
className={`${styles.input} ${config.isDarkTheme ? styles.inputDark : styles.inputLight}`}
47+
>
48+
{property.options.map(option => (
49+
<option key={option} value={option}>{option}</option>
50+
))}
51+
</select>
52+
);
53+
54+
case 'boolean':
55+
return (
56+
<input
57+
type="checkbox"
58+
checked={node.properties[property.name] || property.default}
59+
onChange={(e) => updateNodeProperty(property.name, e.target.checked)}
60+
className={`${styles.checkbox} ${config.isDarkTheme ? styles.checkboxDark : styles.checkboxLight}`}
61+
/>
62+
);
63+
64+
case 'array':
65+
if (property.name === 'cases') {
66+
return (
67+
<div className={styles.casesContainer}>
68+
{(node.properties.cases || []).map((caseObj, index) => (
69+
<div key={index} className={styles.caseRow}>
70+
<input
71+
type="text"
72+
value={caseObj.value}
73+
onChange={(e) => {
74+
const newCases = [...node.properties.cases];
75+
newCases[index] = { ...caseObj, value: e.target.value };
76+
updateNodeProperty('cases', newCases);
77+
}}
78+
placeholder="Case value"
79+
className={`${styles.input} ${config.isDarkTheme ? styles.inputDark : styles.inputLight}`}
80+
/>
81+
<button
82+
onClick={() => {
83+
const newCases = node.properties.cases.filter((_, i) => i !== index);
84+
updateNodeProperty('cases', newCases);
85+
86+
// Remove the corresponding output
87+
nodeType.outputs = nodeType.outputs.filter((_, i) => i !== index + 1);
88+
}}
89+
className={`${styles.removeButton} ${config.isDarkTheme ? styles.removeButtonDark : styles.removeButtonLight}`}
90+
>
91+
<i className="fas fa-minus"></i>
92+
</button>
93+
</div>
94+
))}
95+
<button
96+
onClick={() => {
97+
const newCases = [
98+
...(node.properties.cases || []),
99+
{ value: '', output: `Case ${(node.properties.cases || []).length + 1}` }
100+
];
101+
updateNodeProperty('cases', newCases);
102+
103+
// Add new output
104+
nodeType.outputs.push({
105+
type: 'control',
106+
name: `Case ${node.properties.cases.length + 1}`,
107+
description: `Triggered when case ${node.properties.cases.length + 1} matches`
108+
});
109+
}}
110+
className={`${styles.addButton} ${config.isDarkTheme ? styles.addButtonDark : styles.addButtonLight}`}
111+
>
112+
<i className="fas fa-plus"></i> Add Case
113+
</button>
114+
</div>
115+
);
116+
}
117+
return null;
118+
119+
default:
120+
return (
121+
<input
122+
type={property.type === 'number' ? 'number' : 'text'}
123+
value={node.properties[property.name] || property.default}
124+
onChange={(e) => updateNodeProperty(property.name, e.target.value)}
125+
className={`${styles.input} ${config.isDarkTheme ? styles.inputDark : styles.inputLight}`}
126+
/>
127+
);
128+
}
129+
};
130+
39131
// Helper function to determine if a property should be visible
40132
const isPropertyVisible = (property) => {
41133
if (property.visible === undefined) return true;
@@ -54,70 +146,47 @@ const GraphInspector = ({
54146
className={`fas ${getIconForNodeType(node.type)} ${styles.nodeIcon}`}
55147
style={{ color: nodeType.color }}
56148
/>
57-
<span className={`${styles.nodeTitle} ${config.isDarkTheme ? styles.nodeTitleDark : styles.nodeTitleLight}`}>
149+
<span className={styles.nodeTitle}>
58150
{node.type}
59151
</span>
60152
</div>
61153
</div>
62154

63155
{/* Description */}
64-
<div className={`${styles.description} ${config.isDarkTheme ? styles.descriptionDark : styles.descriptionLight}`}>
156+
<div className={styles.description}>
65157
{nodeType.description}
66158
</div>
67159

68-
<hr></hr>
160+
<hr className={styles.divider} />
69161

70162
{/* Properties */}
71163
{nodeType.properties && nodeType.properties.length > 0 && (
72164
<div className={styles.section}>
73-
<div className={`${styles.sectionTitle} ${config.isDarkTheme ? styles.sectionTitleDark : styles.sectionTitleLight}`}>
74-
Properties
75-
</div>
165+
<div className={styles.sectionTitle}>Properties</div>
76166
{nodeType.properties.map(prop => (
77167
isPropertyVisible(prop) && (
78168
<div key={prop.name} className={styles.propertyContainer}>
79-
<label className={`${styles.propertyLabel} ${config.isDarkTheme ? styles.propertyLabelDark : styles.propertyLabelLight}`}>
169+
<label className={styles.propertyLabel}>
80170
{prop.name}
81171
</label>
82-
{prop.type === 'select' ? (
83-
<select
84-
value={node.properties[prop.name] || prop.default}
85-
onChange={(e) => updateNodeProperty(prop.name, e.target.value)}
86-
className={`${styles.input} ${config.isDarkTheme ? styles.inputDark : styles.inputLight}`}
87-
>
88-
{prop.options.map(option => (
89-
<option key={option} value={option}>{option}</option>
90-
))}
91-
</select>
92-
) : (
93-
<input
94-
type={prop.type === 'number' ? 'number' : 'text'}
95-
value={node.properties[prop.name] || prop.default}
96-
onChange={(e) => updateNodeProperty(prop.name, e.target.value)}
97-
className={`${styles.input} ${config.isDarkTheme ? styles.inputDark : styles.inputLight}`}
98-
/>
99-
)}
172+
{renderPropertyInput(prop)}
100173
</div>
101174
)
102175
))}
103176
</div>
104177
)}
105178

106-
<hr></hr>
179+
<hr className={styles.divider} />
107180

108181
{/* Ports */}
109182
<div className={styles.section}>
110-
{/* Input Ports */}
111-
<div className={`${styles.sectionTitle} ${config.isDarkTheme ? styles.sectionTitleDark : styles.sectionTitleLight}`}>
112-
Input Ports
113-
</div>
183+
<div className={styles.sectionTitle}>Input Ports</div>
114184
{nodeType.inputs.map((input, index) => (
115185
<PortItem key={index} port={input} isDarkTheme={config.isDarkTheme} />
116186
))}
117187

118-
{/* Output Ports */}
119-
<div className={`${styles.sectionTitle} ${config.isDarkTheme ? styles.sectionTitleDark : styles.sectionTitleLight}`}
120-
style={{ marginTop: '20px' }}>
188+
<hr className={styles.divider} />
189+
<div className={styles.sectionTitle}>
121190
Output Ports
122191
</div>
123192
{nodeType.outputs.map((output, index) => (

0 commit comments

Comments
 (0)