@workbench-kit/shell-react 0.0.2-prototype.0.2.33 → 0.0.2-prototype.0.2.34
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 +32 -0
- package/package.json +9 -9
- package/src/commands/use-command-descriptors.ts +7 -3
- package/src/commands/use-extension-registry-command-descriptors.ts +23 -6
- package/src/devtools/use-workbench-devtools-snapshot.ts +39 -14
- package/src/devtools/workbench-devtools-snapshot.ts +32 -13
- package/src/editor/area.tsx +3 -2
- package/src/editor/state-storage.ts +31 -0
- package/src/editor/tab-context-menu.ts +8 -8
- package/src/editor/use-editor.ts +6 -6
- package/src/explorer/context-menu.ts +8 -4
- package/src/explorer/view.tsx +4 -3
- package/src/extensions/canonical-extension-descriptions.ts +58 -0
- package/src/extensions/context-menu.ts +9 -6
- package/src/extensions/extension-enablement-context.ts +15 -0
- package/src/extensions/extension-enablement-controller.ts +453 -0
- package/src/extensions/management-model.ts +111 -47
- package/src/extensions/theme-selection-protection.ts +98 -0
- package/src/extensions/uninstall-eligibility.ts +103 -0
- package/src/extensions/use-extension-management.ts +151 -67
- package/src/extensions/view.tsx +4 -0
- package/src/field-remap/chrome-labels.ts +58 -2
- package/src/field-remap/convert-palette.tsx +151 -6
- package/src/field-remap/demo.tsx +31 -2
- package/src/field-remap/flow.tsx +57 -13
- package/src/field-remap/history.ts +99 -0
- package/src/field-remap/index.ts +9 -1
- package/src/field-remap/panel.tsx +371 -97
- package/src/field-remap/preview-controller.ts +122 -0
- package/src/field-remap/preview.tsx +171 -0
- package/src/field-remap/view.css +72 -0
- package/src/index.ts +16 -1
- package/src/management/keybinding-overrides-storage.ts +28 -0
- package/src/management/preference-settings-storage.ts +28 -0
- package/src/management/settings.tsx +2 -0
- package/src/management/use-command-management.ts +31 -26
- package/src/management/use-keybinding-management.ts +25 -12
- package/src/shell/focused-extension-services.ts +138 -0
- package/src/shell/host-shell.tsx +13 -10
- package/src/shell/persistence-diagnostic-context.ts +11 -0
- package/src/shell/provider.tsx +373 -69
- package/src/shell/settings.tsx +25 -16
- package/src/shell/shell.tsx +119 -78
- package/src/shell/view-host.tsx +23 -22
- package/src/storage/local-json-storage.ts +43 -29
- package/src/storage/persistence-diagnostics.ts +72 -0
- package/src/workbench/appearance-storage.ts +28 -0
- package/src/workbench/command-host.tsx +19 -22
- package/src/workbench/command-palette.ts +14 -13
- package/src/workbench/layout-storage.ts +52 -5
- package/src/workbench/use-persisted-appearance.ts +38 -12
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState, type JSX } from 'react';
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
useEffect,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type JSX,
|
|
8
|
+
type Ref,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import {
|
|
5
11
|
createBuiltinValueTransformRegistry,
|
|
6
|
-
defineConversion,
|
|
7
|
-
defineDataShape,
|
|
8
12
|
findParentChildMappingConflicts,
|
|
9
|
-
normalizeMappingOperators,
|
|
10
13
|
projectSourceFields,
|
|
11
14
|
projectTargetSlots,
|
|
12
15
|
pruneMappingEdgesForShapes,
|
|
13
16
|
sourceFieldsFromPlainObject,
|
|
14
17
|
targetSlotsFromPlainObject,
|
|
15
|
-
withConversionEdges,
|
|
16
18
|
type MappingEdge,
|
|
17
19
|
type MappingOperator,
|
|
18
20
|
type SourceField,
|
|
@@ -32,13 +34,47 @@ import {
|
|
|
32
34
|
type FieldRemapSampleId,
|
|
33
35
|
} from './samples.js';
|
|
34
36
|
import { jsonataValueTransform } from './jsonata-transform.js';
|
|
37
|
+
import {
|
|
38
|
+
areFieldRemapHistorySnapshotsEqual,
|
|
39
|
+
createFieldRemapHistorySnapshot,
|
|
40
|
+
createFieldRemapHistoryState,
|
|
41
|
+
recordFieldRemapHistory,
|
|
42
|
+
redoFieldRemapHistory,
|
|
43
|
+
undoFieldRemapHistory,
|
|
44
|
+
type FieldRemapHistorySnapshot,
|
|
45
|
+
} from './history.js';
|
|
35
46
|
import {
|
|
36
47
|
FieldRemapShapeIoEditor,
|
|
37
48
|
ingestSourceShape,
|
|
38
49
|
ingestTargetShape,
|
|
39
50
|
} from './shape-io-editor.js';
|
|
51
|
+
import { createFieldRemapPreviewController } from './preview-controller.js';
|
|
52
|
+
import type { FieldRemapPreviewState } from './preview.js';
|
|
40
53
|
import './view.css';
|
|
41
54
|
|
|
55
|
+
export type { FieldRemapHistorySnapshot } from './history.js';
|
|
56
|
+
|
|
57
|
+
export interface FieldRemapHistoryOwner {
|
|
58
|
+
readonly canUndo: boolean;
|
|
59
|
+
readonly canRedo: boolean;
|
|
60
|
+
readonly record: (current: FieldRemapHistorySnapshot, next: FieldRemapHistorySnapshot) => void;
|
|
61
|
+
readonly reset: (next: FieldRemapHistorySnapshot) => void;
|
|
62
|
+
readonly undo: () => FieldRemapHistorySnapshot | undefined;
|
|
63
|
+
readonly redo: () => FieldRemapHistorySnapshot | undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface FieldRemapHistoryActions {
|
|
67
|
+
readonly canUndo: boolean;
|
|
68
|
+
readonly canRedo: boolean;
|
|
69
|
+
readonly undo: () => void;
|
|
70
|
+
readonly redo: () => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface FieldRemapHistoryAvailability {
|
|
74
|
+
readonly canUndo: boolean;
|
|
75
|
+
readonly canRedo: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
42
78
|
export interface FieldRemapPanelProps {
|
|
43
79
|
/** Catalog sample id, or a full sample definition. Defaults to `nested-ab` labels/ids. */
|
|
44
80
|
readonly sample?: FieldRemapSampleId | FieldRemapSampleDefinition | undefined;
|
|
@@ -71,6 +107,16 @@ export interface FieldRemapPanelProps {
|
|
|
71
107
|
/** Optional controlled n→m operators (document v2). */
|
|
72
108
|
readonly operators?: readonly MappingOperator[] | undefined;
|
|
73
109
|
readonly onOperatorsChange?: ((operators: readonly MappingOperator[]) => void) | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Composite history owner for controlled or mixed-control mapping state. When either durable
|
|
112
|
+
* channel is controlled, the Panel never creates a partial private history.
|
|
113
|
+
*/
|
|
114
|
+
readonly historyOwner?: FieldRemapHistoryOwner | undefined;
|
|
115
|
+
/** Imperative semantic undo/redo actions for host chrome. Keyboard routing remains host-owned. */
|
|
116
|
+
readonly historyActionsRef?: Ref<FieldRemapHistoryActions | null> | undefined;
|
|
117
|
+
/** Reports the active history owner's current undo/redo availability. */
|
|
118
|
+
readonly onHistoryAvailabilityChange?:
|
|
119
|
+
((availability: FieldRemapHistoryAvailability) => void) | undefined;
|
|
74
120
|
/** Controlled source field tree (host-owned shapes). */
|
|
75
121
|
readonly sources?: readonly SourceField[] | undefined;
|
|
76
122
|
readonly onSourcesChange?: ((fields: readonly SourceField[]) => void) | undefined;
|
|
@@ -90,6 +136,8 @@ export interface FieldRemapPanelProps {
|
|
|
90
136
|
readonly showBindingsList?: FieldRemapFlowMapperProps['showBindingsList'];
|
|
91
137
|
readonly showConvertPalette?: FieldRemapFlowMapperProps['showConvertPalette'];
|
|
92
138
|
readonly emptyDetail?: FieldRemapFlowMapperProps['emptyDetail'];
|
|
139
|
+
/** Show the controller-owned preview snapshot in the nested Flow rail. */
|
|
140
|
+
readonly showFlowPreview?: boolean;
|
|
93
141
|
/** Forwarded to {@link FieldRemapFlowMapper} Controls MiniMap toggle. */
|
|
94
142
|
readonly onShowMinimapChange?: FieldRemapFlowMapperProps['onShowMinimapChange'];
|
|
95
143
|
readonly onPaneContextMenu?: FieldRemapFlowMapperProps['onPaneContextMenu'];
|
|
@@ -101,11 +149,6 @@ export interface FieldRemapPanelProps {
|
|
|
101
149
|
readonly t?: FieldRemapFlowMapperProps['t'];
|
|
102
150
|
}
|
|
103
151
|
|
|
104
|
-
type FieldRemapPreviewResult = {
|
|
105
|
-
readonly output: Record<string, unknown>;
|
|
106
|
-
readonly error?: string;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
152
|
function resolveSample(sample: FieldRemapPanelProps['sample']): FieldRemapSampleDefinition {
|
|
110
153
|
if (!sample) {
|
|
111
154
|
return getFieldRemapSample('nested-ab');
|
|
@@ -116,6 +159,83 @@ function resolveSample(sample: FieldRemapPanelProps['sample']): FieldRemapSample
|
|
|
116
159
|
return sample;
|
|
117
160
|
}
|
|
118
161
|
|
|
162
|
+
function createFieldRemapPreviewSignature(): (value: unknown) => string {
|
|
163
|
+
const referenceIds = new WeakMap<object, number>();
|
|
164
|
+
let nextReferenceId = 1;
|
|
165
|
+
const referenceId = (value: object): number => {
|
|
166
|
+
const current = referenceIds.get(value);
|
|
167
|
+
if (current !== undefined) {
|
|
168
|
+
return current;
|
|
169
|
+
}
|
|
170
|
+
const next = nextReferenceId;
|
|
171
|
+
nextReferenceId += 1;
|
|
172
|
+
referenceIds.set(value, next);
|
|
173
|
+
return next;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const visit = (value: unknown, ancestors: Set<object>): string => {
|
|
177
|
+
if (value === null) {
|
|
178
|
+
return 'null';
|
|
179
|
+
}
|
|
180
|
+
if (typeof value === 'string') {
|
|
181
|
+
return JSON.stringify(value);
|
|
182
|
+
}
|
|
183
|
+
if (typeof value === 'number') {
|
|
184
|
+
return Number.isNaN(value) ? 'number:NaN' : `number:${String(value)}`;
|
|
185
|
+
}
|
|
186
|
+
if (typeof value === 'boolean' || typeof value === 'bigint') {
|
|
187
|
+
return `${typeof value}:${String(value)}`;
|
|
188
|
+
}
|
|
189
|
+
if (typeof value === 'undefined') {
|
|
190
|
+
return 'undefined';
|
|
191
|
+
}
|
|
192
|
+
if (typeof value === 'symbol') {
|
|
193
|
+
return `symbol:${String(value.description)}`;
|
|
194
|
+
}
|
|
195
|
+
if (typeof value === 'function') {
|
|
196
|
+
return `function:${referenceId(value)}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (ancestors.has(value)) {
|
|
200
|
+
return `reference:${referenceId(value)}`;
|
|
201
|
+
}
|
|
202
|
+
ancestors.add(value);
|
|
203
|
+
try {
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
return `[${value.map((item) => visit(item, ancestors)).join(',')}]`;
|
|
206
|
+
}
|
|
207
|
+
if (value instanceof Date) {
|
|
208
|
+
return `date:${value.toISOString()}`;
|
|
209
|
+
}
|
|
210
|
+
if (value instanceof Map) {
|
|
211
|
+
return `map:{${[...value.entries()]
|
|
212
|
+
.map(([key, item]) => `${visit(key, ancestors)}=>${visit(item, ancestors)}`)
|
|
213
|
+
.sort()
|
|
214
|
+
.join(',')}}`;
|
|
215
|
+
}
|
|
216
|
+
if (value instanceof Set) {
|
|
217
|
+
return `set:{${[...value.values()]
|
|
218
|
+
.map((item) => visit(item, ancestors))
|
|
219
|
+
.sort()
|
|
220
|
+
.join(',')}}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const record = value as Record<string, unknown>;
|
|
224
|
+
const tag = Object.prototype.toString.call(value);
|
|
225
|
+
return `${tag}:{${Object.keys(record)
|
|
226
|
+
.sort()
|
|
227
|
+
.map((key) => `${JSON.stringify(key)}:${visit(record[key], ancestors)}`)
|
|
228
|
+
.join(',')}}`;
|
|
229
|
+
} catch {
|
|
230
|
+
return `object:${referenceId(value)}`;
|
|
231
|
+
} finally {
|
|
232
|
+
ancestors.delete(value);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return (value) => visit(value, new Set<object>());
|
|
237
|
+
}
|
|
238
|
+
|
|
119
239
|
/**
|
|
120
240
|
* Self-contained field-remap workbench panel:
|
|
121
241
|
* schema columns A/B + optional convert wires (XYFlow) and `convertToShape` preview.
|
|
@@ -138,6 +258,9 @@ export function FieldRemapPanel({
|
|
|
138
258
|
onEdgesChange,
|
|
139
259
|
operators: operatorsProp,
|
|
140
260
|
onOperatorsChange,
|
|
261
|
+
historyOwner,
|
|
262
|
+
historyActionsRef,
|
|
263
|
+
onHistoryAvailabilityChange,
|
|
141
264
|
sources: sourcesProp,
|
|
142
265
|
onSourcesChange,
|
|
143
266
|
targets: targetsProp,
|
|
@@ -150,6 +273,7 @@ export function FieldRemapPanel({
|
|
|
150
273
|
showBindingsList,
|
|
151
274
|
showConvertPalette,
|
|
152
275
|
emptyDetail,
|
|
276
|
+
showFlowPreview,
|
|
153
277
|
onShowMinimapChange,
|
|
154
278
|
onPaneContextMenu,
|
|
155
279
|
onNodeContextMenu,
|
|
@@ -190,7 +314,12 @@ export function FieldRemapPanel({
|
|
|
190
314
|
const [uncontrolledOperators, setUncontrolledOperators] = useState<readonly MappingOperator[]>(
|
|
191
315
|
() => [...(operatorsProp ?? sample.operators ?? [])],
|
|
192
316
|
);
|
|
193
|
-
const [
|
|
317
|
+
const [preview, setPreview] = useState<FieldRemapPreviewState>({ status: 'loading' });
|
|
318
|
+
const [previewController] = useState(() => createFieldRemapPreviewController());
|
|
319
|
+
const previewControllerDisposeTimer = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
320
|
+
undefined,
|
|
321
|
+
);
|
|
322
|
+
const [previewSignature] = useState(() => createFieldRemapPreviewSignature());
|
|
194
323
|
const [uncontrolledSourceSample, setUncontrolledSourceSample] = useState<unknown>(
|
|
195
324
|
() => sourceSampleProp ?? sample.source,
|
|
196
325
|
);
|
|
@@ -221,6 +350,26 @@ export function FieldRemapPanel({
|
|
|
221
350
|
const sourceFields = sourcesControlled ? sourcesProp : uncontrolledSources;
|
|
222
351
|
const targetSlots = targetsControlled ? targetsProp : uncontrolledTargets;
|
|
223
352
|
const sourceSample = sourceSampleControlled ? sourceSampleProp : uncontrolledSourceSample;
|
|
353
|
+
const transformRegistryRevision = previewSignature(registry.list());
|
|
354
|
+
const previewRevision = previewSignature({
|
|
355
|
+
sources: sourceFields,
|
|
356
|
+
targets: targetSlots,
|
|
357
|
+
edges,
|
|
358
|
+
operators,
|
|
359
|
+
sourceSample,
|
|
360
|
+
sourceShapeId: sample.sourceIdPrefix,
|
|
361
|
+
targetShapeId: sample.targetIdPrefix,
|
|
362
|
+
sourceLabel: sample.sourceLabel,
|
|
363
|
+
targetLabel: sample.targetLabel,
|
|
364
|
+
transformRegistryRevision,
|
|
365
|
+
});
|
|
366
|
+
const historyOwnedByPanel = !edgesControlled && !operatorsControlled;
|
|
367
|
+
const [panelHistory, setPanelHistory] = useState(createFieldRemapHistoryState);
|
|
368
|
+
const currentHistorySnapshot = useMemo(
|
|
369
|
+
() => createFieldRemapHistorySnapshot(edges, operators),
|
|
370
|
+
[edges, operators],
|
|
371
|
+
);
|
|
372
|
+
const shapeRefs = useRef({ sources: sourceFields, targets: targetSlots });
|
|
224
373
|
|
|
225
374
|
const flowSources = useMemo(
|
|
226
375
|
() => projectSourceFields(sourceFields, { includeHidden }),
|
|
@@ -234,15 +383,6 @@ export function FieldRemapPanel({
|
|
|
234
383
|
() => pruneMappingEdgesForShapes(edges, flowSources, flowTargets),
|
|
235
384
|
[edges, flowSources, flowTargets],
|
|
236
385
|
);
|
|
237
|
-
const commitFlowEdges = (next: readonly MappingEdge[]) => {
|
|
238
|
-
if (includeHidden) {
|
|
239
|
-
commitEdges(next);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const visibleIds = new Set(flowEdges.map((edge) => edge.id));
|
|
243
|
-
const preserved = edges.filter((edge) => !visibleIds.has(edge.id));
|
|
244
|
-
commitEdges([...preserved, ...next]);
|
|
245
|
-
};
|
|
246
386
|
|
|
247
387
|
const commitEdges = (next: readonly MappingEdge[]) => {
|
|
248
388
|
if (!edgesControlled) {
|
|
@@ -258,6 +398,147 @@ export function FieldRemapPanel({
|
|
|
258
398
|
onOperatorsChange?.(next);
|
|
259
399
|
};
|
|
260
400
|
|
|
401
|
+
const applyHistorySnapshot = (next: FieldRemapHistorySnapshot) => {
|
|
402
|
+
if (
|
|
403
|
+
!areFieldRemapHistorySnapshotsEqual(
|
|
404
|
+
createFieldRemapHistorySnapshot(edges, next.operators),
|
|
405
|
+
next,
|
|
406
|
+
)
|
|
407
|
+
) {
|
|
408
|
+
commitEdges(next.edges);
|
|
409
|
+
}
|
|
410
|
+
if (
|
|
411
|
+
!areFieldRemapHistorySnapshotsEqual(
|
|
412
|
+
createFieldRemapHistorySnapshot(next.edges, operators),
|
|
413
|
+
next,
|
|
414
|
+
)
|
|
415
|
+
) {
|
|
416
|
+
commitOperators(next.operators);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const recordSemanticSnapshot = (next: FieldRemapHistorySnapshot) => {
|
|
421
|
+
if (areFieldRemapHistorySnapshotsEqual(currentHistorySnapshot, next)) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (historyOwnedByPanel) {
|
|
425
|
+
setPanelHistory((current) => recordFieldRemapHistory(current, currentHistorySnapshot, next));
|
|
426
|
+
} else {
|
|
427
|
+
historyOwner?.record(currentHistorySnapshot, next);
|
|
428
|
+
}
|
|
429
|
+
applyHistorySnapshot(next);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const commitFlowEdges = (next: readonly MappingEdge[]) => {
|
|
433
|
+
if (includeHidden) {
|
|
434
|
+
recordSemanticSnapshot(createFieldRemapHistorySnapshot(next, operators));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const visibleIds = new Set(flowEdges.map((edge) => edge.id));
|
|
438
|
+
const preserved = edges.filter((edge) => !visibleIds.has(edge.id));
|
|
439
|
+
recordSemanticSnapshot(createFieldRemapHistorySnapshot([...preserved, ...next], operators));
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const resetHistory = (next: FieldRemapHistorySnapshot) => {
|
|
443
|
+
if (historyOwnedByPanel) {
|
|
444
|
+
setPanelHistory(createFieldRemapHistoryState());
|
|
445
|
+
} else {
|
|
446
|
+
historyOwner?.reset(next);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const resetHistoryForShapes = (
|
|
451
|
+
nextEdges: readonly MappingEdge[],
|
|
452
|
+
nextSources: readonly SourceField[],
|
|
453
|
+
nextTargets: readonly TargetSlot[],
|
|
454
|
+
) => {
|
|
455
|
+
shapeRefs.current = { sources: nextSources, targets: nextTargets };
|
|
456
|
+
const next = createFieldRemapHistorySnapshot(nextEdges, operators);
|
|
457
|
+
resetHistory(next);
|
|
458
|
+
applyHistorySnapshot(next);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const undo = () => {
|
|
462
|
+
if (historyOwnedByPanel) {
|
|
463
|
+
const result = undoFieldRemapHistory(panelHistory, currentHistorySnapshot);
|
|
464
|
+
if (!result) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
setPanelHistory(result.state);
|
|
468
|
+
applyHistorySnapshot(result.snapshot);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (!historyOwner?.canUndo) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const next = historyOwner.undo();
|
|
475
|
+
if (next) {
|
|
476
|
+
applyHistorySnapshot(next);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const redo = () => {
|
|
481
|
+
if (historyOwnedByPanel) {
|
|
482
|
+
const result = redoFieldRemapHistory(panelHistory, currentHistorySnapshot);
|
|
483
|
+
if (!result) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
setPanelHistory(result.state);
|
|
487
|
+
applyHistorySnapshot(result.snapshot);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (!historyOwner?.canRedo) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const next = historyOwner.redo();
|
|
494
|
+
if (next) {
|
|
495
|
+
applyHistorySnapshot(next);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const historyAvailability = {
|
|
500
|
+
canUndo: historyOwnedByPanel ? panelHistory.past.length > 0 : (historyOwner?.canUndo ?? false),
|
|
501
|
+
canRedo: historyOwnedByPanel
|
|
502
|
+
? panelHistory.future.length > 0
|
|
503
|
+
: (historyOwner?.canRedo ?? false),
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
useImperativeHandle(historyActionsRef, () => ({ ...historyAvailability, undo, redo }), [
|
|
507
|
+
historyAvailability.canRedo,
|
|
508
|
+
historyAvailability.canUndo,
|
|
509
|
+
panelHistory,
|
|
510
|
+
currentHistorySnapshot,
|
|
511
|
+
historyOwner,
|
|
512
|
+
]);
|
|
513
|
+
|
|
514
|
+
useEffect(() => {
|
|
515
|
+
onHistoryAvailabilityChange?.(historyAvailability);
|
|
516
|
+
}, [historyAvailability.canRedo, historyAvailability.canUndo, onHistoryAvailabilityChange]);
|
|
517
|
+
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
if (shapeRefs.current.sources === sourceFields && shapeRefs.current.targets === targetSlots) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
shapeRefs.current = { sources: sourceFields, targets: targetSlots };
|
|
523
|
+
const nextEdges = pruneMappingEdgesForShapes(edges, sourceFields, targetSlots);
|
|
524
|
+
const next = createFieldRemapHistorySnapshot(nextEdges, operators);
|
|
525
|
+
if (historyOwnedByPanel) {
|
|
526
|
+
setPanelHistory(createFieldRemapHistoryState());
|
|
527
|
+
} else {
|
|
528
|
+
historyOwner?.reset(next);
|
|
529
|
+
}
|
|
530
|
+
applyHistorySnapshot(next);
|
|
531
|
+
}, [
|
|
532
|
+
edges,
|
|
533
|
+
historyOwnedByPanel,
|
|
534
|
+
historyOwner,
|
|
535
|
+
onEdgesChange,
|
|
536
|
+
onOperatorsChange,
|
|
537
|
+
operators,
|
|
538
|
+
sourceFields,
|
|
539
|
+
targetSlots,
|
|
540
|
+
]);
|
|
541
|
+
|
|
261
542
|
const commitSources = (next: readonly SourceField[]) => {
|
|
262
543
|
if (!sourcesControlled) {
|
|
263
544
|
setUncontrolledSources(next);
|
|
@@ -272,84 +553,64 @@ export function FieldRemapPanel({
|
|
|
272
553
|
onTargetsChange?.(next);
|
|
273
554
|
};
|
|
274
555
|
|
|
275
|
-
const shapes = useMemo(
|
|
276
|
-
() => [
|
|
277
|
-
defineDataShape({
|
|
278
|
-
id: sample.sourceIdPrefix,
|
|
279
|
-
label: sample.sourceLabel,
|
|
280
|
-
role: 'source',
|
|
281
|
-
fields: sourceFields,
|
|
282
|
-
}),
|
|
283
|
-
defineDataShape({
|
|
284
|
-
id: sample.targetIdPrefix,
|
|
285
|
-
label: sample.targetLabel,
|
|
286
|
-
role: 'target',
|
|
287
|
-
fields: targetSlots,
|
|
288
|
-
}),
|
|
289
|
-
],
|
|
290
|
-
[sample, sourceFields, targetSlots],
|
|
291
|
-
);
|
|
292
|
-
|
|
293
556
|
const conflicts = useMemo(
|
|
294
557
|
() => findParentChildMappingConflicts(edges, sourceFields, targetSlots),
|
|
295
558
|
[edges, sourceFields, targetSlots],
|
|
296
559
|
);
|
|
297
560
|
|
|
298
561
|
useEffect(() => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
void convertToShape({
|
|
311
|
-
conversion,
|
|
312
|
-
shapes,
|
|
313
|
-
inputs: { [sample.sourceIdPrefix]: sourceSample },
|
|
314
|
-
transforms: registry,
|
|
315
|
-
signal: controller.signal,
|
|
316
|
-
})
|
|
317
|
-
.then(async (next) => {
|
|
318
|
-
if (controller.signal.aborted) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
const normalizedOps = normalizeMappingOperators(operators);
|
|
322
|
-
if (!normalizedOps?.length) {
|
|
323
|
-
setResult({ output: next.output });
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
const merged = await applyMappingOperators({
|
|
327
|
-
operators: normalizedOps,
|
|
328
|
-
sources: sourceFields,
|
|
329
|
-
targets: targetSlots,
|
|
330
|
-
inputs: { [sample.sourceIdPrefix]: sourceSample },
|
|
331
|
-
transforms: registry,
|
|
332
|
-
output: next.output,
|
|
333
|
-
signal: controller.signal,
|
|
334
|
-
});
|
|
335
|
-
if (!controller.signal.aborted) {
|
|
336
|
-
setResult({ output: merged.output });
|
|
337
|
-
}
|
|
338
|
-
})
|
|
339
|
-
.catch((error: unknown) => {
|
|
340
|
-
if (controller.signal.aborted) {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
setResult({
|
|
344
|
-
output: {},
|
|
345
|
-
error: error instanceof Error ? error.message : String(error),
|
|
346
|
-
});
|
|
347
|
-
});
|
|
562
|
+
if (previewControllerDisposeTimer.current !== undefined) {
|
|
563
|
+
clearTimeout(previewControllerDisposeTimer.current);
|
|
564
|
+
previewControllerDisposeTimer.current = undefined;
|
|
565
|
+
}
|
|
566
|
+
const unsubscribe = previewController.subscribe(() => {
|
|
567
|
+
setPreview(previewController.getSnapshot());
|
|
568
|
+
});
|
|
569
|
+
const currentSnapshot = previewController.getSnapshot();
|
|
570
|
+
if (currentSnapshot.status !== 'unavailable') {
|
|
571
|
+
setPreview(currentSnapshot);
|
|
572
|
+
}
|
|
348
573
|
|
|
349
574
|
return () => {
|
|
350
|
-
|
|
575
|
+
unsubscribe();
|
|
576
|
+
const timer = setTimeout(() => {
|
|
577
|
+
if (previewControllerDisposeTimer.current === timer) {
|
|
578
|
+
previewControllerDisposeTimer.current = undefined;
|
|
579
|
+
previewController.dispose();
|
|
580
|
+
}
|
|
581
|
+
}, 0);
|
|
582
|
+
previewControllerDisposeTimer.current = timer;
|
|
351
583
|
};
|
|
352
|
-
}, [
|
|
584
|
+
}, [previewController]);
|
|
585
|
+
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
previewController.update({
|
|
588
|
+
kind: 'evaluate',
|
|
589
|
+
revision: previewRevision,
|
|
590
|
+
input: {
|
|
591
|
+
sources: sourceFields,
|
|
592
|
+
targets: targetSlots,
|
|
593
|
+
edges,
|
|
594
|
+
operators,
|
|
595
|
+
inputs: { [sample.sourceIdPrefix]: sourceSample },
|
|
596
|
+
transforms: registry,
|
|
597
|
+
sourceShapeIds: [sample.sourceIdPrefix],
|
|
598
|
+
targetShapeId: sample.targetIdPrefix,
|
|
599
|
+
sourceLabel: sample.sourceLabel,
|
|
600
|
+
targetLabel: sample.targetLabel,
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
}, [
|
|
604
|
+
edges,
|
|
605
|
+
operators,
|
|
606
|
+
previewController,
|
|
607
|
+
previewRevision,
|
|
608
|
+
registry,
|
|
609
|
+
sample,
|
|
610
|
+
sourceFields,
|
|
611
|
+
sourceSample,
|
|
612
|
+
targetSlots,
|
|
613
|
+
]);
|
|
353
614
|
|
|
354
615
|
const applySourceShape = (parsed: unknown) => {
|
|
355
616
|
const ingested = ingestSourceShape(parsed, sample.sourceIdPrefix);
|
|
@@ -358,7 +619,11 @@ export function FieldRemapPanel({
|
|
|
358
619
|
}
|
|
359
620
|
setSourceJson(ingested.sampleJson);
|
|
360
621
|
commitSources(ingested.fields);
|
|
361
|
-
|
|
622
|
+
resetHistoryForShapes(
|
|
623
|
+
pruneMappingEdgesForShapes(edges, ingested.fields, targetSlots),
|
|
624
|
+
ingested.fields,
|
|
625
|
+
targetSlots,
|
|
626
|
+
);
|
|
362
627
|
};
|
|
363
628
|
|
|
364
629
|
const applyTargetShape = (parsed: unknown) => {
|
|
@@ -366,7 +631,11 @@ export function FieldRemapPanel({
|
|
|
366
631
|
setTargetSample(parsed);
|
|
367
632
|
setTargetJson(ingested.sampleJson);
|
|
368
633
|
commitTargets(ingested.fields);
|
|
369
|
-
|
|
634
|
+
resetHistoryForShapes(
|
|
635
|
+
pruneMappingEdgesForShapes(edges, sourceFields, ingested.fields),
|
|
636
|
+
sourceFields,
|
|
637
|
+
ingested.fields,
|
|
638
|
+
);
|
|
370
639
|
};
|
|
371
640
|
|
|
372
641
|
return (
|
|
@@ -428,7 +697,9 @@ export function FieldRemapPanel({
|
|
|
428
697
|
transforms={registry}
|
|
429
698
|
onEdgesChange={commitFlowEdges}
|
|
430
699
|
operators={operators}
|
|
431
|
-
onOperatorsChange={
|
|
700
|
+
onOperatorsChange={(next) =>
|
|
701
|
+
recordSemanticSnapshot(createFieldRemapHistorySnapshot(edges, next))
|
|
702
|
+
}
|
|
432
703
|
sourceTitle={sample.sourceLabel}
|
|
433
704
|
targetTitle={sample.targetLabel}
|
|
434
705
|
showMinimap={showMinimap}
|
|
@@ -437,6 +708,7 @@ export function FieldRemapPanel({
|
|
|
437
708
|
showBindingsList={showBindingsList}
|
|
438
709
|
showConvertPalette={showConvertPalette}
|
|
439
710
|
emptyDetail={emptyDetail}
|
|
711
|
+
{...(showFlowPreview ? { preview, showPreview: true } : {})}
|
|
440
712
|
onShowMinimapChange={onShowMinimapChange}
|
|
441
713
|
includeHidden={includeHidden}
|
|
442
714
|
onIncludeHiddenChange={setIncludeHidden}
|
|
@@ -460,9 +732,9 @@ export function FieldRemapPanel({
|
|
|
460
732
|
</p>
|
|
461
733
|
) : null}
|
|
462
734
|
|
|
463
|
-
{
|
|
735
|
+
{preview.status === 'error' ? (
|
|
464
736
|
<p className="workbench-field-remap-demo__error" role="alert">
|
|
465
|
-
{
|
|
737
|
+
{preview.message}
|
|
466
738
|
</p>
|
|
467
739
|
) : null}
|
|
468
740
|
|
|
@@ -473,7 +745,9 @@ export function FieldRemapPanel({
|
|
|
473
745
|
</section>
|
|
474
746
|
<section className="workbench-field-remap-demo__pane" aria-labelledby="field-remap-target">
|
|
475
747
|
<h3 id="field-remap-target">{sample.targetLabel}</h3>
|
|
476
|
-
<pre data-testid="field-remap-result">
|
|
748
|
+
<pre data-testid="field-remap-result">
|
|
749
|
+
{JSON.stringify(preview.status === 'ready' ? preview.result.output : {}, null, 2)}
|
|
750
|
+
</pre>
|
|
477
751
|
</section>
|
|
478
752
|
</div>
|
|
479
753
|
</div>
|