@workbench-kit/shell-react 0.0.2-prototype.0.2.43 → 0.0.2-prototype.0.2.45
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/README.md +39 -0
- package/package.json +10 -9
- package/src/editor/workspace-reconcile.tsx +12 -7
- package/src/field-remap/chrome-labels.ts +99 -0
- package/src/field-remap/convert-note-editor.tsx +115 -106
- package/src/field-remap/detail-panel.tsx +524 -456
- package/src/field-remap/document-io.tsx +299 -0
- package/src/field-remap/flow.tsx +188 -126
- package/src/field-remap/panel.tsx +60 -0
- package/src/field-remap/transform-options-editor.tsx +64 -48
- package/src/field-remap/view.css +46 -147
- package/src/keybinding-management-settings.ts +4 -0
- package/src/management/keybinding-settings-view.tsx +31 -0
- package/src/management/keybinding-settings.tsx +4 -21
- package/src/management/use-keybinding-management.ts +4 -4
- package/src/shell/provider.tsx +10 -1
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { useMemo, useState, type JSX } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import { useId, useMemo, useState, type JSX } from 'react';
|
|
2
|
+
import { WorkbenchPropertyInline } from '@workbench-kit/react/layout';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Select,
|
|
6
|
+
WorkbenchPropertyHint,
|
|
7
|
+
WorkbenchPropertyRow,
|
|
8
|
+
WorkbenchPropertySection,
|
|
9
|
+
WorkbenchPropertyStack,
|
|
10
|
+
} from '@workbench-kit/react/primitives';
|
|
3
11
|
import {
|
|
4
12
|
MAX_TRANSFORM_CHAIN,
|
|
5
13
|
findSourceField,
|
|
@@ -109,6 +117,14 @@ export function FieldRemapDetailPanel({
|
|
|
109
117
|
const [paletteId, setPaletteId] = useState<string>('');
|
|
110
118
|
const [pendingOperatorFieldId, setPendingOperatorFieldId] = useState('');
|
|
111
119
|
const [pendingOperatorSlotId, setPendingOperatorSlotId] = useState('');
|
|
120
|
+
const propertyId = useId();
|
|
121
|
+
const operatorAddInputId = `${propertyId}-operator-add-input`;
|
|
122
|
+
const operatorOutputId = `${propertyId}-operator-output`;
|
|
123
|
+
const operatorInputId = `${propertyId}-operator-input`;
|
|
124
|
+
const operatorAddOutputId = `${propertyId}-operator-add-output`;
|
|
125
|
+
const paletteSelectId = `${propertyId}-palette-select`;
|
|
126
|
+
const itemSourceId = `${propertyId}-item-source`;
|
|
127
|
+
const itemTargetId = `${propertyId}-item-target`;
|
|
112
128
|
const flatSources = useMemo(() => flattenSourceFields(sources), [sources]);
|
|
113
129
|
const flatTargets = useMemo(() => flattenTargetSlots(targets), [targets]);
|
|
114
130
|
const commitOperators = (next: readonly MappingOperator[]) => {
|
|
@@ -125,8 +141,16 @@ export function FieldRemapDetailPanel({
|
|
|
125
141
|
data-testid="field-remap-detail"
|
|
126
142
|
aria-label="Binding details"
|
|
127
143
|
>
|
|
128
|
-
<
|
|
129
|
-
|
|
144
|
+
<WorkbenchPropertyStack gap="sm">
|
|
145
|
+
<WorkbenchPropertySection title={emptyDetailTitle}>
|
|
146
|
+
<WorkbenchPropertyHint
|
|
147
|
+
className="workbench-field-remap-detail__muted"
|
|
148
|
+
data-testid="field-remap-detail-empty-hint"
|
|
149
|
+
>
|
|
150
|
+
{emptyDetailDescription}
|
|
151
|
+
</WorkbenchPropertyHint>
|
|
152
|
+
</WorkbenchPropertySection>
|
|
153
|
+
</WorkbenchPropertyStack>
|
|
130
154
|
</aside>
|
|
131
155
|
);
|
|
132
156
|
}
|
|
@@ -139,47 +163,51 @@ export function FieldRemapDetailPanel({
|
|
|
139
163
|
data-testid="field-remap-detail"
|
|
140
164
|
aria-label="Draft convert"
|
|
141
165
|
>
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
166
|
+
<WorkbenchPropertyStack gap="sm">
|
|
167
|
+
<WorkbenchPropertySection
|
|
168
|
+
title="Draft convert"
|
|
169
|
+
actions={
|
|
170
|
+
!readOnly ? (
|
|
171
|
+
<Button
|
|
172
|
+
compact
|
|
173
|
+
type="button"
|
|
174
|
+
data-testid="field-remap-detail-discard-draft"
|
|
175
|
+
onClick={() => {
|
|
176
|
+
onDiscardDraft?.(selection.localId);
|
|
177
|
+
onSelectionChange(null);
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
Discard
|
|
181
|
+
</Button>
|
|
182
|
+
) : null
|
|
183
|
+
}
|
|
184
|
+
>
|
|
185
|
+
<WorkbenchPropertyStack gap="sm">
|
|
186
|
+
<WorkbenchPropertyRow label="Convert">
|
|
187
|
+
<code data-testid="field-remap-detail-draft-id">
|
|
188
|
+
{definition?.label ?? draft?.transformId ?? selection.localId}
|
|
189
|
+
</code>
|
|
190
|
+
</WorkbenchPropertyRow>
|
|
191
|
+
<WorkbenchPropertyHint className="workbench-field-remap-detail__muted">
|
|
192
|
+
Wire source then target ports. When both bind, the convert note editor opens.
|
|
193
|
+
</WorkbenchPropertyHint>
|
|
194
|
+
</WorkbenchPropertyStack>
|
|
195
|
+
</WorkbenchPropertySection>
|
|
196
|
+
<WorkbenchPropertySection
|
|
197
|
+
level="group"
|
|
198
|
+
title="Ports"
|
|
199
|
+
data-testid="field-remap-detail-draft-ports"
|
|
200
|
+
>
|
|
201
|
+
<WorkbenchPropertyStack gap="xs">
|
|
202
|
+
<WorkbenchPropertyRow label="Source">
|
|
203
|
+
<code>{draft?.sourceFieldId ?? 'Unwired'}</code>
|
|
204
|
+
</WorkbenchPropertyRow>
|
|
205
|
+
<WorkbenchPropertyRow label="Target">
|
|
206
|
+
<code>{draft?.targetSlotId ?? 'Unwired'}</code>
|
|
207
|
+
</WorkbenchPropertyRow>
|
|
208
|
+
</WorkbenchPropertyStack>
|
|
209
|
+
</WorkbenchPropertySection>
|
|
210
|
+
</WorkbenchPropertyStack>
|
|
183
211
|
</aside>
|
|
184
212
|
);
|
|
185
213
|
}
|
|
@@ -188,11 +216,18 @@ export function FieldRemapDetailPanel({
|
|
|
188
216
|
if (!operator || (!readOnly && !onOperatorsChange)) {
|
|
189
217
|
return (
|
|
190
218
|
<aside className="workbench-field-remap-detail" data-testid="field-remap-detail">
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
219
|
+
<WorkbenchPropertyStack gap="sm">
|
|
220
|
+
<WorkbenchPropertySection title="Operator">
|
|
221
|
+
<WorkbenchPropertyStack gap="sm">
|
|
222
|
+
<WorkbenchPropertyRow label="Identifier">
|
|
223
|
+
<code data-testid="field-remap-detail-operator-id">{selection.operatorId}</code>
|
|
224
|
+
</WorkbenchPropertyRow>
|
|
225
|
+
<WorkbenchPropertyHint className="workbench-field-remap-detail__muted">
|
|
226
|
+
Host must wire onOperatorsChange for authoring.
|
|
227
|
+
</WorkbenchPropertyHint>
|
|
228
|
+
</WorkbenchPropertyStack>
|
|
229
|
+
</WorkbenchPropertySection>
|
|
230
|
+
</WorkbenchPropertyStack>
|
|
196
231
|
</aside>
|
|
197
232
|
);
|
|
198
233
|
}
|
|
@@ -202,204 +237,217 @@ export function FieldRemapDetailPanel({
|
|
|
202
237
|
data-testid="field-remap-detail"
|
|
203
238
|
aria-label="Operator details"
|
|
204
239
|
>
|
|
205
|
-
<
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{!readOnly ? (
|
|
211
|
-
<Button
|
|
212
|
-
compact
|
|
213
|
-
type="button"
|
|
214
|
-
data-testid="field-remap-detail-delete-operator"
|
|
215
|
-
onClick={() => {
|
|
216
|
-
commitOperators(removeMappingOperator(operators, operator.id));
|
|
217
|
-
onSelectionChange(null);
|
|
218
|
-
}}
|
|
219
|
-
>
|
|
220
|
-
Delete
|
|
221
|
-
</Button>
|
|
222
|
-
) : null}
|
|
223
|
-
</div>
|
|
224
|
-
{operator.kind === 'combine' ? (
|
|
225
|
-
<section className="workbench-field-remap-detail__section">
|
|
226
|
-
<h5>Inputs</h5>
|
|
227
|
-
<ul className="workbench-field-remap-detail__item-edges">
|
|
228
|
-
{operator.inputFieldIds.map((fieldId) => (
|
|
229
|
-
<li key={fieldId}>
|
|
230
|
-
<code>{fieldLabel(fieldId, sources, targets)}</code>
|
|
231
|
-
{!readOnly ? (
|
|
232
|
-
<Button
|
|
233
|
-
compact
|
|
234
|
-
type="button"
|
|
235
|
-
onClick={() =>
|
|
236
|
-
commitOperators(
|
|
237
|
-
updateMappingOperator(operators, operator.id, (current) =>
|
|
238
|
-
removeOperatorInput(current, fieldId),
|
|
239
|
-
),
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
>
|
|
243
|
-
Remove
|
|
244
|
-
</Button>
|
|
245
|
-
) : null}
|
|
246
|
-
</li>
|
|
247
|
-
))}
|
|
248
|
-
</ul>
|
|
249
|
-
{!readOnly ? (
|
|
250
|
-
<div className="workbench-field-remap-detail__field">
|
|
251
|
-
<label>
|
|
252
|
-
<span>Add input</span>
|
|
253
|
-
<select
|
|
254
|
-
data-testid="field-remap-operator-add-input"
|
|
255
|
-
value={pendingOperatorFieldId}
|
|
256
|
-
onChange={(event) => setPendingOperatorFieldId(event.target.value)}
|
|
257
|
-
>
|
|
258
|
-
<option value="">Select…</option>
|
|
259
|
-
{flatSources.map((field) => (
|
|
260
|
-
<option key={field.id} value={field.id}>
|
|
261
|
-
{field.path ?? field.label}
|
|
262
|
-
</option>
|
|
263
|
-
))}
|
|
264
|
-
</select>
|
|
265
|
-
</label>
|
|
240
|
+
<WorkbenchPropertyStack gap="sm">
|
|
241
|
+
<WorkbenchPropertySection
|
|
242
|
+
title={operator.kind === 'combine' ? 'Combine (n→1)' : 'Split (1→n)'}
|
|
243
|
+
actions={
|
|
244
|
+
!readOnly ? (
|
|
266
245
|
<Button
|
|
267
246
|
compact
|
|
268
247
|
type="button"
|
|
269
|
-
data-testid="field-remap-
|
|
270
|
-
disabled={!pendingOperatorFieldId}
|
|
248
|
+
data-testid="field-remap-detail-delete-operator"
|
|
271
249
|
onClick={() => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
updateMappingOperator(operators, operator.id, (current) =>
|
|
275
|
-
bindOperatorInput(current, pendingOperatorFieldId),
|
|
276
|
-
),
|
|
277
|
-
);
|
|
278
|
-
setPendingOperatorFieldId('');
|
|
250
|
+
commitOperators(removeMappingOperator(operators, operator.id));
|
|
251
|
+
onSelectionChange(null);
|
|
279
252
|
}}
|
|
280
253
|
>
|
|
281
|
-
|
|
254
|
+
Delete
|
|
282
255
|
</Button>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
<div className="workbench-field-remap-detail__field">
|
|
316
|
-
<label>
|
|
317
|
-
<span>Input field</span>
|
|
318
|
-
<select
|
|
319
|
-
data-testid="field-remap-operator-input"
|
|
320
|
-
value={operator.inputFieldId}
|
|
321
|
-
disabled={readOnly}
|
|
322
|
-
onChange={(event) => {
|
|
323
|
-
const fieldId = event.target.value;
|
|
324
|
-
commitOperators(
|
|
325
|
-
updateMappingOperator(operators, operator.id, (current) =>
|
|
326
|
-
fieldId
|
|
327
|
-
? bindOperatorInput(current, fieldId)
|
|
328
|
-
: removeOperatorInput(current, operator.inputFieldId),
|
|
329
|
-
),
|
|
330
|
-
);
|
|
331
|
-
}}
|
|
332
|
-
>
|
|
333
|
-
<option value="">Unwired…</option>
|
|
334
|
-
{flatSources.map((field) => (
|
|
335
|
-
<option key={field.id} value={field.id}>
|
|
336
|
-
{field.path ?? field.label}
|
|
337
|
-
</option>
|
|
256
|
+
) : null
|
|
257
|
+
}
|
|
258
|
+
>
|
|
259
|
+
<WorkbenchPropertyRow label="Identifier">
|
|
260
|
+
<code data-testid="field-remap-detail-operator-id">{operator.id}</code>
|
|
261
|
+
</WorkbenchPropertyRow>
|
|
262
|
+
</WorkbenchPropertySection>
|
|
263
|
+
{operator.kind === 'combine' ? (
|
|
264
|
+
<WorkbenchPropertySection level="group" title="Inputs">
|
|
265
|
+
<WorkbenchPropertyStack gap="sm">
|
|
266
|
+
<ul className="workbench-field-remap-detail__item-edges">
|
|
267
|
+
{operator.inputFieldIds.map((fieldId) => (
|
|
268
|
+
<li key={fieldId}>
|
|
269
|
+
<WorkbenchPropertyInline justify="between">
|
|
270
|
+
<code>{fieldLabel(fieldId, sources, targets)}</code>
|
|
271
|
+
{!readOnly ? (
|
|
272
|
+
<Button
|
|
273
|
+
compact
|
|
274
|
+
type="button"
|
|
275
|
+
onClick={() =>
|
|
276
|
+
commitOperators(
|
|
277
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
278
|
+
removeOperatorInput(current, fieldId),
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
>
|
|
283
|
+
Remove
|
|
284
|
+
</Button>
|
|
285
|
+
) : null}
|
|
286
|
+
</WorkbenchPropertyInline>
|
|
287
|
+
</li>
|
|
338
288
|
))}
|
|
339
|
-
</
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
289
|
+
</ul>
|
|
290
|
+
{!readOnly ? (
|
|
291
|
+
<WorkbenchPropertyRow label="Add input" htmlFor={operatorAddInputId}>
|
|
292
|
+
<WorkbenchPropertyInline>
|
|
293
|
+
<Select
|
|
294
|
+
id={operatorAddInputId}
|
|
295
|
+
aria-label="Add input"
|
|
296
|
+
controlWidth="full"
|
|
297
|
+
data-testid="field-remap-operator-add-input"
|
|
298
|
+
value={pendingOperatorFieldId}
|
|
299
|
+
onValueChange={setPendingOperatorFieldId}
|
|
300
|
+
>
|
|
301
|
+
<option value="">Select…</option>
|
|
302
|
+
{flatSources.map((field) => (
|
|
303
|
+
<option key={field.id} value={field.id}>
|
|
304
|
+
{field.path ?? field.label}
|
|
305
|
+
</option>
|
|
306
|
+
))}
|
|
307
|
+
</Select>
|
|
308
|
+
<Button
|
|
309
|
+
compact
|
|
310
|
+
type="button"
|
|
311
|
+
data-testid="field-remap-operator-bind-input"
|
|
312
|
+
disabled={!pendingOperatorFieldId}
|
|
313
|
+
onClick={() => {
|
|
314
|
+
if (!pendingOperatorFieldId) return;
|
|
315
|
+
commitOperators(
|
|
316
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
317
|
+
bindOperatorInput(current, pendingOperatorFieldId),
|
|
318
|
+
),
|
|
319
|
+
);
|
|
320
|
+
setPendingOperatorFieldId('');
|
|
321
|
+
}}
|
|
322
|
+
>
|
|
323
|
+
Add
|
|
324
|
+
</Button>
|
|
325
|
+
</WorkbenchPropertyInline>
|
|
326
|
+
</WorkbenchPropertyRow>
|
|
327
|
+
) : null}
|
|
328
|
+
<WorkbenchPropertyRow label="Output slot" htmlFor={operatorOutputId}>
|
|
329
|
+
<Select
|
|
330
|
+
id={operatorOutputId}
|
|
331
|
+
aria-label="Output slot"
|
|
332
|
+
controlWidth="full"
|
|
333
|
+
data-testid="field-remap-operator-output"
|
|
334
|
+
value={operator.outputSlotId}
|
|
335
|
+
disabled={readOnly}
|
|
336
|
+
onValueChange={(slotId) => {
|
|
337
|
+
commitOperators(
|
|
338
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
339
|
+
slotId
|
|
340
|
+
? bindOperatorOutput(current, slotId)
|
|
341
|
+
: removeOperatorOutput(current, operator.outputSlotId),
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
}}
|
|
373
345
|
>
|
|
374
|
-
<option value="">
|
|
346
|
+
<option value="">Unwired…</option>
|
|
375
347
|
{flatTargets.map((slot) => (
|
|
376
348
|
<option key={slot.id} value={slot.id}>
|
|
377
349
|
{slot.path ?? slot.label}
|
|
378
350
|
</option>
|
|
379
351
|
))}
|
|
380
|
-
</
|
|
381
|
-
</
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
352
|
+
</Select>
|
|
353
|
+
</WorkbenchPropertyRow>
|
|
354
|
+
</WorkbenchPropertyStack>
|
|
355
|
+
</WorkbenchPropertySection>
|
|
356
|
+
) : (
|
|
357
|
+
<WorkbenchPropertySection level="group" title="Outputs">
|
|
358
|
+
<WorkbenchPropertyStack gap="sm">
|
|
359
|
+
<WorkbenchPropertyRow label="Input field" htmlFor={operatorInputId}>
|
|
360
|
+
<Select
|
|
361
|
+
id={operatorInputId}
|
|
362
|
+
aria-label="Input field"
|
|
363
|
+
controlWidth="full"
|
|
364
|
+
data-testid="field-remap-operator-input"
|
|
365
|
+
value={operator.inputFieldId}
|
|
366
|
+
disabled={readOnly}
|
|
367
|
+
onValueChange={(fieldId) => {
|
|
368
|
+
commitOperators(
|
|
369
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
370
|
+
fieldId
|
|
371
|
+
? bindOperatorInput(current, fieldId)
|
|
372
|
+
: removeOperatorInput(current, operator.inputFieldId),
|
|
373
|
+
),
|
|
374
|
+
);
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
<option value="">Unwired…</option>
|
|
378
|
+
{flatSources.map((field) => (
|
|
379
|
+
<option key={field.id} value={field.id}>
|
|
380
|
+
{field.path ?? field.label}
|
|
381
|
+
</option>
|
|
382
|
+
))}
|
|
383
|
+
</Select>
|
|
384
|
+
</WorkbenchPropertyRow>
|
|
385
|
+
<ul className="workbench-field-remap-detail__item-edges">
|
|
386
|
+
{operator.outputSlotIds.map((slotId) => (
|
|
387
|
+
<li key={slotId}>
|
|
388
|
+
<WorkbenchPropertyInline justify="between">
|
|
389
|
+
<code>{fieldLabel(slotId, sources, targets)}</code>
|
|
390
|
+
{!readOnly ? (
|
|
391
|
+
<Button
|
|
392
|
+
compact
|
|
393
|
+
type="button"
|
|
394
|
+
onClick={() =>
|
|
395
|
+
commitOperators(
|
|
396
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
397
|
+
removeOperatorOutput(current, slotId),
|
|
398
|
+
),
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
>
|
|
402
|
+
Remove
|
|
403
|
+
</Button>
|
|
404
|
+
) : null}
|
|
405
|
+
</WorkbenchPropertyInline>
|
|
406
|
+
</li>
|
|
407
|
+
))}
|
|
408
|
+
</ul>
|
|
409
|
+
{!readOnly ? (
|
|
410
|
+
<WorkbenchPropertyRow label="Add output" htmlFor={operatorAddOutputId}>
|
|
411
|
+
<WorkbenchPropertyInline>
|
|
412
|
+
<Select
|
|
413
|
+
id={operatorAddOutputId}
|
|
414
|
+
aria-label="Add output"
|
|
415
|
+
controlWidth="full"
|
|
416
|
+
data-testid="field-remap-operator-add-output"
|
|
417
|
+
value={pendingOperatorSlotId}
|
|
418
|
+
onValueChange={setPendingOperatorSlotId}
|
|
419
|
+
>
|
|
420
|
+
<option value="">Select…</option>
|
|
421
|
+
{flatTargets.map((slot) => (
|
|
422
|
+
<option key={slot.id} value={slot.id}>
|
|
423
|
+
{slot.path ?? slot.label}
|
|
424
|
+
</option>
|
|
425
|
+
))}
|
|
426
|
+
</Select>
|
|
427
|
+
<Button
|
|
428
|
+
compact
|
|
429
|
+
type="button"
|
|
430
|
+
data-testid="field-remap-operator-bind-output"
|
|
431
|
+
disabled={!pendingOperatorSlotId}
|
|
432
|
+
onClick={() => {
|
|
433
|
+
if (!pendingOperatorSlotId) return;
|
|
434
|
+
commitOperators(
|
|
435
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
436
|
+
bindOperatorOutput(current, pendingOperatorSlotId),
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
setPendingOperatorSlotId('');
|
|
440
|
+
}}
|
|
441
|
+
>
|
|
442
|
+
Add
|
|
443
|
+
</Button>
|
|
444
|
+
</WorkbenchPropertyInline>
|
|
445
|
+
</WorkbenchPropertyRow>
|
|
446
|
+
) : null}
|
|
447
|
+
</WorkbenchPropertyStack>
|
|
448
|
+
</WorkbenchPropertySection>
|
|
449
|
+
)}
|
|
450
|
+
</WorkbenchPropertyStack>
|
|
403
451
|
</aside>
|
|
404
452
|
);
|
|
405
453
|
}
|
|
@@ -411,24 +459,32 @@ export function FieldRemapDetailPanel({
|
|
|
411
459
|
data-testid="field-remap-detail"
|
|
412
460
|
aria-label="Binding details"
|
|
413
461
|
>
|
|
414
|
-
<
|
|
462
|
+
<WorkbenchPropertyStack gap="sm">
|
|
463
|
+
<WorkbenchPropertySection title="Binding">
|
|
464
|
+
<WorkbenchPropertyHint className="workbench-field-remap-detail__muted">
|
|
465
|
+
Select a binding for mapping details, or a convert node to open the convert editor.
|
|
466
|
+
</WorkbenchPropertyHint>
|
|
467
|
+
</WorkbenchPropertySection>
|
|
468
|
+
</WorkbenchPropertyStack>
|
|
415
469
|
</aside>
|
|
416
470
|
);
|
|
417
471
|
}
|
|
418
472
|
|
|
419
473
|
if (selection.kind === 'transformStep') {
|
|
420
474
|
return (
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
475
|
+
<div data-testid="field-remap-detail-transform-step">
|
|
476
|
+
<ConvertNoteEditor
|
|
477
|
+
edge={edge}
|
|
478
|
+
stepIndex={selection.stepIndex}
|
|
479
|
+
sources={sources}
|
|
480
|
+
targets={targets}
|
|
481
|
+
transforms={transforms}
|
|
482
|
+
edges={edges}
|
|
483
|
+
onEdgesChange={onEdgesChange}
|
|
484
|
+
onSelectionChange={onSelectionChange}
|
|
485
|
+
readOnly={readOnly}
|
|
486
|
+
/>
|
|
487
|
+
</div>
|
|
432
488
|
);
|
|
433
489
|
}
|
|
434
490
|
|
|
@@ -467,227 +523,239 @@ export function FieldRemapDetailPanel({
|
|
|
467
523
|
data-testid="field-remap-detail"
|
|
468
524
|
aria-label="Binding details"
|
|
469
525
|
>
|
|
470
|
-
<
|
|
471
|
-
<
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
526
|
+
<WorkbenchPropertyStack gap="sm">
|
|
527
|
+
<WorkbenchPropertySection
|
|
528
|
+
title="Binding"
|
|
529
|
+
actions={
|
|
530
|
+
<Button
|
|
531
|
+
compact
|
|
532
|
+
type="button"
|
|
533
|
+
data-testid="field-remap-detail-clear"
|
|
534
|
+
onClick={() => onSelectionChange(null)}
|
|
535
|
+
>
|
|
536
|
+
Clear
|
|
537
|
+
</Button>
|
|
538
|
+
}
|
|
482
539
|
>
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
540
|
+
<WorkbenchPropertyRow label="Route">
|
|
541
|
+
<code data-testid="field-remap-detail-binding">
|
|
542
|
+
{edge.sourceFieldId} → {edge.targetSlotId}
|
|
543
|
+
</code>
|
|
544
|
+
</WorkbenchPropertyRow>
|
|
545
|
+
</WorkbenchPropertySection>
|
|
486
546
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
547
|
+
<WorkbenchPropertySection level="group" title="Convert chain">
|
|
548
|
+
<WorkbenchPropertyStack gap="sm">
|
|
549
|
+
{chain.length === 0 ? (
|
|
550
|
+
<WorkbenchPropertyHint className="workbench-field-remap-detail__muted">
|
|
551
|
+
Direct (no convert)
|
|
552
|
+
</WorkbenchPropertyHint>
|
|
553
|
+
) : (
|
|
554
|
+
<ol className="workbench-field-remap-detail__steps">
|
|
555
|
+
{chain.map((transformId, index) => {
|
|
556
|
+
const definition = transforms.get(transformId);
|
|
557
|
+
return (
|
|
558
|
+
<li key={`${edge.id}-${index}-${transformId}`}>
|
|
559
|
+
<WorkbenchPropertyInline justify="between">
|
|
560
|
+
<button
|
|
561
|
+
type="button"
|
|
562
|
+
className="workbench-field-remap-detail__step"
|
|
563
|
+
data-testid={`field-remap-detail-step-${index}`}
|
|
564
|
+
onClick={() =>
|
|
565
|
+
onSelectionChange({
|
|
566
|
+
kind: 'transformStep',
|
|
567
|
+
edgeId: edge.id,
|
|
568
|
+
stepIndex: index,
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
>
|
|
572
|
+
<strong>{definition?.label ?? transformId}</strong>
|
|
573
|
+
<code>{transformId}</code>
|
|
574
|
+
<WorkbenchPropertyHint className="workbench-field-remap-detail__step-hint">
|
|
575
|
+
{readOnly ? 'Inspect convert' : 'Edit convert'}
|
|
576
|
+
</WorkbenchPropertyHint>
|
|
577
|
+
</button>
|
|
578
|
+
{!readOnly ? (
|
|
579
|
+
<Button
|
|
580
|
+
compact
|
|
581
|
+
type="button"
|
|
582
|
+
aria-label={`Remove convert step ${index + 1}`}
|
|
583
|
+
data-testid={`field-remap-detail-remove-step-${index}`}
|
|
584
|
+
onClick={() => {
|
|
585
|
+
const next = removeTransformStepFromEdge(edge, index);
|
|
586
|
+
applyEdge(next);
|
|
587
|
+
}}
|
|
588
|
+
>
|
|
589
|
+
×
|
|
590
|
+
</Button>
|
|
591
|
+
) : null}
|
|
592
|
+
</WorkbenchPropertyInline>
|
|
593
|
+
</li>
|
|
594
|
+
);
|
|
595
|
+
})}
|
|
596
|
+
</ol>
|
|
597
|
+
)}
|
|
598
|
+
|
|
599
|
+
{!readOnly && chain.length < MAX_TRANSFORM_CHAIN ? (
|
|
600
|
+
<WorkbenchPropertyRow label="Add convert" htmlFor={paletteSelectId}>
|
|
601
|
+
<WorkbenchPropertyInline
|
|
602
|
+
className="workbench-field-remap-detail__palette"
|
|
603
|
+
data-testid="field-remap-transform-palette"
|
|
604
|
+
>
|
|
605
|
+
<Select
|
|
606
|
+
id={paletteSelectId}
|
|
607
|
+
aria-label="Convert palette"
|
|
608
|
+
controlWidth="full"
|
|
609
|
+
data-testid="field-remap-palette-select"
|
|
610
|
+
value={effectivePaletteId}
|
|
611
|
+
disabled={appendCatalog.length === 0}
|
|
612
|
+
onValueChange={setPaletteId}
|
|
613
|
+
>
|
|
614
|
+
{appendCatalog.length === 0 ? (
|
|
615
|
+
<option value="">No compatible converts</option>
|
|
616
|
+
) : null}
|
|
617
|
+
{appendCatalog.map((item) => (
|
|
618
|
+
<option key={item.id} value={item.id}>
|
|
619
|
+
{item.label}
|
|
620
|
+
</option>
|
|
621
|
+
))}
|
|
622
|
+
</Select>
|
|
623
|
+
<Button
|
|
624
|
+
compact
|
|
501
625
|
type="button"
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
onClick={() =>
|
|
626
|
+
data-testid="field-remap-palette-add"
|
|
627
|
+
disabled={!effectivePaletteId}
|
|
628
|
+
onClick={() => {
|
|
629
|
+
const next = addTransformStepToEdge(edge, effectivePaletteId, {
|
|
630
|
+
registry: transforms,
|
|
631
|
+
sourceType: portTypes.sourceType,
|
|
632
|
+
targetType: portTypes.targetType,
|
|
633
|
+
});
|
|
634
|
+
if (!next) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
applyEdge(next);
|
|
505
638
|
onSelectionChange({
|
|
506
639
|
kind: 'transformStep',
|
|
507
640
|
edgeId: edge.id,
|
|
508
|
-
stepIndex:
|
|
509
|
-
})
|
|
510
|
-
}
|
|
641
|
+
stepIndex: (next.transformIds?.length ?? 1) - 1,
|
|
642
|
+
});
|
|
643
|
+
}}
|
|
511
644
|
>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
<Button
|
|
520
|
-
compact
|
|
521
|
-
type="button"
|
|
522
|
-
aria-label={`Remove convert step ${index + 1}`}
|
|
523
|
-
data-testid={`field-remap-detail-remove-step-${index}`}
|
|
524
|
-
onClick={() => {
|
|
525
|
-
const next = removeTransformStepFromEdge(edge, index);
|
|
526
|
-
applyEdge(next);
|
|
527
|
-
}}
|
|
528
|
-
>
|
|
529
|
-
×
|
|
530
|
-
</Button>
|
|
531
|
-
) : null}
|
|
532
|
-
</li>
|
|
533
|
-
);
|
|
534
|
-
})}
|
|
535
|
-
</ol>
|
|
536
|
-
)}
|
|
645
|
+
Add
|
|
646
|
+
</Button>
|
|
647
|
+
</WorkbenchPropertyInline>
|
|
648
|
+
</WorkbenchPropertyRow>
|
|
649
|
+
) : null}
|
|
650
|
+
</WorkbenchPropertyStack>
|
|
651
|
+
</WorkbenchPropertySection>
|
|
537
652
|
|
|
538
|
-
{
|
|
539
|
-
<
|
|
540
|
-
|
|
541
|
-
|
|
653
|
+
{listContextEnabled ? (
|
|
654
|
+
<WorkbenchPropertySection
|
|
655
|
+
level="group"
|
|
656
|
+
title="List context"
|
|
657
|
+
data-testid="field-remap-list-context"
|
|
658
|
+
actions={
|
|
659
|
+
!readOnly && !edge.itemEdges ? (
|
|
660
|
+
<Button
|
|
661
|
+
compact
|
|
662
|
+
type="button"
|
|
663
|
+
data-testid="field-remap-enable-list-context"
|
|
664
|
+
onClick={() => applyEdge(enableListContextOnEdge(edge))}
|
|
665
|
+
>
|
|
666
|
+
Enable item fields
|
|
667
|
+
</Button>
|
|
668
|
+
) : null
|
|
669
|
+
}
|
|
542
670
|
>
|
|
543
|
-
|
|
544
|
-
<
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
>
|
|
552
|
-
{appendCatalog.length === 0 ? (
|
|
553
|
-
<option value="">No compatible converts</option>
|
|
671
|
+
{edge.itemEdges ? (
|
|
672
|
+
<WorkbenchPropertyStack gap="sm">
|
|
673
|
+
{!readOnly ? (
|
|
674
|
+
<WorkbenchPropertyHint className="workbench-field-remap-detail__muted">
|
|
675
|
+
{pendingItemSourceId
|
|
676
|
+
? 'Select a target item field to complete the item binding.'
|
|
677
|
+
: 'Select a source item field, then a target item field.'}
|
|
678
|
+
</WorkbenchPropertyHint>
|
|
554
679
|
) : null}
|
|
555
|
-
{
|
|
556
|
-
<
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const next = addTransformStepToEdge(edge, effectivePaletteId, {
|
|
569
|
-
registry: transforms,
|
|
570
|
-
sourceType: portTypes.sourceType,
|
|
571
|
-
targetType: portTypes.targetType,
|
|
572
|
-
});
|
|
573
|
-
if (!next) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
applyEdge(next);
|
|
577
|
-
onSelectionChange({
|
|
578
|
-
kind: 'transformStep',
|
|
579
|
-
edgeId: edge.id,
|
|
580
|
-
stepIndex: (next.transformIds?.length ?? 1) - 1,
|
|
581
|
-
});
|
|
582
|
-
}}
|
|
583
|
-
>
|
|
584
|
-
Add
|
|
585
|
-
</Button>
|
|
586
|
-
</div>
|
|
587
|
-
) : null}
|
|
588
|
-
</section>
|
|
589
|
-
|
|
590
|
-
{listContextEnabled ? (
|
|
591
|
-
<section
|
|
592
|
-
className="workbench-field-remap-detail__section"
|
|
593
|
-
aria-labelledby="field-remap-detail-list-context"
|
|
594
|
-
data-testid="field-remap-list-context"
|
|
595
|
-
>
|
|
596
|
-
<div className="workbench-field-remap-detail__list-context-bar">
|
|
597
|
-
<h5 id="field-remap-detail-list-context">List context</h5>
|
|
598
|
-
{!readOnly && !edge.itemEdges ? (
|
|
599
|
-
<Button
|
|
600
|
-
compact
|
|
601
|
-
type="button"
|
|
602
|
-
data-testid="field-remap-enable-list-context"
|
|
603
|
-
onClick={() => applyEdge(enableListContextOnEdge(edge))}
|
|
604
|
-
>
|
|
605
|
-
Enable item fields
|
|
606
|
-
</Button>
|
|
607
|
-
) : null}
|
|
608
|
-
</div>
|
|
609
|
-
|
|
610
|
-
{edge.itemEdges ? (
|
|
611
|
-
<>
|
|
612
|
-
{!readOnly ? (
|
|
613
|
-
<p className="workbench-field-remap-detail__muted">
|
|
614
|
-
{pendingItemSourceId
|
|
615
|
-
? 'Select a target item field to complete the item binding.'
|
|
616
|
-
: 'Select a source item field, then a target item field.'}
|
|
617
|
-
</p>
|
|
618
|
-
) : null}
|
|
619
|
-
{!readOnly ? (
|
|
620
|
-
<div className="workbench-field-remap-detail__item-pickers">
|
|
621
|
-
<label>
|
|
622
|
-
<span>Source item field</span>
|
|
623
|
-
<select
|
|
624
|
-
aria-label="Source item field"
|
|
625
|
-
data-testid="field-remap-item-source"
|
|
626
|
-
value={pendingItemSourceId ?? ''}
|
|
627
|
-
onChange={(event) => setPendingItemSourceId(event.target.value || null)}
|
|
628
|
-
>
|
|
629
|
-
<option value="">Select…</option>
|
|
630
|
-
{itemSourceChildren.map((child) => (
|
|
631
|
-
<option key={child.id} value={child.id}>
|
|
632
|
-
{child.path ?? child.label}
|
|
633
|
-
</option>
|
|
634
|
-
))}
|
|
635
|
-
</select>
|
|
636
|
-
</label>
|
|
637
|
-
<label>
|
|
638
|
-
<span>Target item field</span>
|
|
639
|
-
<select
|
|
640
|
-
aria-label="Target item field"
|
|
641
|
-
data-testid="field-remap-item-target"
|
|
642
|
-
value=""
|
|
643
|
-
disabled={!pendingItemSourceId}
|
|
644
|
-
onChange={(event) => {
|
|
645
|
-
const targetId = event.target.value;
|
|
646
|
-
if (!pendingItemSourceId || !targetId) {
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
const itemEdge: MappingEdge = {
|
|
650
|
-
id: `ie-${pendingItemSourceId}-${targetId}-${Date.now()}`,
|
|
651
|
-
sourceFieldId: pendingItemSourceId,
|
|
652
|
-
targetSlotId: targetId,
|
|
653
|
-
};
|
|
654
|
-
applyEdge(upsertItemEdgeOnParent(edge, itemEdge));
|
|
655
|
-
setPendingItemSourceId(null);
|
|
656
|
-
}}
|
|
657
|
-
>
|
|
658
|
-
<option value="">Select…</option>
|
|
659
|
-
{itemTargetChildren.map((child) => (
|
|
660
|
-
<option key={child.id} value={child.id}>
|
|
661
|
-
{child.path ?? child.label}
|
|
662
|
-
</option>
|
|
663
|
-
))}
|
|
664
|
-
</select>
|
|
665
|
-
</label>
|
|
666
|
-
</div>
|
|
667
|
-
) : null}
|
|
668
|
-
<ul className="workbench-field-remap-detail__item-edges">
|
|
669
|
-
{(edge.itemEdges ?? []).map((itemEdge) => (
|
|
670
|
-
<li key={itemEdge.id} data-testid={`field-remap-item-edge-${itemEdge.id}`}>
|
|
671
|
-
<code>
|
|
672
|
-
{fieldLabel(itemEdge.sourceFieldId, sources, targets)} →{' '}
|
|
673
|
-
{fieldLabel(itemEdge.targetSlotId, sources, targets)}
|
|
674
|
-
</code>
|
|
675
|
-
{!readOnly ? (
|
|
676
|
-
<Button
|
|
677
|
-
compact
|
|
678
|
-
type="button"
|
|
679
|
-
onClick={() => applyEdge(removeItemEdgeFromParent(edge, itemEdge.id))}
|
|
680
|
+
{!readOnly ? (
|
|
681
|
+
<WorkbenchPropertyStack
|
|
682
|
+
className="workbench-field-remap-detail__item-pickers"
|
|
683
|
+
gap="xs"
|
|
684
|
+
>
|
|
685
|
+
<WorkbenchPropertyRow label="Source item field" htmlFor={itemSourceId}>
|
|
686
|
+
<Select
|
|
687
|
+
id={itemSourceId}
|
|
688
|
+
aria-label="Source item field"
|
|
689
|
+
controlWidth="full"
|
|
690
|
+
data-testid="field-remap-item-source"
|
|
691
|
+
value={pendingItemSourceId ?? ''}
|
|
692
|
+
onValueChange={(sourceId) => setPendingItemSourceId(sourceId || null)}
|
|
680
693
|
>
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
694
|
+
<option value="">Select…</option>
|
|
695
|
+
{itemSourceChildren.map((child) => (
|
|
696
|
+
<option key={child.id} value={child.id}>
|
|
697
|
+
{child.path ?? child.label}
|
|
698
|
+
</option>
|
|
699
|
+
))}
|
|
700
|
+
</Select>
|
|
701
|
+
</WorkbenchPropertyRow>
|
|
702
|
+
<WorkbenchPropertyRow label="Target item field" htmlFor={itemTargetId}>
|
|
703
|
+
<Select
|
|
704
|
+
id={itemTargetId}
|
|
705
|
+
aria-label="Target item field"
|
|
706
|
+
controlWidth="full"
|
|
707
|
+
data-testid="field-remap-item-target"
|
|
708
|
+
value=""
|
|
709
|
+
disabled={!pendingItemSourceId}
|
|
710
|
+
onValueChange={(targetId) => {
|
|
711
|
+
if (!pendingItemSourceId || !targetId) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const itemEdge: MappingEdge = {
|
|
715
|
+
id: `ie-${pendingItemSourceId}-${targetId}-${Date.now()}`,
|
|
716
|
+
sourceFieldId: pendingItemSourceId,
|
|
717
|
+
targetSlotId: targetId,
|
|
718
|
+
};
|
|
719
|
+
applyEdge(upsertItemEdgeOnParent(edge, itemEdge));
|
|
720
|
+
setPendingItemSourceId(null);
|
|
721
|
+
}}
|
|
722
|
+
>
|
|
723
|
+
<option value="">Select…</option>
|
|
724
|
+
{itemTargetChildren.map((child) => (
|
|
725
|
+
<option key={child.id} value={child.id}>
|
|
726
|
+
{child.path ?? child.label}
|
|
727
|
+
</option>
|
|
728
|
+
))}
|
|
729
|
+
</Select>
|
|
730
|
+
</WorkbenchPropertyRow>
|
|
731
|
+
</WorkbenchPropertyStack>
|
|
732
|
+
) : null}
|
|
733
|
+
<ul className="workbench-field-remap-detail__item-edges">
|
|
734
|
+
{(edge.itemEdges ?? []).map((itemEdge) => (
|
|
735
|
+
<li key={itemEdge.id} data-testid={`field-remap-item-edge-${itemEdge.id}`}>
|
|
736
|
+
<WorkbenchPropertyInline justify="between">
|
|
737
|
+
<code>
|
|
738
|
+
{fieldLabel(itemEdge.sourceFieldId, sources, targets)} →{' '}
|
|
739
|
+
{fieldLabel(itemEdge.targetSlotId, sources, targets)}
|
|
740
|
+
</code>
|
|
741
|
+
{!readOnly ? (
|
|
742
|
+
<Button
|
|
743
|
+
compact
|
|
744
|
+
type="button"
|
|
745
|
+
onClick={() => applyEdge(removeItemEdgeFromParent(edge, itemEdge.id))}
|
|
746
|
+
>
|
|
747
|
+
Remove
|
|
748
|
+
</Button>
|
|
749
|
+
) : null}
|
|
750
|
+
</WorkbenchPropertyInline>
|
|
751
|
+
</li>
|
|
752
|
+
))}
|
|
753
|
+
</ul>
|
|
754
|
+
</WorkbenchPropertyStack>
|
|
755
|
+
) : null}
|
|
756
|
+
</WorkbenchPropertySection>
|
|
757
|
+
) : null}
|
|
758
|
+
</WorkbenchPropertyStack>
|
|
691
759
|
</aside>
|
|
692
760
|
);
|
|
693
761
|
}
|