@workbench-kit/shell-react 0.0.2-prototype.0.2.40 → 0.0.2-prototype.0.2.43
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 +42 -2
- package/package.json +13 -10
- package/src/extensions/extension-enablement-controller.ts +40 -9
- package/src/extensions/theme-selection-protection.ts +80 -55
- package/src/field-remap/chrome-labels.ts +18 -0
- package/src/field-remap/convert-note-editor.tsx +30 -24
- package/src/field-remap/convert-palette.tsx +6 -0
- package/src/field-remap/demo.tsx +8 -2
- package/src/field-remap/detail-panel.tsx +225 -192
- package/src/field-remap/drag-payload.ts +48 -0
- package/src/field-remap/flow-adapter.ts +216 -88
- package/src/field-remap/flow-ops.ts +150 -0
- package/src/field-remap/flow.tsx +1055 -162
- package/src/field-remap/index.ts +2 -0
- package/src/field-remap/io-class-browse.tsx +34 -22
- package/src/field-remap/keyboard.ts +16 -0
- package/src/field-remap/modal-detail.tsx +34 -0
- package/src/field-remap/panel.tsx +50 -14
- package/src/field-remap/transform-options-editor.tsx +5 -1
- package/src/field-remap/view.css +61 -42
- package/src/index.ts +7 -0
- package/src/management/keybinding-overrides-storage.ts +48 -23
- package/src/management/keybinding-settings.tsx +10 -1
- package/src/management/use-keybinding-management.ts +69 -19
- package/src/shell/appearance-catalog.ts +453 -0
- package/src/shell/appearance-controller.ts +331 -0
- package/src/shell/appearance-presentation.ts +269 -0
- package/src/shell/provider.tsx +147 -18
- package/src/shell/settings.tsx +282 -153
- package/src/shell/shell.tsx +88 -18
- package/src/workbench/appearance-storage.ts +2 -2
- package/src/workbench/command-host-controller.tsx +223 -0
- package/src/workbench/command-host.tsx +100 -150
- package/src/workbench/keybinding-bridge.ts +84 -38
- package/src/workbench/shell-command-registration.ts +27 -2
|
@@ -45,6 +45,7 @@ export interface FieldRemapDetailPanelProps {
|
|
|
45
45
|
readonly onDiscardDraft?: ((localId: string) => void) | undefined;
|
|
46
46
|
readonly operators?: readonly MappingOperator[] | undefined;
|
|
47
47
|
readonly onOperatorsChange?: ((operators: readonly MappingOperator[]) => void) | undefined;
|
|
48
|
+
readonly readOnly?: boolean | undefined;
|
|
48
49
|
readonly emptyDetailTitle?: string | undefined;
|
|
49
50
|
readonly emptyDetailDescription?: string | undefined;
|
|
50
51
|
}
|
|
@@ -79,6 +80,7 @@ export function FieldRemapDetailPanel({
|
|
|
79
80
|
onDiscardDraft,
|
|
80
81
|
operators = [],
|
|
81
82
|
onOperatorsChange,
|
|
83
|
+
readOnly = false,
|
|
82
84
|
emptyDetailTitle = 'Start with a convert',
|
|
83
85
|
emptyDetailDescription = 'Use the Convert palette to place a convert, then wire source → draft → target. Or select an existing binding / convert note on the canvas.',
|
|
84
86
|
}: FieldRemapDetailPanelProps): JSX.Element | null {
|
|
@@ -109,6 +111,12 @@ export function FieldRemapDetailPanel({
|
|
|
109
111
|
const [pendingOperatorSlotId, setPendingOperatorSlotId] = useState('');
|
|
110
112
|
const flatSources = useMemo(() => flattenSourceFields(sources), [sources]);
|
|
111
113
|
const flatTargets = useMemo(() => flattenTargetSlots(targets), [targets]);
|
|
114
|
+
const commitOperators = (next: readonly MappingOperator[]) => {
|
|
115
|
+
if (readOnly) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
onOperatorsChange?.(next);
|
|
119
|
+
};
|
|
112
120
|
|
|
113
121
|
if (!selection) {
|
|
114
122
|
return (
|
|
@@ -138,17 +146,19 @@ export function FieldRemapDetailPanel({
|
|
|
138
146
|
{definition?.label ?? draft?.transformId ?? selection.localId}
|
|
139
147
|
</code>
|
|
140
148
|
</div>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
{!readOnly ? (
|
|
150
|
+
<Button
|
|
151
|
+
compact
|
|
152
|
+
type="button"
|
|
153
|
+
data-testid="field-remap-detail-discard-draft"
|
|
154
|
+
onClick={() => {
|
|
155
|
+
onDiscardDraft?.(selection.localId);
|
|
156
|
+
onSelectionChange(null);
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
Discard
|
|
160
|
+
</Button>
|
|
161
|
+
) : null}
|
|
152
162
|
</div>
|
|
153
163
|
<p className="workbench-field-remap-detail__muted">
|
|
154
164
|
Wire source then target ports. When both bind, the convert note editor opens.
|
|
@@ -175,7 +185,7 @@ export function FieldRemapDetailPanel({
|
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
if (selection.kind === 'operator') {
|
|
178
|
-
if (!operator || !onOperatorsChange) {
|
|
188
|
+
if (!operator || (!readOnly && !onOperatorsChange)) {
|
|
179
189
|
return (
|
|
180
190
|
<aside className="workbench-field-remap-detail" data-testid="field-remap-detail">
|
|
181
191
|
<h4>Operator</h4>
|
|
@@ -197,17 +207,19 @@ export function FieldRemapDetailPanel({
|
|
|
197
207
|
<h4>{operator.kind === 'combine' ? 'Combine (n→1)' : 'Split (1→n)'}</h4>
|
|
198
208
|
<code data-testid="field-remap-detail-operator-id">{operator.id}</code>
|
|
199
209
|
</div>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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}
|
|
211
223
|
</div>
|
|
212
224
|
{operator.kind === 'combine' ? (
|
|
213
225
|
<section className="workbench-field-remap-detail__section">
|
|
@@ -216,65 +228,70 @@ export function FieldRemapDetailPanel({
|
|
|
216
228
|
{operator.inputFieldIds.map((fieldId) => (
|
|
217
229
|
<li key={fieldId}>
|
|
218
230
|
<code>{fieldLabel(fieldId, sources, targets)}</code>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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}
|
|
232
246
|
</li>
|
|
233
247
|
))}
|
|
234
248
|
</ul>
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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>
|
|
266
|
+
<Button
|
|
267
|
+
compact
|
|
268
|
+
type="button"
|
|
269
|
+
data-testid="field-remap-operator-bind-input"
|
|
270
|
+
disabled={!pendingOperatorFieldId}
|
|
271
|
+
onClick={() => {
|
|
272
|
+
if (!pendingOperatorFieldId) return;
|
|
273
|
+
commitOperators(
|
|
274
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
275
|
+
bindOperatorInput(current, pendingOperatorFieldId),
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
setPendingOperatorFieldId('');
|
|
279
|
+
}}
|
|
242
280
|
>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
</option>
|
|
248
|
-
))}
|
|
249
|
-
</select>
|
|
250
|
-
</label>
|
|
251
|
-
<Button
|
|
252
|
-
compact
|
|
253
|
-
type="button"
|
|
254
|
-
data-testid="field-remap-operator-bind-input"
|
|
255
|
-
disabled={!pendingOperatorFieldId}
|
|
256
|
-
onClick={() => {
|
|
257
|
-
if (!pendingOperatorFieldId) return;
|
|
258
|
-
onOperatorsChange(
|
|
259
|
-
updateMappingOperator(operators, operator.id, (current) =>
|
|
260
|
-
bindOperatorInput(current, pendingOperatorFieldId),
|
|
261
|
-
),
|
|
262
|
-
);
|
|
263
|
-
setPendingOperatorFieldId('');
|
|
264
|
-
}}
|
|
265
|
-
>
|
|
266
|
-
Add
|
|
267
|
-
</Button>
|
|
268
|
-
</div>
|
|
281
|
+
Add
|
|
282
|
+
</Button>
|
|
283
|
+
</div>
|
|
284
|
+
) : null}
|
|
269
285
|
<div className="workbench-field-remap-detail__field">
|
|
270
286
|
<label>
|
|
271
287
|
<span>Output slot</span>
|
|
272
288
|
<select
|
|
273
289
|
data-testid="field-remap-operator-output"
|
|
274
290
|
value={operator.outputSlotId}
|
|
291
|
+
disabled={readOnly}
|
|
275
292
|
onChange={(event) => {
|
|
276
293
|
const slotId = event.target.value;
|
|
277
|
-
|
|
294
|
+
commitOperators(
|
|
278
295
|
updateMappingOperator(operators, operator.id, (current) =>
|
|
279
296
|
slotId
|
|
280
297
|
? bindOperatorOutput(current, slotId)
|
|
@@ -301,9 +318,10 @@ export function FieldRemapDetailPanel({
|
|
|
301
318
|
<select
|
|
302
319
|
data-testid="field-remap-operator-input"
|
|
303
320
|
value={operator.inputFieldId}
|
|
321
|
+
disabled={readOnly}
|
|
304
322
|
onChange={(event) => {
|
|
305
323
|
const fieldId = event.target.value;
|
|
306
|
-
|
|
324
|
+
commitOperators(
|
|
307
325
|
updateMappingOperator(operators, operator.id, (current) =>
|
|
308
326
|
fieldId
|
|
309
327
|
? bindOperatorInput(current, fieldId)
|
|
@@ -326,56 +344,60 @@ export function FieldRemapDetailPanel({
|
|
|
326
344
|
{operator.outputSlotIds.map((slotId) => (
|
|
327
345
|
<li key={slotId}>
|
|
328
346
|
<code>{fieldLabel(slotId, sources, targets)}</code>
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
347
|
+
{!readOnly ? (
|
|
348
|
+
<Button
|
|
349
|
+
compact
|
|
350
|
+
type="button"
|
|
351
|
+
onClick={() =>
|
|
352
|
+
commitOperators(
|
|
353
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
354
|
+
removeOperatorOutput(current, slotId),
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
>
|
|
359
|
+
Remove
|
|
360
|
+
</Button>
|
|
361
|
+
) : null}
|
|
342
362
|
</li>
|
|
343
363
|
))}
|
|
344
364
|
</ul>
|
|
345
|
-
|
|
346
|
-
<
|
|
347
|
-
<
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
365
|
+
{!readOnly ? (
|
|
366
|
+
<div className="workbench-field-remap-detail__field">
|
|
367
|
+
<label>
|
|
368
|
+
<span>Add output</span>
|
|
369
|
+
<select
|
|
370
|
+
data-testid="field-remap-operator-add-output"
|
|
371
|
+
value={pendingOperatorSlotId}
|
|
372
|
+
onChange={(event) => setPendingOperatorSlotId(event.target.value)}
|
|
373
|
+
>
|
|
374
|
+
<option value="">Select…</option>
|
|
375
|
+
{flatTargets.map((slot) => (
|
|
376
|
+
<option key={slot.id} value={slot.id}>
|
|
377
|
+
{slot.path ?? slot.label}
|
|
378
|
+
</option>
|
|
379
|
+
))}
|
|
380
|
+
</select>
|
|
381
|
+
</label>
|
|
382
|
+
<Button
|
|
383
|
+
compact
|
|
384
|
+
type="button"
|
|
385
|
+
data-testid="field-remap-operator-bind-output"
|
|
386
|
+
disabled={!pendingOperatorSlotId}
|
|
387
|
+
onClick={() => {
|
|
388
|
+
if (!pendingOperatorSlotId) return;
|
|
389
|
+
commitOperators(
|
|
390
|
+
updateMappingOperator(operators, operator.id, (current) =>
|
|
391
|
+
bindOperatorOutput(current, pendingOperatorSlotId),
|
|
392
|
+
),
|
|
393
|
+
);
|
|
394
|
+
setPendingOperatorSlotId('');
|
|
395
|
+
}}
|
|
352
396
|
>
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
</option>
|
|
358
|
-
))}
|
|
359
|
-
</select>
|
|
360
|
-
</label>
|
|
361
|
-
<Button
|
|
362
|
-
compact
|
|
363
|
-
type="button"
|
|
364
|
-
data-testid="field-remap-operator-bind-output"
|
|
365
|
-
disabled={!pendingOperatorSlotId}
|
|
366
|
-
onClick={() => {
|
|
367
|
-
if (!pendingOperatorSlotId) return;
|
|
368
|
-
onOperatorsChange(
|
|
369
|
-
updateMappingOperator(operators, operator.id, (current) =>
|
|
370
|
-
bindOperatorOutput(current, pendingOperatorSlotId),
|
|
371
|
-
),
|
|
372
|
-
);
|
|
373
|
-
setPendingOperatorSlotId('');
|
|
374
|
-
}}
|
|
375
|
-
>
|
|
376
|
-
Add
|
|
377
|
-
</Button>
|
|
378
|
-
</div>
|
|
397
|
+
Add
|
|
398
|
+
</Button>
|
|
399
|
+
</div>
|
|
400
|
+
) : null}
|
|
379
401
|
</section>
|
|
380
402
|
)}
|
|
381
403
|
</aside>
|
|
@@ -405,6 +427,7 @@ export function FieldRemapDetailPanel({
|
|
|
405
427
|
edges={edges}
|
|
406
428
|
onEdgesChange={onEdgesChange}
|
|
407
429
|
onSelectionChange={onSelectionChange}
|
|
430
|
+
readOnly={readOnly}
|
|
408
431
|
/>
|
|
409
432
|
);
|
|
410
433
|
}
|
|
@@ -432,7 +455,7 @@ export function FieldRemapDetailPanel({
|
|
|
432
455
|
const itemTargetChildren = targetSlot?.children ?? [];
|
|
433
456
|
|
|
434
457
|
const applyEdge = (next: MappingEdge | null) => {
|
|
435
|
-
if (!next) {
|
|
458
|
+
if (readOnly || !next) {
|
|
436
459
|
return;
|
|
437
460
|
}
|
|
438
461
|
onEdgesChange(edges.map((item) => (item.id === edge.id ? next : item)));
|
|
@@ -488,27 +511,31 @@ export function FieldRemapDetailPanel({
|
|
|
488
511
|
>
|
|
489
512
|
<strong>{definition?.label ?? transformId}</strong>
|
|
490
513
|
<code>{transformId}</code>
|
|
491
|
-
<span className="workbench-field-remap-detail__step-hint">
|
|
514
|
+
<span className="workbench-field-remap-detail__step-hint">
|
|
515
|
+
{readOnly ? 'Inspect convert' : 'Edit convert'}
|
|
516
|
+
</span>
|
|
492
517
|
</button>
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
518
|
+
{!readOnly ? (
|
|
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}
|
|
505
532
|
</li>
|
|
506
533
|
);
|
|
507
534
|
})}
|
|
508
535
|
</ol>
|
|
509
536
|
)}
|
|
510
537
|
|
|
511
|
-
{chain.length < MAX_TRANSFORM_CHAIN ? (
|
|
538
|
+
{!readOnly && chain.length < MAX_TRANSFORM_CHAIN ? (
|
|
512
539
|
<div
|
|
513
540
|
className="workbench-field-remap-detail__palette"
|
|
514
541
|
data-testid="field-remap-transform-palette"
|
|
@@ -568,7 +595,7 @@ export function FieldRemapDetailPanel({
|
|
|
568
595
|
>
|
|
569
596
|
<div className="workbench-field-remap-detail__list-context-bar">
|
|
570
597
|
<h5 id="field-remap-detail-list-context">List context</h5>
|
|
571
|
-
{!edge.itemEdges ? (
|
|
598
|
+
{!readOnly && !edge.itemEdges ? (
|
|
572
599
|
<Button
|
|
573
600
|
compact
|
|
574
601
|
type="button"
|
|
@@ -582,58 +609,62 @@ export function FieldRemapDetailPanel({
|
|
|
582
609
|
|
|
583
610
|
{edge.itemEdges ? (
|
|
584
611
|
<>
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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}
|
|
637
668
|
<ul className="workbench-field-remap-detail__item-edges">
|
|
638
669
|
{(edge.itemEdges ?? []).map((itemEdge) => (
|
|
639
670
|
<li key={itemEdge.id} data-testid={`field-remap-item-edge-${itemEdge.id}`}>
|
|
@@ -641,13 +672,15 @@ export function FieldRemapDetailPanel({
|
|
|
641
672
|
{fieldLabel(itemEdge.sourceFieldId, sources, targets)} →{' '}
|
|
642
673
|
{fieldLabel(itemEdge.targetSlotId, sources, targets)}
|
|
643
674
|
</code>
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
675
|
+
{!readOnly ? (
|
|
676
|
+
<Button
|
|
677
|
+
compact
|
|
678
|
+
type="button"
|
|
679
|
+
onClick={() => applyEdge(removeItemEdgeFromParent(edge, itemEdge.id))}
|
|
680
|
+
>
|
|
681
|
+
Remove
|
|
682
|
+
</Button>
|
|
683
|
+
) : null}
|
|
651
684
|
</li>
|
|
652
685
|
))}
|
|
653
686
|
</ul>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const FIELD_REMAP_TRANSFORM_DRAG_TYPE = 'application/x-workbench-field-remap-transform' as const;
|
|
2
|
+
|
|
3
|
+
type FieldRemapDragDataReader = Pick<DataTransfer, 'getData' | 'types'>;
|
|
4
|
+
type FieldRemapDragDataWriter = Pick<DataTransfer, 'setData'>;
|
|
5
|
+
|
|
6
|
+
export function hasFieldRemapTransformDragType(dataTransfer: Pick<DataTransfer, 'types'>): boolean {
|
|
7
|
+
return Array.from(dataTransfer.types).includes(FIELD_REMAP_TRANSFORM_DRAG_TYPE);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isCanonicalTransformPayload(value: unknown): value is { readonly transformId: string } {
|
|
11
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const keys = Object.keys(value);
|
|
15
|
+
return (
|
|
16
|
+
keys.length === 1 &&
|
|
17
|
+
keys[0] === 'transformId' &&
|
|
18
|
+
typeof (value as { readonly transformId?: unknown }).transformId === 'string' &&
|
|
19
|
+
(value as { readonly transformId: string }).transformId.length > 0
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Internal same-component payload; intentionally not exported from the package surface. */
|
|
24
|
+
export function writeFieldRemapTransformDragData(
|
|
25
|
+
dataTransfer: FieldRemapDragDataWriter,
|
|
26
|
+
transformId: string,
|
|
27
|
+
): void {
|
|
28
|
+
dataTransfer.setData(FIELD_REMAP_TRANSFORM_DRAG_TYPE, JSON.stringify({ transformId }));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Parse only the exact private payload shape before callers resolve the registry id. */
|
|
32
|
+
export function readFieldRemapTransformDragData(
|
|
33
|
+
dataTransfer: FieldRemapDragDataReader,
|
|
34
|
+
): string | undefined {
|
|
35
|
+
if (!hasFieldRemapTransformDragType(dataTransfer)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const raw = dataTransfer.getData(FIELD_REMAP_TRANSFORM_DRAG_TYPE);
|
|
39
|
+
if (!raw) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed: unknown = JSON.parse(raw);
|
|
44
|
+
return isCanonicalTransformPayload(parsed) ? parsed.transformId : undefined;
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|