@flowuent-org/diagramming-core 1.0.8 → 1.1.1
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/apps/diagramming/src/AutomationDiagramData.ts +22 -0
- package/apps/diagramming/src/components/AddNodeView.tsx +252 -252
- package/apps/diagramming/src/main.tsx +463 -463
- package/apps/diagramming/src/node-data.ts +664 -664
- package/apps/diagramming/src/stencil-items.ts +31 -31
- package/apps/diagramming/src/vite-env.d.ts +1 -1
- package/package.json +1 -1
- package/packages/diagrams/NODE_DATA_UPDATE_API.md +430 -430
- package/packages/diagrams/README.md +7 -463
- package/packages/diagrams/UNDO_REDO_API.md +306 -306
- package/packages/diagrams/package.json +27 -27
- package/packages/diagrams/project.json +42 -42
- package/packages/diagrams/rollup.config.js +26 -26
- package/packages/diagrams/src/DiagramFlow.tsx +7 -7
- package/packages/diagrams/src/index.ts +116 -116
- package/packages/diagrams/src/index.ts.bak +99 -99
- package/packages/diagrams/src/lib/atoms/CardEditableTitle.tsx +76 -76
- package/packages/diagrams/src/lib/atoms/ExpressionInput.tsx +437 -437
- package/packages/diagrams/src/lib/components/DiagramPanel.tsx +331 -331
- package/packages/diagrams/src/lib/components/automation/AISuggestionsModal.tsx +269 -0
- package/packages/diagrams/src/lib/components/automation/AISuggestionsPanel.tsx +227 -0
- package/packages/diagrams/src/lib/components/automation/AutomationAISuggestionNode.tsx +178 -115
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +133 -27
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +134 -28
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +132 -27
- package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +124 -17
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +122 -15
- package/packages/diagrams/src/lib/components/automation/index.ts +3 -0
- package/packages/diagrams/src/lib/contexts/onWorkflowNodeDelete.ts +65 -65
- package/packages/diagrams/src/lib/organisms/CustomEdge/useCreateBendPoint.tsx +121 -121
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeActionButtons.tsx +45 -45
- package/packages/diagrams/src/lib/templates/node-forms/CallForm.tsx +370 -370
- package/packages/diagrams/src/lib/templates/systemFlow/components/FloatingEdge.tsx +219 -219
- package/packages/diagrams/src/lib/types/card-node.ts +68 -68
- package/packages/diagrams/src/lib/types/node-types.ts +29 -29
- package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +1179 -1179
- package/packages/diagrams/tsconfig.lib.json +25 -25
- package/tsconfig.base.json +29 -30
- package/TRANSLATION_FIX_SUMMARY.md +0 -118
- package/packages/diagrams/I18N_SETUP.md +0 -126
|
@@ -1,437 +1,437 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import styled from '@emotion/styled';
|
|
3
|
-
import { Divider, Stack, Typography } from '@mui/material';
|
|
4
|
-
import { Expression, OperationExpression } from '../types/ndoe-form-types';
|
|
5
|
-
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import { BlockTypesKeys } from '../organisms/Card/card.params';
|
|
7
|
-
import ValidationMessage from './ValidationMessage';
|
|
8
|
-
import { validationEngine } from '../utils/validationEngine';
|
|
9
|
-
import { ValidationResult } from '../types/validation-types';
|
|
10
|
-
|
|
11
|
-
// Styled Components
|
|
12
|
-
export const StyledSelect: ReturnType<typeof styled.select> = styled.select`
|
|
13
|
-
width: 100%;
|
|
14
|
-
padding: 8px;
|
|
15
|
-
font-size: 14px;
|
|
16
|
-
border-radius: 4px;
|
|
17
|
-
border: 1px solid #ccc;
|
|
18
|
-
background-color: transparent;
|
|
19
|
-
color: #FFFFFF;
|
|
20
|
-
|
|
21
|
-
&:hover {
|
|
22
|
-
border-color: #000000;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
&:focus {
|
|
26
|
-
outline: none;
|
|
27
|
-
border-color: #1976d2;
|
|
28
|
-
}
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
export const StyledInput: ReturnType<typeof styled.input> = styled.input`
|
|
32
|
-
width: 100%;
|
|
33
|
-
padding: 8px;
|
|
34
|
-
font-size: 14px;
|
|
35
|
-
border-radius: 4px;
|
|
36
|
-
border: 1px solid #ccc;
|
|
37
|
-
background-color: transparent;
|
|
38
|
-
color: #FFFFFF;
|
|
39
|
-
|
|
40
|
-
&:hover {
|
|
41
|
-
border-color: #000000;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
&:focus {
|
|
45
|
-
outline: none;
|
|
46
|
-
border-color: #1976d2;
|
|
47
|
-
}
|
|
48
|
-
`;
|
|
49
|
-
|
|
50
|
-
// New styled components for the integrated input with dropdown
|
|
51
|
-
const InputWithDropdownContainer = styled.div`
|
|
52
|
-
position: relative;
|
|
53
|
-
display: flex;
|
|
54
|
-
align-items: center;
|
|
55
|
-
width: 100%;
|
|
56
|
-
`;
|
|
57
|
-
|
|
58
|
-
const StyledInputWithDropdown = styled.input`
|
|
59
|
-
width: 100%;
|
|
60
|
-
padding: 8px;
|
|
61
|
-
padding-right: 110px; /* Make room for dropdown */
|
|
62
|
-
font-size: 14px;
|
|
63
|
-
border-radius: 4px;
|
|
64
|
-
border: 1px solid #ccc;
|
|
65
|
-
background-color: transparent;
|
|
66
|
-
color: #FFFFFF;
|
|
67
|
-
|
|
68
|
-
&:hover {
|
|
69
|
-
border-color: #000000;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
&:focus {
|
|
73
|
-
outline: none;
|
|
74
|
-
border-color: #1976d2;
|
|
75
|
-
}
|
|
76
|
-
`;
|
|
77
|
-
|
|
78
|
-
const DropdownOverlay = styled.div`
|
|
79
|
-
position: absolute;
|
|
80
|
-
right: 2px;
|
|
81
|
-
top: 50%;
|
|
82
|
-
transform: translateY(-50%);
|
|
83
|
-
z-index: 10;
|
|
84
|
-
pointer-events: auto;
|
|
85
|
-
width: 100px;
|
|
86
|
-
`;
|
|
87
|
-
|
|
88
|
-
const StyledSelectOverlay = styled.select`
|
|
89
|
-
min-width: 100px;
|
|
90
|
-
height: 32px;
|
|
91
|
-
background-color: transparent;
|
|
92
|
-
cursor: pointer;
|
|
93
|
-
border: none;
|
|
94
|
-
outline: none;
|
|
95
|
-
padding: 4px 8px;
|
|
96
|
-
font-size: 12px;
|
|
97
|
-
color: #FFFFFF;
|
|
98
|
-
text-align: center;
|
|
99
|
-
border-radius: 4px;
|
|
100
|
-
|
|
101
|
-
&:hover {
|
|
102
|
-
background-color: rgba(255, 255, 255, 0.1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
&:focus {
|
|
106
|
-
background-color: rgba(255, 255, 255, 0.15);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
option {
|
|
110
|
-
background-color: #393939;
|
|
111
|
-
color: #FFFFFF;
|
|
112
|
-
padding: 8px;
|
|
113
|
-
}
|
|
114
|
-
`;
|
|
115
|
-
|
|
116
|
-
const OperatorContainer = styled.div`
|
|
117
|
-
display: flex;
|
|
118
|
-
align-items: center;
|
|
119
|
-
justify-content: space-between;
|
|
120
|
-
gap: 10px;
|
|
121
|
-
`;
|
|
122
|
-
|
|
123
|
-
const OperandWrapper = styled.div`
|
|
124
|
-
flex: 1;
|
|
125
|
-
`;
|
|
126
|
-
|
|
127
|
-
interface ExpressionInputProps {
|
|
128
|
-
value: Expression;
|
|
129
|
-
onChange: (newValue: Expression) => void;
|
|
130
|
-
label: string;
|
|
131
|
-
className?: string;
|
|
132
|
-
showDataTypeDropdown?: boolean;
|
|
133
|
-
dataType?: string;
|
|
134
|
-
onDataTypeChange?: (dataType: string) => void;
|
|
135
|
-
onValidationChange?: (isValid: boolean, errors: string[]) => void;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const operationOperandCounts: { [key: string]: number } = {
|
|
139
|
-
'+': 2,
|
|
140
|
-
'-': 2,
|
|
141
|
-
'*': 2,
|
|
142
|
-
'/': 2,
|
|
143
|
-
'>': 2,
|
|
144
|
-
'>=': 2,
|
|
145
|
-
'<': 2,
|
|
146
|
-
'<=': 2,
|
|
147
|
-
'=': 2,
|
|
148
|
-
'==': 2,
|
|
149
|
-
'!=': 2,
|
|
150
|
-
neg: 1,
|
|
151
|
-
not: 1,
|
|
152
|
-
and: 2,
|
|
153
|
-
or: 2,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const ExpressionInput: React.FC<ExpressionInputProps> = ({
|
|
157
|
-
value,
|
|
158
|
-
onChange,
|
|
159
|
-
label,
|
|
160
|
-
className = '',
|
|
161
|
-
showDataTypeDropdown = false,
|
|
162
|
-
dataType = 'String',
|
|
163
|
-
onDataTypeChange,
|
|
164
|
-
onValidationChange,
|
|
165
|
-
}) => {
|
|
166
|
-
const { t } = useTranslation();
|
|
167
|
-
const isOperation = value && typeof value === 'object' && 'operation' in value;
|
|
168
|
-
|
|
169
|
-
// State for validation results
|
|
170
|
-
const [operandValidations, setOperandValidations] = useState<ValidationResult[][]>([]);
|
|
171
|
-
const [showOperandValidations, setShowOperandValidations] = useState<boolean[]>([]);
|
|
172
|
-
const [primitiveValidations, setPrimitiveValidations] = useState<ValidationResult[]>([]);
|
|
173
|
-
const [showPrimitiveValidations, setShowPrimitiveValidations] = useState<boolean>(false);
|
|
174
|
-
|
|
175
|
-
// Validate primitive value using validation engine
|
|
176
|
-
const validatePrimitiveValue = (value: string): ValidationResult[] => {
|
|
177
|
-
return validationEngine.validatePrimitiveValue(value, dataType);
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// Re-validate primitive value when data type changes
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
if (!isOperation && typeof value === 'string') {
|
|
183
|
-
const validations = validatePrimitiveValue(value);
|
|
184
|
-
setPrimitiveValidations(validations);
|
|
185
|
-
setShowPrimitiveValidations(validations.length > 0);
|
|
186
|
-
}
|
|
187
|
-
}, [dataType, value, isOperation]);
|
|
188
|
-
|
|
189
|
-
const handlePrimitiveChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
190
|
-
const newValue = e.target.value;
|
|
191
|
-
onChange(newValue); // Update the parent directly
|
|
192
|
-
|
|
193
|
-
// Validate and show validation results if needed
|
|
194
|
-
const validations = validatePrimitiveValue(newValue);
|
|
195
|
-
setPrimitiveValidations(validations);
|
|
196
|
-
setShowPrimitiveValidations(validations.length > 0);
|
|
197
|
-
|
|
198
|
-
// Notify parent component about validation state
|
|
199
|
-
if (onValidationChange) {
|
|
200
|
-
const hasErrors = validationEngine.hasErrors(validations);
|
|
201
|
-
const errorMessages = validationEngine.getErrors(validations).map(v => v.message);
|
|
202
|
-
onValidationChange(!hasErrors, errorMessages);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const handleOperationTypeChange = (
|
|
207
|
-
e: React.ChangeEvent<HTMLSelectElement>,
|
|
208
|
-
) => {
|
|
209
|
-
const newOperation = e.target.value;
|
|
210
|
-
const operandCount = operationOperandCounts[newOperation] || 2;
|
|
211
|
-
const newOperands = Array(operandCount).fill('');
|
|
212
|
-
onChange({ operation: newOperation, operands: newOperands });
|
|
213
|
-
|
|
214
|
-
// Reset validation states for new operation
|
|
215
|
-
setOperandValidations(Array(operandCount).fill([]));
|
|
216
|
-
setShowOperandValidations(Array(operandCount).fill(false));
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Validate operands when they change
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
if (isOperation) {
|
|
222
|
-
const operationValue = value as OperationExpression;
|
|
223
|
-
const validations: ValidationResult[][] = [];
|
|
224
|
-
const showValidations: boolean[] = [];
|
|
225
|
-
|
|
226
|
-
operationValue.operands?.forEach((operand, index) => {
|
|
227
|
-
// Use validation engine to validate operand
|
|
228
|
-
const operandValidations = validationEngine.validateOperationOperands([operand], dataType);
|
|
229
|
-
validations.push(operandValidations);
|
|
230
|
-
|
|
231
|
-
// Show validation if operand has been interacted with (has value or was touched)
|
|
232
|
-
showValidations.push(!!operand || showOperandValidations[index] || false);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
setOperandValidations(validations);
|
|
236
|
-
setShowOperandValidations(showValidations);
|
|
237
|
-
|
|
238
|
-
// Notify parent component about validation state
|
|
239
|
-
if (onValidationChange) {
|
|
240
|
-
const allValidations = validations.flat();
|
|
241
|
-
const hasErrors = validationEngine.hasErrors(allValidations);
|
|
242
|
-
const errorMessages = validationEngine.getErrors(allValidations).map(v => v.message);
|
|
243
|
-
onValidationChange(!hasErrors, errorMessages);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}, [value, dataType, isOperation, showOperandValidations, onValidationChange]);
|
|
247
|
-
|
|
248
|
-
const handleOperandChange = (index: number, operandValue: string) => {
|
|
249
|
-
if (isOperation) {
|
|
250
|
-
const operationValue = value as OperationExpression;
|
|
251
|
-
const updatedOperands = [...operationValue.operands];
|
|
252
|
-
updatedOperands[index] = operandValue;
|
|
253
|
-
|
|
254
|
-
// Mark this operand as touched for validation display
|
|
255
|
-
const newShowValidations = [...showOperandValidations];
|
|
256
|
-
newShowValidations[index] = true;
|
|
257
|
-
setShowOperandValidations(newShowValidations);
|
|
258
|
-
|
|
259
|
-
// Clear validation when user starts typing
|
|
260
|
-
if (operandValidations[index] && operandValidations[index].length > 0) {
|
|
261
|
-
const newValidations = [...operandValidations];
|
|
262
|
-
newValidations[index] = [];
|
|
263
|
-
setOperandValidations(newValidations);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
onChange({ ...operationValue, operands: updatedOperands });
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const handleTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
271
|
-
const selectedValue = e.target.value;
|
|
272
|
-
if (selectedValue === 'primitive') {
|
|
273
|
-
onChange(String(value ?? '')); // Switch to primitive
|
|
274
|
-
// Clear operand validations when switching to primitive
|
|
275
|
-
setOperandValidations([]);
|
|
276
|
-
setShowOperandValidations([]);
|
|
277
|
-
// Clear primitive validations
|
|
278
|
-
setPrimitiveValidations([]);
|
|
279
|
-
setShowPrimitiveValidations(false);
|
|
280
|
-
} else {
|
|
281
|
-
// Always initialize with a proper operation structure when switching to operation mode
|
|
282
|
-
onChange({ operation: '+', operands: ['', ''] });
|
|
283
|
-
// Initialize validation states for new operation
|
|
284
|
-
setOperandValidations([[], []]); // Start with empty validations
|
|
285
|
-
setShowOperandValidations([false, false]); // Don't show validations initially
|
|
286
|
-
// Clear primitive validations
|
|
287
|
-
setPrimitiveValidations([]);
|
|
288
|
-
setShowPrimitiveValidations(false);
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const renderOperandInput = (operand: string, index: number) => (
|
|
293
|
-
<div>
|
|
294
|
-
<StyledInput
|
|
295
|
-
type="text"
|
|
296
|
-
value={operand}
|
|
297
|
-
onChange={(e) => handleOperandChange(index, e.target.value)}
|
|
298
|
-
placeholder={`Operand ${index + 1}`}
|
|
299
|
-
style={{
|
|
300
|
-
borderColor: showOperandValidations[index] && operandValidations[index]?.some(v => v.severity === 'error') ? '#d32f2f' : undefined
|
|
301
|
-
}}
|
|
302
|
-
/>
|
|
303
|
-
{showOperandValidations[index] && operandValidations[index]?.map((validation, vIndex) => (
|
|
304
|
-
<ValidationMessage
|
|
305
|
-
key={vIndex}
|
|
306
|
-
message={validation.message}
|
|
307
|
-
severity={validation.severity}
|
|
308
|
-
show={true}
|
|
309
|
-
/>
|
|
310
|
-
))}
|
|
311
|
-
</div>
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
const renderPrimitiveInput = () => {
|
|
315
|
-
if (showDataTypeDropdown) {
|
|
316
|
-
return (
|
|
317
|
-
<div>
|
|
318
|
-
<InputWithDropdownContainer>
|
|
319
|
-
<StyledInputWithDropdown
|
|
320
|
-
className="zd__primitive-input-with-dropdown"
|
|
321
|
-
type="text"
|
|
322
|
-
value={String(value ?? '')}
|
|
323
|
-
onChange={handlePrimitiveChange}
|
|
324
|
-
placeholder="Enter value"
|
|
325
|
-
style={{
|
|
326
|
-
borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
|
|
327
|
-
}}
|
|
328
|
-
/>
|
|
329
|
-
<DropdownOverlay>
|
|
330
|
-
<StyledSelectOverlay
|
|
331
|
-
value={dataType}
|
|
332
|
-
onChange={(e) => onDataTypeChange?.(e.target.value)}
|
|
333
|
-
>
|
|
334
|
-
{BlockTypesKeys.map((type) => (
|
|
335
|
-
<option key={type} value={type}>
|
|
336
|
-
{type}
|
|
337
|
-
</option>
|
|
338
|
-
))}
|
|
339
|
-
</StyledSelectOverlay>
|
|
340
|
-
</DropdownOverlay>
|
|
341
|
-
</InputWithDropdownContainer>
|
|
342
|
-
{showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
|
|
343
|
-
<ValidationMessage
|
|
344
|
-
key={vIndex}
|
|
345
|
-
message={validation.message}
|
|
346
|
-
severity={validation.severity}
|
|
347
|
-
show={true}
|
|
348
|
-
/>
|
|
349
|
-
))}
|
|
350
|
-
</div>
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return (
|
|
355
|
-
<div>
|
|
356
|
-
<StyledInput
|
|
357
|
-
type="text"
|
|
358
|
-
value={String(value ?? '')}
|
|
359
|
-
onChange={handlePrimitiveChange}
|
|
360
|
-
placeholder="Enter value"
|
|
361
|
-
style={{
|
|
362
|
-
borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
|
|
363
|
-
}}
|
|
364
|
-
/>
|
|
365
|
-
{showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
|
|
366
|
-
<ValidationMessage
|
|
367
|
-
key={vIndex}
|
|
368
|
-
message={validation.message}
|
|
369
|
-
severity={validation.severity}
|
|
370
|
-
show={true}
|
|
371
|
-
/>
|
|
372
|
-
))}
|
|
373
|
-
</div>
|
|
374
|
-
);
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
return (
|
|
378
|
-
<Stack
|
|
379
|
-
spacing={1}
|
|
380
|
-
>
|
|
381
|
-
<Divider />
|
|
382
|
-
<Typography component="label" sx={{ color: '#8E8E93' }}>
|
|
383
|
-
{label}
|
|
384
|
-
</Typography>
|
|
385
|
-
|
|
386
|
-
{/* Type Selection */}
|
|
387
|
-
<StyledSelect
|
|
388
|
-
value={isOperation ? 'operation' : 'primitive'}
|
|
389
|
-
onChange={handleTypeChange}
|
|
390
|
-
>
|
|
391
|
-
<option value="primitive" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.primitive')}</option>
|
|
392
|
-
<option value="operation" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.operation')}</option>
|
|
393
|
-
</StyledSelect>
|
|
394
|
-
|
|
395
|
-
{/* Primitive or Operation Input */}
|
|
396
|
-
{!isOperation ? (
|
|
397
|
-
renderPrimitiveInput()
|
|
398
|
-
) : (
|
|
399
|
-
<Stack spacing={1}>
|
|
400
|
-
{/* Operation Selection */}
|
|
401
|
-
<StyledSelect
|
|
402
|
-
value={(value as OperationExpression).operation || '+'}
|
|
403
|
-
onChange={handleOperationTypeChange}
|
|
404
|
-
>
|
|
405
|
-
<option value="+" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.add')}</option>
|
|
406
|
-
<option value="-" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.subtract')}</option>
|
|
407
|
-
<option value="*" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.multiply')}</option>
|
|
408
|
-
<option value="/" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.divide')}</option>
|
|
409
|
-
<option value=">" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThan')}</option>
|
|
410
|
-
<option value=">=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThanOrEqual')}</option>
|
|
411
|
-
<option value="<" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThan')}</option>
|
|
412
|
-
<option value="<=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThanOrEqual')}</option>
|
|
413
|
-
<option value="=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.assign')}</option>
|
|
414
|
-
<option value="==" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.equal')}</option>
|
|
415
|
-
<option value="!=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.notEqual')}</option>
|
|
416
|
-
<option value="neg" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.negate')}</option>
|
|
417
|
-
<option value="not" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.not')}</option>
|
|
418
|
-
<option value="and" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.and')}</option>
|
|
419
|
-
<option value="or" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.or')}</option>
|
|
420
|
-
</StyledSelect>
|
|
421
|
-
|
|
422
|
-
{/* Operand Inputs */}
|
|
423
|
-
<OperatorContainer>
|
|
424
|
-
{(value as OperationExpression).operands?.map((operand, index) => (
|
|
425
|
-
<OperandWrapper key={index}>
|
|
426
|
-
{renderOperandInput(operand || '', index)}
|
|
427
|
-
</OperandWrapper>
|
|
428
|
-
)) || []}
|
|
429
|
-
</OperatorContainer>
|
|
430
|
-
</Stack>
|
|
431
|
-
)}
|
|
432
|
-
<Divider />
|
|
433
|
-
</Stack>
|
|
434
|
-
);
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
export default ExpressionInput;
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { Divider, Stack, Typography } from '@mui/material';
|
|
4
|
+
import { Expression, OperationExpression } from '../types/ndoe-form-types';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { BlockTypesKeys } from '../organisms/Card/card.params';
|
|
7
|
+
import ValidationMessage from './ValidationMessage';
|
|
8
|
+
import { validationEngine } from '../utils/validationEngine';
|
|
9
|
+
import { ValidationResult } from '../types/validation-types';
|
|
10
|
+
|
|
11
|
+
// Styled Components
|
|
12
|
+
export const StyledSelect: ReturnType<typeof styled.select> = styled.select`
|
|
13
|
+
width: 100%;
|
|
14
|
+
padding: 8px;
|
|
15
|
+
font-size: 14px;
|
|
16
|
+
border-radius: 4px;
|
|
17
|
+
border: 1px solid #ccc;
|
|
18
|
+
background-color: transparent;
|
|
19
|
+
color: #FFFFFF;
|
|
20
|
+
|
|
21
|
+
&:hover {
|
|
22
|
+
border-color: #000000;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&:focus {
|
|
26
|
+
outline: none;
|
|
27
|
+
border-color: #1976d2;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export const StyledInput: ReturnType<typeof styled.input> = styled.input`
|
|
32
|
+
width: 100%;
|
|
33
|
+
padding: 8px;
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
border: 1px solid #ccc;
|
|
37
|
+
background-color: transparent;
|
|
38
|
+
color: #FFFFFF;
|
|
39
|
+
|
|
40
|
+
&:hover {
|
|
41
|
+
border-color: #000000;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:focus {
|
|
45
|
+
outline: none;
|
|
46
|
+
border-color: #1976d2;
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
// New styled components for the integrated input with dropdown
|
|
51
|
+
const InputWithDropdownContainer = styled.div`
|
|
52
|
+
position: relative;
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
width: 100%;
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const StyledInputWithDropdown = styled.input`
|
|
59
|
+
width: 100%;
|
|
60
|
+
padding: 8px;
|
|
61
|
+
padding-right: 110px; /* Make room for dropdown */
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
border: 1px solid #ccc;
|
|
65
|
+
background-color: transparent;
|
|
66
|
+
color: #FFFFFF;
|
|
67
|
+
|
|
68
|
+
&:hover {
|
|
69
|
+
border-color: #000000;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&:focus {
|
|
73
|
+
outline: none;
|
|
74
|
+
border-color: #1976d2;
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const DropdownOverlay = styled.div`
|
|
79
|
+
position: absolute;
|
|
80
|
+
right: 2px;
|
|
81
|
+
top: 50%;
|
|
82
|
+
transform: translateY(-50%);
|
|
83
|
+
z-index: 10;
|
|
84
|
+
pointer-events: auto;
|
|
85
|
+
width: 100px;
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const StyledSelectOverlay = styled.select`
|
|
89
|
+
min-width: 100px;
|
|
90
|
+
height: 32px;
|
|
91
|
+
background-color: transparent;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
border: none;
|
|
94
|
+
outline: none;
|
|
95
|
+
padding: 4px 8px;
|
|
96
|
+
font-size: 12px;
|
|
97
|
+
color: #FFFFFF;
|
|
98
|
+
text-align: center;
|
|
99
|
+
border-radius: 4px;
|
|
100
|
+
|
|
101
|
+
&:hover {
|
|
102
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&:focus {
|
|
106
|
+
background-color: rgba(255, 255, 255, 0.15);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
option {
|
|
110
|
+
background-color: #393939;
|
|
111
|
+
color: #FFFFFF;
|
|
112
|
+
padding: 8px;
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
const OperatorContainer = styled.div`
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: space-between;
|
|
120
|
+
gap: 10px;
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const OperandWrapper = styled.div`
|
|
124
|
+
flex: 1;
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
interface ExpressionInputProps {
|
|
128
|
+
value: Expression;
|
|
129
|
+
onChange: (newValue: Expression) => void;
|
|
130
|
+
label: string;
|
|
131
|
+
className?: string;
|
|
132
|
+
showDataTypeDropdown?: boolean;
|
|
133
|
+
dataType?: string;
|
|
134
|
+
onDataTypeChange?: (dataType: string) => void;
|
|
135
|
+
onValidationChange?: (isValid: boolean, errors: string[]) => void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const operationOperandCounts: { [key: string]: number } = {
|
|
139
|
+
'+': 2,
|
|
140
|
+
'-': 2,
|
|
141
|
+
'*': 2,
|
|
142
|
+
'/': 2,
|
|
143
|
+
'>': 2,
|
|
144
|
+
'>=': 2,
|
|
145
|
+
'<': 2,
|
|
146
|
+
'<=': 2,
|
|
147
|
+
'=': 2,
|
|
148
|
+
'==': 2,
|
|
149
|
+
'!=': 2,
|
|
150
|
+
neg: 1,
|
|
151
|
+
not: 1,
|
|
152
|
+
and: 2,
|
|
153
|
+
or: 2,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const ExpressionInput: React.FC<ExpressionInputProps> = ({
|
|
157
|
+
value,
|
|
158
|
+
onChange,
|
|
159
|
+
label,
|
|
160
|
+
className = '',
|
|
161
|
+
showDataTypeDropdown = false,
|
|
162
|
+
dataType = 'String',
|
|
163
|
+
onDataTypeChange,
|
|
164
|
+
onValidationChange,
|
|
165
|
+
}) => {
|
|
166
|
+
const { t } = useTranslation();
|
|
167
|
+
const isOperation = value && typeof value === 'object' && 'operation' in value;
|
|
168
|
+
|
|
169
|
+
// State for validation results
|
|
170
|
+
const [operandValidations, setOperandValidations] = useState<ValidationResult[][]>([]);
|
|
171
|
+
const [showOperandValidations, setShowOperandValidations] = useState<boolean[]>([]);
|
|
172
|
+
const [primitiveValidations, setPrimitiveValidations] = useState<ValidationResult[]>([]);
|
|
173
|
+
const [showPrimitiveValidations, setShowPrimitiveValidations] = useState<boolean>(false);
|
|
174
|
+
|
|
175
|
+
// Validate primitive value using validation engine
|
|
176
|
+
const validatePrimitiveValue = (value: string): ValidationResult[] => {
|
|
177
|
+
return validationEngine.validatePrimitiveValue(value, dataType);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Re-validate primitive value when data type changes
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!isOperation && typeof value === 'string') {
|
|
183
|
+
const validations = validatePrimitiveValue(value);
|
|
184
|
+
setPrimitiveValidations(validations);
|
|
185
|
+
setShowPrimitiveValidations(validations.length > 0);
|
|
186
|
+
}
|
|
187
|
+
}, [dataType, value, isOperation]);
|
|
188
|
+
|
|
189
|
+
const handlePrimitiveChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
190
|
+
const newValue = e.target.value;
|
|
191
|
+
onChange(newValue); // Update the parent directly
|
|
192
|
+
|
|
193
|
+
// Validate and show validation results if needed
|
|
194
|
+
const validations = validatePrimitiveValue(newValue);
|
|
195
|
+
setPrimitiveValidations(validations);
|
|
196
|
+
setShowPrimitiveValidations(validations.length > 0);
|
|
197
|
+
|
|
198
|
+
// Notify parent component about validation state
|
|
199
|
+
if (onValidationChange) {
|
|
200
|
+
const hasErrors = validationEngine.hasErrors(validations);
|
|
201
|
+
const errorMessages = validationEngine.getErrors(validations).map(v => v.message);
|
|
202
|
+
onValidationChange(!hasErrors, errorMessages);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleOperationTypeChange = (
|
|
207
|
+
e: React.ChangeEvent<HTMLSelectElement>,
|
|
208
|
+
) => {
|
|
209
|
+
const newOperation = e.target.value;
|
|
210
|
+
const operandCount = operationOperandCounts[newOperation] || 2;
|
|
211
|
+
const newOperands = Array(operandCount).fill('');
|
|
212
|
+
onChange({ operation: newOperation, operands: newOperands });
|
|
213
|
+
|
|
214
|
+
// Reset validation states for new operation
|
|
215
|
+
setOperandValidations(Array(operandCount).fill([]));
|
|
216
|
+
setShowOperandValidations(Array(operandCount).fill(false));
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Validate operands when they change
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
if (isOperation) {
|
|
222
|
+
const operationValue = value as OperationExpression;
|
|
223
|
+
const validations: ValidationResult[][] = [];
|
|
224
|
+
const showValidations: boolean[] = [];
|
|
225
|
+
|
|
226
|
+
operationValue.operands?.forEach((operand, index) => {
|
|
227
|
+
// Use validation engine to validate operand
|
|
228
|
+
const operandValidations = validationEngine.validateOperationOperands([operand], dataType);
|
|
229
|
+
validations.push(operandValidations);
|
|
230
|
+
|
|
231
|
+
// Show validation if operand has been interacted with (has value or was touched)
|
|
232
|
+
showValidations.push(!!operand || showOperandValidations[index] || false);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
setOperandValidations(validations);
|
|
236
|
+
setShowOperandValidations(showValidations);
|
|
237
|
+
|
|
238
|
+
// Notify parent component about validation state
|
|
239
|
+
if (onValidationChange) {
|
|
240
|
+
const allValidations = validations.flat();
|
|
241
|
+
const hasErrors = validationEngine.hasErrors(allValidations);
|
|
242
|
+
const errorMessages = validationEngine.getErrors(allValidations).map(v => v.message);
|
|
243
|
+
onValidationChange(!hasErrors, errorMessages);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}, [value, dataType, isOperation, showOperandValidations, onValidationChange]);
|
|
247
|
+
|
|
248
|
+
const handleOperandChange = (index: number, operandValue: string) => {
|
|
249
|
+
if (isOperation) {
|
|
250
|
+
const operationValue = value as OperationExpression;
|
|
251
|
+
const updatedOperands = [...operationValue.operands];
|
|
252
|
+
updatedOperands[index] = operandValue;
|
|
253
|
+
|
|
254
|
+
// Mark this operand as touched for validation display
|
|
255
|
+
const newShowValidations = [...showOperandValidations];
|
|
256
|
+
newShowValidations[index] = true;
|
|
257
|
+
setShowOperandValidations(newShowValidations);
|
|
258
|
+
|
|
259
|
+
// Clear validation when user starts typing
|
|
260
|
+
if (operandValidations[index] && operandValidations[index].length > 0) {
|
|
261
|
+
const newValidations = [...operandValidations];
|
|
262
|
+
newValidations[index] = [];
|
|
263
|
+
setOperandValidations(newValidations);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
onChange({ ...operationValue, operands: updatedOperands });
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const handleTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
271
|
+
const selectedValue = e.target.value;
|
|
272
|
+
if (selectedValue === 'primitive') {
|
|
273
|
+
onChange(String(value ?? '')); // Switch to primitive
|
|
274
|
+
// Clear operand validations when switching to primitive
|
|
275
|
+
setOperandValidations([]);
|
|
276
|
+
setShowOperandValidations([]);
|
|
277
|
+
// Clear primitive validations
|
|
278
|
+
setPrimitiveValidations([]);
|
|
279
|
+
setShowPrimitiveValidations(false);
|
|
280
|
+
} else {
|
|
281
|
+
// Always initialize with a proper operation structure when switching to operation mode
|
|
282
|
+
onChange({ operation: '+', operands: ['', ''] });
|
|
283
|
+
// Initialize validation states for new operation
|
|
284
|
+
setOperandValidations([[], []]); // Start with empty validations
|
|
285
|
+
setShowOperandValidations([false, false]); // Don't show validations initially
|
|
286
|
+
// Clear primitive validations
|
|
287
|
+
setPrimitiveValidations([]);
|
|
288
|
+
setShowPrimitiveValidations(false);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const renderOperandInput = (operand: string, index: number) => (
|
|
293
|
+
<div>
|
|
294
|
+
<StyledInput
|
|
295
|
+
type="text"
|
|
296
|
+
value={operand}
|
|
297
|
+
onChange={(e) => handleOperandChange(index, e.target.value)}
|
|
298
|
+
placeholder={`Operand ${index + 1}`}
|
|
299
|
+
style={{
|
|
300
|
+
borderColor: showOperandValidations[index] && operandValidations[index]?.some(v => v.severity === 'error') ? '#d32f2f' : undefined
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
{showOperandValidations[index] && operandValidations[index]?.map((validation, vIndex) => (
|
|
304
|
+
<ValidationMessage
|
|
305
|
+
key={vIndex}
|
|
306
|
+
message={validation.message}
|
|
307
|
+
severity={validation.severity}
|
|
308
|
+
show={true}
|
|
309
|
+
/>
|
|
310
|
+
))}
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const renderPrimitiveInput = () => {
|
|
315
|
+
if (showDataTypeDropdown) {
|
|
316
|
+
return (
|
|
317
|
+
<div>
|
|
318
|
+
<InputWithDropdownContainer>
|
|
319
|
+
<StyledInputWithDropdown
|
|
320
|
+
className="zd__primitive-input-with-dropdown"
|
|
321
|
+
type="text"
|
|
322
|
+
value={String(value ?? '')}
|
|
323
|
+
onChange={handlePrimitiveChange}
|
|
324
|
+
placeholder="Enter value"
|
|
325
|
+
style={{
|
|
326
|
+
borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
|
|
327
|
+
}}
|
|
328
|
+
/>
|
|
329
|
+
<DropdownOverlay>
|
|
330
|
+
<StyledSelectOverlay
|
|
331
|
+
value={dataType}
|
|
332
|
+
onChange={(e) => onDataTypeChange?.(e.target.value)}
|
|
333
|
+
>
|
|
334
|
+
{BlockTypesKeys.map((type) => (
|
|
335
|
+
<option key={type} value={type}>
|
|
336
|
+
{type}
|
|
337
|
+
</option>
|
|
338
|
+
))}
|
|
339
|
+
</StyledSelectOverlay>
|
|
340
|
+
</DropdownOverlay>
|
|
341
|
+
</InputWithDropdownContainer>
|
|
342
|
+
{showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
|
|
343
|
+
<ValidationMessage
|
|
344
|
+
key={vIndex}
|
|
345
|
+
message={validation.message}
|
|
346
|
+
severity={validation.severity}
|
|
347
|
+
show={true}
|
|
348
|
+
/>
|
|
349
|
+
))}
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<div>
|
|
356
|
+
<StyledInput
|
|
357
|
+
type="text"
|
|
358
|
+
value={String(value ?? '')}
|
|
359
|
+
onChange={handlePrimitiveChange}
|
|
360
|
+
placeholder="Enter value"
|
|
361
|
+
style={{
|
|
362
|
+
borderColor: showPrimitiveValidations ? '#d32f2f' : undefined
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
365
|
+
{showPrimitiveValidations && primitiveValidations.map((validation, vIndex) => (
|
|
366
|
+
<ValidationMessage
|
|
367
|
+
key={vIndex}
|
|
368
|
+
message={validation.message}
|
|
369
|
+
severity={validation.severity}
|
|
370
|
+
show={true}
|
|
371
|
+
/>
|
|
372
|
+
))}
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<Stack
|
|
379
|
+
spacing={1}
|
|
380
|
+
>
|
|
381
|
+
<Divider />
|
|
382
|
+
<Typography component="label" sx={{ color: '#8E8E93' }}>
|
|
383
|
+
{label}
|
|
384
|
+
</Typography>
|
|
385
|
+
|
|
386
|
+
{/* Type Selection */}
|
|
387
|
+
<StyledSelect
|
|
388
|
+
value={isOperation ? 'operation' : 'primitive'}
|
|
389
|
+
onChange={handleTypeChange}
|
|
390
|
+
>
|
|
391
|
+
<option value="primitive" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.primitive')}</option>
|
|
392
|
+
<option value="operation" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('expression.operation')}</option>
|
|
393
|
+
</StyledSelect>
|
|
394
|
+
|
|
395
|
+
{/* Primitive or Operation Input */}
|
|
396
|
+
{!isOperation ? (
|
|
397
|
+
renderPrimitiveInput()
|
|
398
|
+
) : (
|
|
399
|
+
<Stack spacing={1}>
|
|
400
|
+
{/* Operation Selection */}
|
|
401
|
+
<StyledSelect
|
|
402
|
+
value={(value as OperationExpression).operation || '+'}
|
|
403
|
+
onChange={handleOperationTypeChange}
|
|
404
|
+
>
|
|
405
|
+
<option value="+" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.add')}</option>
|
|
406
|
+
<option value="-" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.subtract')}</option>
|
|
407
|
+
<option value="*" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.multiply')}</option>
|
|
408
|
+
<option value="/" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.divide')}</option>
|
|
409
|
+
<option value=">" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThan')}</option>
|
|
410
|
+
<option value=">=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.greaterThanOrEqual')}</option>
|
|
411
|
+
<option value="<" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThan')}</option>
|
|
412
|
+
<option value="<=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.lessThanOrEqual')}</option>
|
|
413
|
+
<option value="=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.assign')}</option>
|
|
414
|
+
<option value="==" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.equal')}</option>
|
|
415
|
+
<option value="!=" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.notEqual')}</option>
|
|
416
|
+
<option value="neg" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.negate')}</option>
|
|
417
|
+
<option value="not" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.not')}</option>
|
|
418
|
+
<option value="and" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.and')}</option>
|
|
419
|
+
<option value="or" style={{ color: '#FFFFFF', backgroundColor: '#393939' }}>{t('operator.or')}</option>
|
|
420
|
+
</StyledSelect>
|
|
421
|
+
|
|
422
|
+
{/* Operand Inputs */}
|
|
423
|
+
<OperatorContainer>
|
|
424
|
+
{(value as OperationExpression).operands?.map((operand, index) => (
|
|
425
|
+
<OperandWrapper key={index}>
|
|
426
|
+
{renderOperandInput(operand || '', index)}
|
|
427
|
+
</OperandWrapper>
|
|
428
|
+
)) || []}
|
|
429
|
+
</OperatorContainer>
|
|
430
|
+
</Stack>
|
|
431
|
+
)}
|
|
432
|
+
<Divider />
|
|
433
|
+
</Stack>
|
|
434
|
+
);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
export default ExpressionInput;
|