Skip to content

Commit dfbdaa0

Browse files
SF-05 Subflow inputs outputs (#32)
* Start SF-05 * Add Edit Subflow Template button to title of subflow editor * Correctly specify type to buttons in forms * Add new nodes to redux state instead of diagram engine in flow-canvas.tsx * Correctly render changes to draggable nodes in draggable-node-wrapper.tsx * Write three new updateSubflow(), updateSubflowInputs() and updateSubflowOutpus() in flow.logic * Write NodeLogic.createFlowNode() * Write new editFlow(), editSubflow(), and editFlowEntityById() methods in builder.logic * Render custom port labels in flow/node.logic * Create new in ports when inputs increase in flow/node.logic (handle new nodes being created in redux state) * Add input and output controls to subflow <Workspace /> that create and remove new 'in' and 'out' nodes in subflow * Standardize palette node icons * Write new builder.logic tests * Write new tests for flow.slice * Add palette node entities to in/out node instances * Update node.logic tests * Update graph.logic tests * Finish SF-05
1 parent 8564e23 commit dfbdaa0

File tree

24 files changed

+1910
-331
lines changed

24 files changed

+1910
-331
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,11 @@ The backlog is organized by epic, with each task having a unique ID, description
217217

218218
| To Do | In Progress | In Review | Done |
219219
| ----- | ----------- | --------- | ----- |
220-
| SF-05 | | | SF-04 |
220+
| | | | SF-04 |
221221
| | | | SF-01 |
222222
| | | | SF-02 |
223223
| | | | SF-03 |
224+
| | | | SF-05 |
224225

225226
### Progress Tracking
226227

packages/flow-client/src/app/components/builder/secondary-sidebar.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ const StyledSecondarySidebar = styled(TabbedSidebar)`
1717
overflow: hidden; // Prevents overflow of child components
1818
1919
.workspace-section {
20-
min-height: 50px;
20+
min-height: 130px;
21+
22+
&:has(.flow) {
23+
min-height: 50px;
24+
}
2125
2226
&.collapsed {
2327
min-height: 0;

packages/flow-client/src/app/components/builder/workspace.tsx

Lines changed: 181 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1+
import { useCallback, useEffect, useState } from 'react';
12
import styled from 'styled-components';
2-
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
3-
import {
4-
EDITING_TYPE,
5-
builderActions,
6-
selectActiveFlow,
7-
} from '../../redux/modules/builder/builder.slice';
3+
4+
import { useAppDispatch, useAppLogic, useAppSelector } from '../../redux/hooks';
5+
import { selectActiveFlow } from '../../redux/modules/builder/builder.slice';
86
import {
97
SubflowEntity,
108
selectFlowEntityById,
119
} from '../../redux/modules/flow/flow.slice';
12-
import { useCallback } from 'react';
1310

1411
const StyledWorkspace = styled.div`
1512
display: flex;
@@ -18,25 +15,28 @@ const StyledWorkspace = styled.div`
1815
color: var(--color-text-sharp);
1916
font-size: 0.8em;
2017
21-
p {
18+
.row {
2219
display: flex;
20+
align-items: center;
2321
gap: 5px;
2422
2523
margin: 0;
2624
27-
.type {
25+
label {
2826
font-weight: bold;
2927
text-transform: capitalize;
28+
flex: 0 0 55px;
3029
}
30+
}
3131
32+
.title {
3233
.name {
3334
text-overflow: ellipsis;
3435
overflow: hidden;
3536
white-space: nowrap;
3637
}
3738
3839
i {
39-
margin-left: 5px;
4040
color: var(--color-text-medium);
4141
cursor: pointer;
4242
@@ -45,61 +45,193 @@ const StyledWorkspace = styled.div`
4545
}
4646
}
4747
}
48+
49+
.control-group {
50+
display: flex;
51+
margin-top: 10px;
52+
53+
button,
54+
input {
55+
background-color: var(--color-background-element-light);
56+
border: 1px solid var(--color-border-sharp);
57+
border-right-style: none;
58+
color: var(--color-text-sharp);
59+
cursor: pointer;
60+
padding: 5px 10px;
61+
width: 30px;
62+
height: 30px;
63+
64+
&:first-child {
65+
border-radius: 2px 0 0 2px;
66+
}
67+
68+
&:last-child {
69+
border-right-style: solid;
70+
border-radius: 0 2px 2px 0;
71+
}
72+
73+
&.active,
74+
&:active {
75+
background-color: var(--color-background-element-medium);
76+
color: var(--color-active-text);
77+
}
78+
}
79+
80+
input:focus {
81+
background-color: var(--color-background-element-focus);
82+
outline: 0;
83+
}
84+
}
4885
`;
4986

5087
export const Workspace = () => {
5188
const dispatch = useAppDispatch();
89+
const builderLogic = useAppLogic().builder;
90+
const flowLogic = useAppLogic().flow;
5291
const activeFlowId = useAppSelector(selectActiveFlow);
5392
const activeFlow = useAppSelector(state =>
5493
activeFlowId ? selectFlowEntityById(state, activeFlowId) : null
5594
);
5695

96+
const inputs = (activeFlow as SubflowEntity)?.in?.length ?? 0;
97+
const outputs = (activeFlow as SubflowEntity)?.out?.length ?? 0;
98+
const [currentOutputs, setCurrentOutputs] = useState(outputs);
99+
57100
const handleEditClick = useCallback(() => {
58-
if (!activeFlowId || !activeFlow) {
101+
if (!activeFlowId) {
59102
return;
60103
}
61104

62-
dispatch(
63-
builderActions.setEditing({
64-
id: activeFlowId,
65-
type: {
66-
flow: EDITING_TYPE.FLOW,
67-
subflow: EDITING_TYPE.SUBFLOW,
68-
}[activeFlow.type],
69-
data: {
70-
info: activeFlow.info,
71-
name: activeFlow.name,
72-
env: activeFlow.env,
73-
...{
74-
flow: {},
75-
subflow: {
76-
color: (activeFlow as SubflowEntity).color,
77-
icon: (activeFlow as SubflowEntity).icon,
78-
category: (activeFlow as SubflowEntity).category,
79-
inputLabels: (activeFlow as SubflowEntity)
80-
.inputLabels,
81-
outputLabels: (activeFlow as SubflowEntity)
82-
.outputLabels,
83-
},
84-
}[activeFlow.type],
85-
},
86-
})
87-
);
88-
}, [dispatch, activeFlowId, activeFlow]);
105+
dispatch(builderLogic.editFlowEntityById(activeFlowId));
106+
}, [dispatch, activeFlowId, builderLogic]);
107+
108+
const handleInputsChange = useCallback(
109+
(inputs: number) => {
110+
if (!activeFlowId || !activeFlow || activeFlow.type !== 'subflow') {
111+
return;
112+
}
113+
114+
dispatch(flowLogic.updateSubflowInputs(activeFlow, inputs));
115+
},
116+
[activeFlow, activeFlowId, dispatch, flowLogic]
117+
);
118+
119+
const handleOutputsChange = useCallback(
120+
(outputs: number) => {
121+
if (!activeFlowId || !activeFlow || activeFlow.type !== 'subflow') {
122+
return;
123+
}
124+
125+
dispatch(flowLogic.updateSubflowOutputs(activeFlow, outputs));
126+
},
127+
[activeFlow, activeFlowId, dispatch, flowLogic]
128+
);
129+
130+
const handleIncrementOutputs = useCallback(() => {
131+
const newOutputs = currentOutputs + 1;
132+
setCurrentOutputs(newOutputs);
133+
handleOutputsChange(newOutputs);
134+
}, [currentOutputs, handleOutputsChange]);
135+
136+
const handleDecrementOutputs = useCallback(() => {
137+
const newOutputs = Math.max(currentOutputs - 1, 0);
138+
setCurrentOutputs(newOutputs);
139+
handleOutputsChange(newOutputs);
140+
}, [currentOutputs, handleOutputsChange]);
141+
142+
const handleCurrentOutputsChange = useCallback(
143+
(e: React.ChangeEvent<HTMLInputElement>) => {
144+
const newOutputs = Number(e.target.value);
145+
setCurrentOutputs(
146+
Math.max(isNaN(newOutputs) ? currentOutputs : newOutputs, 0)
147+
);
148+
},
149+
[currentOutputs]
150+
);
151+
152+
const handleOutputsSubmit = useCallback(
153+
(e: React.FormEvent<HTMLFormElement>) => {
154+
e.preventDefault();
155+
e.currentTarget.querySelector('input')?.blur();
156+
157+
handleOutputsChange(currentOutputs);
158+
},
159+
[currentOutputs, handleOutputsChange]
160+
);
161+
162+
useEffect(() => {
163+
setCurrentOutputs(outputs);
164+
}, [outputs]);
89165

90166
return (
91-
<StyledWorkspace className="workspace">
92-
{activeFlow ? (
93-
<p>
94-
<span className="type">{activeFlow.type}:</span>{' '}
95-
<span className="name">{activeFlow.name} </span>
96-
<i
97-
className="fa-solid fa-pencil"
98-
onClick={handleEditClick}
99-
/>
100-
</p>
167+
<StyledWorkspace className={`workspace ${activeFlow?.type ?? ''}`}>
168+
{!activeFlow ? (
169+
<p className="empty">No active workspace</p>
101170
) : (
102-
<p>No active workspace</p>
171+
<>
172+
<div className="row title">
173+
<label className="type">{activeFlow.type}:</label>{' '}
174+
<span className="name">{activeFlow.name} </span>
175+
<i
176+
className="fa-solid fa-pencil"
177+
onClick={handleEditClick}
178+
/>
179+
</div>
180+
181+
{activeFlow.type === 'subflow' && (
182+
<>
183+
<div className="row inputs">
184+
<label>Inputs: </label>
185+
<span className="control-group">
186+
<button
187+
type="button"
188+
className={inputs === 0 ? 'active' : ''}
189+
onClick={() => handleInputsChange(0)}
190+
>
191+
0
192+
</button>
193+
194+
<button
195+
type="button"
196+
className={inputs === 1 ? 'active' : ''}
197+
onClick={() => handleInputsChange(1)}
198+
>
199+
1
200+
</button>
201+
</span>
202+
</div>
203+
204+
<div className="row outputs">
205+
<label>Outputs: </label>
206+
<form
207+
className="control-group"
208+
onSubmit={handleOutputsSubmit}
209+
onBlur={handleOutputsSubmit}
210+
>
211+
<button
212+
type="button"
213+
onClick={handleDecrementOutputs}
214+
>
215+
-
216+
</button>
217+
218+
<input
219+
type="text"
220+
value={currentOutputs}
221+
onChange={handleCurrentOutputsChange}
222+
/>
223+
224+
<button
225+
type="button"
226+
onClick={handleIncrementOutputs}
227+
>
228+
+
229+
</button>
230+
</form>
231+
</div>
232+
</>
233+
)}
234+
</>
103235
)}
104236
</StyledWorkspace>
105237
);

0 commit comments

Comments
 (0)