@workbench-kit/shell-react 0.0.2-prototype.0.2.32 → 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 +87 -2
- package/src/field-remap/convert-palette.tsx +164 -18
- package/src/field-remap/demo.tsx +48 -3
- package/src/field-remap/detail-panel.tsx +6 -5
- package/src/field-remap/flow.tsx +341 -155
- package/src/field-remap/history.ts +99 -0
- package/src/field-remap/index.ts +9 -1
- package/src/field-remap/panel.tsx +388 -97
- package/src/field-remap/preview-controller.ts +122 -0
- package/src/field-remap/preview.tsx +171 -0
- package/src/field-remap/view.css +154 -2
- 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;
|
|
@@ -83,6 +129,15 @@ export interface FieldRemapPanelProps {
|
|
|
83
129
|
readonly targetShape?: unknown;
|
|
84
130
|
/** Forwarded to {@link FieldRemapFlowMapper} (default true). */
|
|
85
131
|
readonly showMinimap?: boolean | undefined;
|
|
132
|
+
/** Forwarded Flow chrome preset. `embed` hides the hint and binding list by default. */
|
|
133
|
+
readonly chrome?: FieldRemapFlowMapperProps['chrome'];
|
|
134
|
+
/** Explicit Flow chrome overrides take precedence over {@link FieldRemapPanelProps.chrome}. */
|
|
135
|
+
readonly showFlowHint?: FieldRemapFlowMapperProps['showFlowHint'];
|
|
136
|
+
readonly showBindingsList?: FieldRemapFlowMapperProps['showBindingsList'];
|
|
137
|
+
readonly showConvertPalette?: FieldRemapFlowMapperProps['showConvertPalette'];
|
|
138
|
+
readonly emptyDetail?: FieldRemapFlowMapperProps['emptyDetail'];
|
|
139
|
+
/** Show the controller-owned preview snapshot in the nested Flow rail. */
|
|
140
|
+
readonly showFlowPreview?: boolean;
|
|
86
141
|
/** Forwarded to {@link FieldRemapFlowMapper} Controls MiniMap toggle. */
|
|
87
142
|
readonly onShowMinimapChange?: FieldRemapFlowMapperProps['onShowMinimapChange'];
|
|
88
143
|
readonly onPaneContextMenu?: FieldRemapFlowMapperProps['onPaneContextMenu'];
|
|
@@ -94,11 +149,6 @@ export interface FieldRemapPanelProps {
|
|
|
94
149
|
readonly t?: FieldRemapFlowMapperProps['t'];
|
|
95
150
|
}
|
|
96
151
|
|
|
97
|
-
type FieldRemapPreviewResult = {
|
|
98
|
-
readonly output: Record<string, unknown>;
|
|
99
|
-
readonly error?: string;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
152
|
function resolveSample(sample: FieldRemapPanelProps['sample']): FieldRemapSampleDefinition {
|
|
103
153
|
if (!sample) {
|
|
104
154
|
return getFieldRemapSample('nested-ab');
|
|
@@ -109,6 +159,83 @@ function resolveSample(sample: FieldRemapPanelProps['sample']): FieldRemapSample
|
|
|
109
159
|
return sample;
|
|
110
160
|
}
|
|
111
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
|
+
|
|
112
239
|
/**
|
|
113
240
|
* Self-contained field-remap workbench panel:
|
|
114
241
|
* schema columns A/B + optional convert wires (XYFlow) and `convertToShape` preview.
|
|
@@ -131,6 +258,9 @@ export function FieldRemapPanel({
|
|
|
131
258
|
onEdgesChange,
|
|
132
259
|
operators: operatorsProp,
|
|
133
260
|
onOperatorsChange,
|
|
261
|
+
historyOwner,
|
|
262
|
+
historyActionsRef,
|
|
263
|
+
onHistoryAvailabilityChange,
|
|
134
264
|
sources: sourcesProp,
|
|
135
265
|
onSourcesChange,
|
|
136
266
|
targets: targetsProp,
|
|
@@ -138,6 +268,12 @@ export function FieldRemapPanel({
|
|
|
138
268
|
sourceSample: sourceSampleProp,
|
|
139
269
|
targetShape: targetShapeProp,
|
|
140
270
|
showMinimap,
|
|
271
|
+
chrome,
|
|
272
|
+
showFlowHint,
|
|
273
|
+
showBindingsList,
|
|
274
|
+
showConvertPalette,
|
|
275
|
+
emptyDetail,
|
|
276
|
+
showFlowPreview,
|
|
141
277
|
onShowMinimapChange,
|
|
142
278
|
onPaneContextMenu,
|
|
143
279
|
onNodeContextMenu,
|
|
@@ -178,7 +314,12 @@ export function FieldRemapPanel({
|
|
|
178
314
|
const [uncontrolledOperators, setUncontrolledOperators] = useState<readonly MappingOperator[]>(
|
|
179
315
|
() => [...(operatorsProp ?? sample.operators ?? [])],
|
|
180
316
|
);
|
|
181
|
-
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());
|
|
182
323
|
const [uncontrolledSourceSample, setUncontrolledSourceSample] = useState<unknown>(
|
|
183
324
|
() => sourceSampleProp ?? sample.source,
|
|
184
325
|
);
|
|
@@ -209,6 +350,26 @@ export function FieldRemapPanel({
|
|
|
209
350
|
const sourceFields = sourcesControlled ? sourcesProp : uncontrolledSources;
|
|
210
351
|
const targetSlots = targetsControlled ? targetsProp : uncontrolledTargets;
|
|
211
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 });
|
|
212
373
|
|
|
213
374
|
const flowSources = useMemo(
|
|
214
375
|
() => projectSourceFields(sourceFields, { includeHidden }),
|
|
@@ -222,15 +383,6 @@ export function FieldRemapPanel({
|
|
|
222
383
|
() => pruneMappingEdgesForShapes(edges, flowSources, flowTargets),
|
|
223
384
|
[edges, flowSources, flowTargets],
|
|
224
385
|
);
|
|
225
|
-
const commitFlowEdges = (next: readonly MappingEdge[]) => {
|
|
226
|
-
if (includeHidden) {
|
|
227
|
-
commitEdges(next);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
const visibleIds = new Set(flowEdges.map((edge) => edge.id));
|
|
231
|
-
const preserved = edges.filter((edge) => !visibleIds.has(edge.id));
|
|
232
|
-
commitEdges([...preserved, ...next]);
|
|
233
|
-
};
|
|
234
386
|
|
|
235
387
|
const commitEdges = (next: readonly MappingEdge[]) => {
|
|
236
388
|
if (!edgesControlled) {
|
|
@@ -246,6 +398,147 @@ export function FieldRemapPanel({
|
|
|
246
398
|
onOperatorsChange?.(next);
|
|
247
399
|
};
|
|
248
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
|
+
|
|
249
542
|
const commitSources = (next: readonly SourceField[]) => {
|
|
250
543
|
if (!sourcesControlled) {
|
|
251
544
|
setUncontrolledSources(next);
|
|
@@ -260,84 +553,64 @@ export function FieldRemapPanel({
|
|
|
260
553
|
onTargetsChange?.(next);
|
|
261
554
|
};
|
|
262
555
|
|
|
263
|
-
const shapes = useMemo(
|
|
264
|
-
() => [
|
|
265
|
-
defineDataShape({
|
|
266
|
-
id: sample.sourceIdPrefix,
|
|
267
|
-
label: sample.sourceLabel,
|
|
268
|
-
role: 'source',
|
|
269
|
-
fields: sourceFields,
|
|
270
|
-
}),
|
|
271
|
-
defineDataShape({
|
|
272
|
-
id: sample.targetIdPrefix,
|
|
273
|
-
label: sample.targetLabel,
|
|
274
|
-
role: 'target',
|
|
275
|
-
fields: targetSlots,
|
|
276
|
-
}),
|
|
277
|
-
],
|
|
278
|
-
[sample, sourceFields, targetSlots],
|
|
279
|
-
);
|
|
280
|
-
|
|
281
556
|
const conflicts = useMemo(
|
|
282
557
|
() => findParentChildMappingConflicts(edges, sourceFields, targetSlots),
|
|
283
558
|
[edges, sourceFields, targetSlots],
|
|
284
559
|
);
|
|
285
560
|
|
|
286
561
|
useEffect(() => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
void convertToShape({
|
|
299
|
-
conversion,
|
|
300
|
-
shapes,
|
|
301
|
-
inputs: { [sample.sourceIdPrefix]: sourceSample },
|
|
302
|
-
transforms: registry,
|
|
303
|
-
signal: controller.signal,
|
|
304
|
-
})
|
|
305
|
-
.then(async (next) => {
|
|
306
|
-
if (controller.signal.aborted) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
const normalizedOps = normalizeMappingOperators(operators);
|
|
310
|
-
if (!normalizedOps?.length) {
|
|
311
|
-
setResult({ output: next.output });
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const merged = await applyMappingOperators({
|
|
315
|
-
operators: normalizedOps,
|
|
316
|
-
sources: sourceFields,
|
|
317
|
-
targets: targetSlots,
|
|
318
|
-
inputs: { [sample.sourceIdPrefix]: sourceSample },
|
|
319
|
-
transforms: registry,
|
|
320
|
-
output: next.output,
|
|
321
|
-
signal: controller.signal,
|
|
322
|
-
});
|
|
323
|
-
if (!controller.signal.aborted) {
|
|
324
|
-
setResult({ output: merged.output });
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
.catch((error: unknown) => {
|
|
328
|
-
if (controller.signal.aborted) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
setResult({
|
|
332
|
-
output: {},
|
|
333
|
-
error: error instanceof Error ? error.message : String(error),
|
|
334
|
-
});
|
|
335
|
-
});
|
|
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
|
+
}
|
|
336
573
|
|
|
337
574
|
return () => {
|
|
338
|
-
|
|
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;
|
|
339
583
|
};
|
|
340
|
-
}, [
|
|
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
|
+
]);
|
|
341
614
|
|
|
342
615
|
const applySourceShape = (parsed: unknown) => {
|
|
343
616
|
const ingested = ingestSourceShape(parsed, sample.sourceIdPrefix);
|
|
@@ -346,7 +619,11 @@ export function FieldRemapPanel({
|
|
|
346
619
|
}
|
|
347
620
|
setSourceJson(ingested.sampleJson);
|
|
348
621
|
commitSources(ingested.fields);
|
|
349
|
-
|
|
622
|
+
resetHistoryForShapes(
|
|
623
|
+
pruneMappingEdgesForShapes(edges, ingested.fields, targetSlots),
|
|
624
|
+
ingested.fields,
|
|
625
|
+
targetSlots,
|
|
626
|
+
);
|
|
350
627
|
};
|
|
351
628
|
|
|
352
629
|
const applyTargetShape = (parsed: unknown) => {
|
|
@@ -354,7 +631,11 @@ export function FieldRemapPanel({
|
|
|
354
631
|
setTargetSample(parsed);
|
|
355
632
|
setTargetJson(ingested.sampleJson);
|
|
356
633
|
commitTargets(ingested.fields);
|
|
357
|
-
|
|
634
|
+
resetHistoryForShapes(
|
|
635
|
+
pruneMappingEdgesForShapes(edges, sourceFields, ingested.fields),
|
|
636
|
+
sourceFields,
|
|
637
|
+
ingested.fields,
|
|
638
|
+
);
|
|
358
639
|
};
|
|
359
640
|
|
|
360
641
|
return (
|
|
@@ -416,10 +697,18 @@ export function FieldRemapPanel({
|
|
|
416
697
|
transforms={registry}
|
|
417
698
|
onEdgesChange={commitFlowEdges}
|
|
418
699
|
operators={operators}
|
|
419
|
-
onOperatorsChange={
|
|
700
|
+
onOperatorsChange={(next) =>
|
|
701
|
+
recordSemanticSnapshot(createFieldRemapHistorySnapshot(edges, next))
|
|
702
|
+
}
|
|
420
703
|
sourceTitle={sample.sourceLabel}
|
|
421
704
|
targetTitle={sample.targetLabel}
|
|
422
705
|
showMinimap={showMinimap}
|
|
706
|
+
chrome={chrome}
|
|
707
|
+
showFlowHint={showFlowHint}
|
|
708
|
+
showBindingsList={showBindingsList}
|
|
709
|
+
showConvertPalette={showConvertPalette}
|
|
710
|
+
emptyDetail={emptyDetail}
|
|
711
|
+
{...(showFlowPreview ? { preview, showPreview: true } : {})}
|
|
423
712
|
onShowMinimapChange={onShowMinimapChange}
|
|
424
713
|
includeHidden={includeHidden}
|
|
425
714
|
onIncludeHiddenChange={setIncludeHidden}
|
|
@@ -443,9 +732,9 @@ export function FieldRemapPanel({
|
|
|
443
732
|
</p>
|
|
444
733
|
) : null}
|
|
445
734
|
|
|
446
|
-
{
|
|
735
|
+
{preview.status === 'error' ? (
|
|
447
736
|
<p className="workbench-field-remap-demo__error" role="alert">
|
|
448
|
-
{
|
|
737
|
+
{preview.message}
|
|
449
738
|
</p>
|
|
450
739
|
) : null}
|
|
451
740
|
|
|
@@ -456,7 +745,9 @@ export function FieldRemapPanel({
|
|
|
456
745
|
</section>
|
|
457
746
|
<section className="workbench-field-remap-demo__pane" aria-labelledby="field-remap-target">
|
|
458
747
|
<h3 id="field-remap-target">{sample.targetLabel}</h3>
|
|
459
|
-
<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>
|
|
460
751
|
</section>
|
|
461
752
|
</div>
|
|
462
753
|
</div>
|