@workbench-kit/shell-react 0.0.2-prototype.0.2.14 → 0.0.2-prototype.0.2.16
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/package.json +11 -8
- package/src/editor/state-storage.ts +10 -21
- package/src/field-remap/convert-note-editor.tsx +11 -16
- package/src/field-remap/convert-palette.tsx +4 -5
- package/src/field-remap/flow-sync-key.ts +149 -0
- package/src/field-remap/flow.tsx +35 -19
- package/src/field-remap/samples.ts +1 -14
- package/src/field-remap/view-data.ts +1 -0
- package/src/field-remap/view.tsx +19 -0
- package/src/index.ts +6 -1
- package/src/jdw/lab-view-data.ts +1 -0
- package/src/jdw/lab-view.tsx +27 -0
- package/src/management/keybinding-overrides-storage.ts +9 -23
- package/src/management/preference-settings-storage.ts +9 -23
- package/src/shell/provider.tsx +16 -1
- package/src/shell/shell.tsx +48 -8
- package/src/shell/view-host.tsx +80 -45
- package/src/storage/local-json-storage.ts +66 -0
- package/src/workbench/appearance-storage.ts +14 -27
- package/src/workbench/layout-storage.ts +16 -24
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workbench-kit/shell-react",
|
|
3
|
-
"version": "0.0.2-prototype.0.2.
|
|
3
|
+
"version": "0.0.2-prototype.0.2.16",
|
|
4
4
|
"private": false,
|
|
5
|
+
"sideEffects": [
|
|
6
|
+
"**/*.css"
|
|
7
|
+
],
|
|
5
8
|
"type": "module",
|
|
6
9
|
"exports": {
|
|
7
10
|
".": "./src/index.ts",
|
|
@@ -21,13 +24,13 @@
|
|
|
21
24
|
"@xyflow/react": "^12.11.2",
|
|
22
25
|
"jsonata": "^2.2.0",
|
|
23
26
|
"react-table-mapping": "^1.0.3",
|
|
24
|
-
"@workbench-kit/
|
|
25
|
-
"@workbench-kit/
|
|
26
|
-
"@workbench-kit/
|
|
27
|
-
"@workbench-kit/
|
|
28
|
-
"@workbench-kit/
|
|
29
|
-
"@workbench-kit/
|
|
30
|
-
"@workbench-kit/
|
|
27
|
+
"@workbench-kit/react": "0.0.2-prototype.0.2.16",
|
|
28
|
+
"@workbench-kit/workbench-config": "0.0.2-prototype.0.2.16",
|
|
29
|
+
"@workbench-kit/field-remap": "0.0.2-prototype.0.2.16",
|
|
30
|
+
"@workbench-kit/tokens": "0.0.2-prototype.0.2.16",
|
|
31
|
+
"@workbench-kit/workbench-core": "0.0.2-prototype.0.2.16",
|
|
32
|
+
"@workbench-kit/platform": "0.0.2-prototype.0.2.16",
|
|
33
|
+
"@workbench-kit/workspace": "0.0.2-prototype.0.2.16"
|
|
31
34
|
},
|
|
32
35
|
"peerDependencies": {
|
|
33
36
|
"react": "^19.0.0",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createBrowserWorkbenchStorage,
|
|
3
2
|
type EditorGroupState,
|
|
4
3
|
type EditorLayoutDirection,
|
|
5
4
|
type EditorLayoutNode,
|
|
@@ -10,11 +9,16 @@ import {
|
|
|
10
9
|
} from '@workbench-kit/workbench-core';
|
|
11
10
|
|
|
12
11
|
import { isRecord } from '../is-record.js';
|
|
12
|
+
import {
|
|
13
|
+
readLocalJsonStorage,
|
|
14
|
+
resolveLocalWorkbenchStorage,
|
|
15
|
+
writeLocalJsonStorage,
|
|
16
|
+
} from '../storage/local-json-storage.js';
|
|
13
17
|
|
|
14
18
|
export const DEFAULT_WORKBENCH_EDITOR_STATE_STORAGE_KEY = 'workbench-kit/.workbench/editors';
|
|
15
19
|
|
|
16
20
|
export function isWorkbenchEditorStatePersistenceAvailable(): boolean {
|
|
17
|
-
return
|
|
21
|
+
return resolveLocalWorkbenchStorage() !== undefined;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
export function editorStateToStorageValue(state: EditorState): EditorState {
|
|
@@ -42,17 +46,7 @@ export function readPersistedEditorState(
|
|
|
42
46
|
storageKey = DEFAULT_WORKBENCH_EDITOR_STATE_STORAGE_KEY,
|
|
43
47
|
storage?: WorkbenchStorageReader,
|
|
44
48
|
): EditorState | undefined {
|
|
45
|
-
|
|
46
|
-
if (!resolvedStorage) return undefined;
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const raw = resolvedStorage.getItem(storageKey);
|
|
50
|
-
if (!raw) return undefined;
|
|
51
|
-
|
|
52
|
-
return parseEditorStateStorageValue(JSON.parse(raw) as unknown);
|
|
53
|
-
} catch {
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
49
|
+
return readLocalJsonStorage(storageKey, parseEditorStateStorageValue, () => undefined, storage);
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
export function writePersistedEditorState(
|
|
@@ -60,14 +54,9 @@ export function writePersistedEditorState(
|
|
|
60
54
|
storageKey = DEFAULT_WORKBENCH_EDITOR_STATE_STORAGE_KEY,
|
|
61
55
|
storage?: WorkbenchStorageWriter,
|
|
62
56
|
): void {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
resolvedStorage.setItem(storageKey, JSON.stringify(editorStateToStorageValue(state), null, 2));
|
|
68
|
-
} catch {
|
|
69
|
-
// Ignore quota and security errors so the shell keeps working offline.
|
|
70
|
-
}
|
|
57
|
+
writeLocalJsonStorage(storageKey, state, storage, {
|
|
58
|
+
toStorageValue: editorStateToStorageValue,
|
|
59
|
+
});
|
|
71
60
|
}
|
|
72
61
|
|
|
73
62
|
function parseEditorStateStorageValue(value: unknown): EditorState | undefined {
|
|
@@ -49,10 +49,9 @@ export function ConvertNoteEditor({
|
|
|
49
49
|
|
|
50
50
|
const portTypes = useMemo(() => edgePortTypes(edge, sources, targets), [edge, sources, targets]);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
52
|
+
// ValueTransformRegistry is intentionally mutable (`register`). Resolve its
|
|
53
|
+
// display contract on render so a host rerender sees replacement definitions.
|
|
54
|
+
const optionFields = optionFieldsForStep(transforms, transformId);
|
|
56
55
|
|
|
57
56
|
const optionValue = useMemo(() => {
|
|
58
57
|
if (!transformId) {
|
|
@@ -63,18 +62,14 @@ export function ConvertNoteEditor({
|
|
|
63
62
|
);
|
|
64
63
|
}, [chain, edge.transformOptionSteps, edge.transformOptions, stepIndex, transformId]);
|
|
65
64
|
|
|
66
|
-
const replaceCatalog =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
mode: 'replace',
|
|
75
|
-
}),
|
|
76
|
-
[edge, portTypes.sourceType, portTypes.targetType, stepIndex, transforms],
|
|
77
|
-
);
|
|
65
|
+
const replaceCatalog = listCompatibleTransforms({
|
|
66
|
+
registry: transforms,
|
|
67
|
+
edge,
|
|
68
|
+
stepIndex,
|
|
69
|
+
sourceType: portTypes.sourceType,
|
|
70
|
+
targetType: portTypes.targetType,
|
|
71
|
+
mode: 'replace',
|
|
72
|
+
});
|
|
78
73
|
|
|
79
74
|
if (!transformId) {
|
|
80
75
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { JSX } from 'react';
|
|
2
2
|
import { Button } from '@workbench-kit/react/primitives';
|
|
3
3
|
import type { ValueTransformRegistry } from '@workbench-kit/field-remap';
|
|
4
4
|
|
|
@@ -28,10 +28,9 @@ export function FieldRemapConvertPalette({
|
|
|
28
28
|
onAddSplit,
|
|
29
29
|
chromeLabels = defaultFieldRemapChromeLabels,
|
|
30
30
|
}: FieldRemapConvertPaletteProps): JSX.Element {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
31
|
+
// The registry supports replacing definitions through `register`; resolve
|
|
32
|
+
// display metadata on each host render even when the registry object is stable.
|
|
33
|
+
const catalog = transforms.list().filter((definition) => definition.id !== 'identity');
|
|
35
34
|
|
|
36
35
|
return (
|
|
37
36
|
<aside
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Edge, Node } from '@xyflow/react';
|
|
2
|
+
import type { ValueTransformRegistry } from '@workbench-kit/field-remap';
|
|
3
|
+
|
|
4
|
+
import type { FieldRemapFlowEdgeData, FieldRemapFlowNodeData } from './flow-adapter.js';
|
|
5
|
+
import type { FieldRemapSelection } from './flow-ops.js';
|
|
6
|
+
|
|
7
|
+
type GraphSyncToken = string | number | boolean | null | undefined;
|
|
8
|
+
|
|
9
|
+
function appendToken(parts: string[], value: GraphSyncToken): void {
|
|
10
|
+
const encoded =
|
|
11
|
+
value === undefined
|
|
12
|
+
? 'undefined'
|
|
13
|
+
: value === null
|
|
14
|
+
? 'null'
|
|
15
|
+
: `${typeof value}:${String(value)}`;
|
|
16
|
+
parts.push(`${encoded.length}:${encoded}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function appendTransformRegistrySignature(
|
|
20
|
+
parts: string[],
|
|
21
|
+
transforms: ValueTransformRegistry,
|
|
22
|
+
): void {
|
|
23
|
+
const definitions = transforms
|
|
24
|
+
.list()
|
|
25
|
+
.slice()
|
|
26
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
27
|
+
appendToken(parts, definitions.length);
|
|
28
|
+
for (const definition of definitions) {
|
|
29
|
+
appendToken(parts, definition.id);
|
|
30
|
+
appendToken(parts, definition.label);
|
|
31
|
+
appendToken(parts, definition.description);
|
|
32
|
+
appendToken(parts, definition.category);
|
|
33
|
+
appendToken(parts, definition.outputType);
|
|
34
|
+
appendToken(parts, definition.inputTypes?.length ?? 0);
|
|
35
|
+
for (const inputType of definition.inputTypes ?? []) {
|
|
36
|
+
appendToken(parts, inputType);
|
|
37
|
+
}
|
|
38
|
+
appendToken(parts, definition.optionFields?.length ?? 0);
|
|
39
|
+
for (const option of definition.optionFields ?? []) {
|
|
40
|
+
appendToken(parts, option.key);
|
|
41
|
+
appendToken(parts, option.label);
|
|
42
|
+
appendToken(parts, option.kind);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createTransformRegistrySignature(transforms: ValueTransformRegistry): string {
|
|
48
|
+
const parts: string[] = [];
|
|
49
|
+
appendTransformRegistrySignature(parts, transforms);
|
|
50
|
+
return parts.join('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function appendSelection(parts: string[], selection: FieldRemapSelection): void {
|
|
54
|
+
appendToken(parts, selection?.kind);
|
|
55
|
+
if (!selection) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (selection.kind === 'edge' || selection.kind === 'transformStep') {
|
|
59
|
+
appendToken(parts, selection.edgeId);
|
|
60
|
+
appendToken(parts, selection.kind === 'transformStep' ? selection.stepIndex : undefined);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
appendToken(parts, selection.kind === 'draft' ? selection.localId : selection.operatorId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Stable signature for the controlled values copied into XYFlow's internal node store.
|
|
68
|
+
* This intentionally lists display fields instead of serializing arbitrary props.
|
|
69
|
+
*/
|
|
70
|
+
export function createFieldRemapGraphSyncKey(input: {
|
|
71
|
+
readonly nodes: readonly Node<FieldRemapFlowNodeData>[];
|
|
72
|
+
readonly edges: readonly Edge<FieldRemapFlowEdgeData>[];
|
|
73
|
+
readonly selection: FieldRemapSelection;
|
|
74
|
+
readonly transformRegistrySignature: string;
|
|
75
|
+
}): string {
|
|
76
|
+
const parts: string[] = [];
|
|
77
|
+
appendToken(parts, input.nodes.length);
|
|
78
|
+
for (const node of input.nodes) {
|
|
79
|
+
appendToken(parts, node.id);
|
|
80
|
+
appendToken(parts, node.type);
|
|
81
|
+
appendToken(parts, node.position.x);
|
|
82
|
+
appendToken(parts, node.position.y);
|
|
83
|
+
appendToken(parts, node.selected);
|
|
84
|
+
|
|
85
|
+
const data = node.data;
|
|
86
|
+
appendToken(parts, data.kind);
|
|
87
|
+
if (data.kind === 'source-object' || data.kind === 'target-object') {
|
|
88
|
+
appendToken(parts, data.title);
|
|
89
|
+
appendToken(parts, data.schemaRole);
|
|
90
|
+
appendToken(parts, data.ports.length);
|
|
91
|
+
for (const port of data.ports) {
|
|
92
|
+
appendToken(parts, port.fieldId);
|
|
93
|
+
appendToken(parts, port.label);
|
|
94
|
+
appendToken(parts, port.dataType);
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (data.kind === 'transform') {
|
|
99
|
+
appendToken(parts, data.mappingEdgeId);
|
|
100
|
+
appendToken(parts, data.stepIndex);
|
|
101
|
+
appendToken(parts, data.transformId);
|
|
102
|
+
appendToken(parts, data.label);
|
|
103
|
+
appendToken(parts, data.selected);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (data.kind === 'draft-transform') {
|
|
107
|
+
appendToken(parts, data.localId);
|
|
108
|
+
appendToken(parts, data.transformId);
|
|
109
|
+
appendToken(parts, data.label);
|
|
110
|
+
appendToken(parts, data.sourceFieldId);
|
|
111
|
+
appendToken(parts, data.targetSlotId);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
appendToken(parts, data.operatorId);
|
|
115
|
+
appendToken(parts, data.label);
|
|
116
|
+
if (data.kind === 'combine-operator') {
|
|
117
|
+
appendToken(parts, data.inputFieldIds.length);
|
|
118
|
+
for (const fieldId of data.inputFieldIds) {
|
|
119
|
+
appendToken(parts, fieldId);
|
|
120
|
+
}
|
|
121
|
+
appendToken(parts, data.outputSlotId);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
appendToken(parts, data.inputFieldId);
|
|
125
|
+
appendToken(parts, data.outputSlotIds.length);
|
|
126
|
+
for (const slotId of data.outputSlotIds) {
|
|
127
|
+
appendToken(parts, slotId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
appendToken(parts, input.edges.length);
|
|
132
|
+
for (const edge of input.edges) {
|
|
133
|
+
appendToken(parts, edge.id);
|
|
134
|
+
appendToken(parts, edge.type);
|
|
135
|
+
appendToken(parts, edge.source);
|
|
136
|
+
appendToken(parts, edge.sourceHandle);
|
|
137
|
+
appendToken(parts, edge.target);
|
|
138
|
+
appendToken(parts, edge.targetHandle);
|
|
139
|
+
appendToken(parts, edge.selected);
|
|
140
|
+
appendToken(parts, edge.data?.mappingEdgeId);
|
|
141
|
+
appendToken(parts, edge.data?.draftLocalId);
|
|
142
|
+
appendToken(parts, edge.data?.operatorId);
|
|
143
|
+
appendToken(parts, edge.data?.segment);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
appendSelection(parts, input.selection);
|
|
147
|
+
appendToken(parts, input.transformRegistrySignature);
|
|
148
|
+
return parts.join('');
|
|
149
|
+
}
|
package/src/field-remap/flow.tsx
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
type FieldRemapTargetObjectNodeData,
|
|
61
61
|
type FieldRemapTransformNodeData,
|
|
62
62
|
} from './flow-adapter.js';
|
|
63
|
+
import { createFieldRemapGraphSyncKey, createTransformRegistrySignature } from './flow-sync-key.js';
|
|
63
64
|
import {
|
|
64
65
|
addTransformStepToEdge,
|
|
65
66
|
bindDraftSource,
|
|
@@ -414,6 +415,7 @@ function FieldRemapFlowCanvas({
|
|
|
414
415
|
const first = transforms.list().find((definition) => definition.id !== 'identity');
|
|
415
416
|
return first?.id ?? '';
|
|
416
417
|
});
|
|
418
|
+
const transformRegistrySignature = createTransformRegistrySignature(transforms);
|
|
417
419
|
|
|
418
420
|
const graph = useMemo(
|
|
419
421
|
() =>
|
|
@@ -427,7 +429,17 @@ function FieldRemapFlowCanvas({
|
|
|
427
429
|
targetTitle,
|
|
428
430
|
drafts,
|
|
429
431
|
}),
|
|
430
|
-
[
|
|
432
|
+
[
|
|
433
|
+
sources,
|
|
434
|
+
targets,
|
|
435
|
+
edges,
|
|
436
|
+
transforms,
|
|
437
|
+
operators,
|
|
438
|
+
sourceTitle,
|
|
439
|
+
targetTitle,
|
|
440
|
+
drafts,
|
|
441
|
+
transformRegistrySignature,
|
|
442
|
+
],
|
|
431
443
|
);
|
|
432
444
|
|
|
433
445
|
const nodesWithSelection = useMemo(
|
|
@@ -461,22 +473,15 @@ function FieldRemapFlowCanvas({
|
|
|
461
473
|
const [nodes, setNodes, onNodesChange] = useNodesState(nodesWithSelection);
|
|
462
474
|
const [flowEdges, setFlowEdges, onFlowEdgesChange] = useEdgesState(graph.edges);
|
|
463
475
|
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
const graphSyncKey =
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
`${edge.id}:${edge.source}:${edge.target}:${edge.sourceHandle ?? ''}:${edge.targetHandle ?? ''}`,
|
|
474
|
-
),
|
|
475
|
-
selection ? JSON.stringify(selection) : '',
|
|
476
|
-
...drafts.map((draft) => draft.localId),
|
|
477
|
-
].join('|'),
|
|
478
|
-
[drafts, graph.edges, graph.nodes, selection],
|
|
479
|
-
);
|
|
476
|
+
// Depending directly on `nodesWithSelection` (a new array after each graph
|
|
477
|
+
// calculation) re-enters XYFlow's StoreUpdater. The explicit signature keeps
|
|
478
|
+
// that loop guard while still tracking every value copied into rendered nodes.
|
|
479
|
+
const graphSyncKey = createFieldRemapGraphSyncKey({
|
|
480
|
+
nodes: nodesWithSelection,
|
|
481
|
+
edges: graph.edges,
|
|
482
|
+
selection,
|
|
483
|
+
transformRegistrySignature,
|
|
484
|
+
});
|
|
480
485
|
const nodesWithSelectionRef = useRef(nodesWithSelection);
|
|
481
486
|
const graphEdgesRef = useRef(graph.edges);
|
|
482
487
|
nodesWithSelectionRef.current = nodesWithSelection;
|
|
@@ -611,7 +616,18 @@ function FieldRemapFlowCanvas({
|
|
|
611
616
|
onEdgesChange([...withoutTarget, result.edge]);
|
|
612
617
|
setSelection({ kind: 'edge', edgeId: result.edge.id });
|
|
613
618
|
},
|
|
614
|
-
[
|
|
619
|
+
[
|
|
620
|
+
connectionContext,
|
|
621
|
+
drafts,
|
|
622
|
+
edges,
|
|
623
|
+
onEdgesChange,
|
|
624
|
+
onOperatorsChange,
|
|
625
|
+
operators,
|
|
626
|
+
setSelection,
|
|
627
|
+
sources,
|
|
628
|
+
targets,
|
|
629
|
+
transforms,
|
|
630
|
+
],
|
|
615
631
|
);
|
|
616
632
|
|
|
617
633
|
const onEdgesDelete = useCallback(
|
|
@@ -694,7 +710,7 @@ function FieldRemapFlowCanvas({
|
|
|
694
710
|
stepIndex: data.stepIndex,
|
|
695
711
|
});
|
|
696
712
|
},
|
|
697
|
-
[edges, onEdgesChange, setSelection],
|
|
713
|
+
[edges, onEdgesChange, onOperatorsChange, operators, selection, setSelection],
|
|
698
714
|
);
|
|
699
715
|
|
|
700
716
|
const onKeyDown = useCallback(
|
|
@@ -31,12 +31,6 @@ export interface FieldRemapSampleDefinition {
|
|
|
31
31
|
readonly operators?: readonly MappingOperator[];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/** Alias kept so older URIs still open a known sample. */
|
|
35
|
-
export const FIELD_REMAP_LEGACY_SAMPLE_ALIASES: Readonly<Record<string, FieldRemapSampleId>> = {
|
|
36
|
-
'interactive-bindings': 'nested-ab',
|
|
37
|
-
't-order-invoice': 't-event-time',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
34
|
export const FIELD_REMAP_SAMPLES: readonly FieldRemapSampleDefinition[] = [
|
|
41
35
|
{
|
|
42
36
|
id: 'nested-ab',
|
|
@@ -386,14 +380,7 @@ export function isFieldRemapSampleId(value: string): value is FieldRemapSampleId
|
|
|
386
380
|
}
|
|
387
381
|
|
|
388
382
|
export function resolveFieldRemapSampleId(value: string | undefined): FieldRemapSampleId {
|
|
389
|
-
if (
|
|
390
|
-
return 'nested-ab';
|
|
391
|
-
}
|
|
392
|
-
const aliased = FIELD_REMAP_LEGACY_SAMPLE_ALIASES[value];
|
|
393
|
-
if (aliased) {
|
|
394
|
-
return aliased;
|
|
395
|
-
}
|
|
396
|
-
if (isFieldRemapSampleId(value)) {
|
|
383
|
+
if (value && isFieldRemapSampleId(value)) {
|
|
397
384
|
return value;
|
|
398
385
|
}
|
|
399
386
|
return 'nested-ab';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const SAMPLE_FIELD_REMAP_VIEW_RENDER_KIND =
|
|
2
2
|
'workbench-kit.samples.field-remap.view' as const;
|
|
3
|
+
export const SAMPLE_FIELD_REMAP_VIEW_ID = 'workbench-kit.samples.field-remap.panel' as const;
|
|
3
4
|
|
|
4
5
|
export interface SampleFieldRemapViewRenderData {
|
|
5
6
|
readonly kind: typeof SAMPLE_FIELD_REMAP_VIEW_RENDER_KIND;
|
package/src/field-remap/view.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSX } from 'react';
|
|
2
2
|
import { WorkbenchActionSidebar } from '@workbench-kit/react/layout';
|
|
3
|
+
import type { ViewHostFactory } from '@workbench-kit/workbench-core';
|
|
3
4
|
|
|
4
5
|
import './view.css';
|
|
5
6
|
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
} from './samples.js';
|
|
11
12
|
import {
|
|
12
13
|
isSampleFieldRemapViewRenderData,
|
|
14
|
+
SAMPLE_FIELD_REMAP_VIEW_ID,
|
|
13
15
|
SAMPLE_FIELD_REMAP_VIEW_RENDER_KIND,
|
|
14
16
|
type SampleFieldRemapViewRenderData,
|
|
15
17
|
} from './view-data.js';
|
|
@@ -43,6 +45,23 @@ export interface SampleFieldRemapViewProps {
|
|
|
43
45
|
readonly className?: string | undefined;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
export const SAMPLE_FIELD_REMAP_VIEW_HOST_FACTORY: ViewHostFactory = {
|
|
49
|
+
id: 'workbench-kit.samples.field-remap.react-view-host',
|
|
50
|
+
priority: 100,
|
|
51
|
+
canCreate: ({ viewId }) => viewId === SAMPLE_FIELD_REMAP_VIEW_ID,
|
|
52
|
+
create: ({ provider }) => {
|
|
53
|
+
const host = provider.resolveViewHost();
|
|
54
|
+
const render = host.render.bind(host);
|
|
55
|
+
|
|
56
|
+
host.render = () => {
|
|
57
|
+
const renderData = render();
|
|
58
|
+
return isSampleFieldRemapViewRenderData(renderData) ? <SampleFieldRemapView /> : renderData;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return host;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
46
65
|
/** Sidebar: catalog of field-remap / table-mapping samples. */
|
|
47
66
|
export function SampleFieldRemapView({ className }: SampleFieldRemapViewProps = {}): JSX.Element {
|
|
48
67
|
const editorService = useEditorService();
|
package/src/index.ts
CHANGED
|
@@ -80,7 +80,11 @@ export {
|
|
|
80
80
|
isBuiltinEditorMarkdownPreviewRenderData,
|
|
81
81
|
type BuiltinEditorMarkdownPreviewRenderData,
|
|
82
82
|
} from './editor/markdown-document-view-data.js';
|
|
83
|
-
export {
|
|
83
|
+
export {
|
|
84
|
+
SAMPLE_JDW_LAB_VIEW_HOST_FACTORY,
|
|
85
|
+
SampleJdwLabView,
|
|
86
|
+
type SampleJdwLabViewProps,
|
|
87
|
+
} from './jdw/lab-view.js';
|
|
84
88
|
export { JdwWidgetFormView, type JdwWidgetFormViewProps } from './jdw/widget-form-view.js';
|
|
85
89
|
export { JdwWidgetPreviewView, type JdwWidgetPreviewViewProps } from './jdw/widget-preview-view.js';
|
|
86
90
|
export {
|
|
@@ -186,6 +190,7 @@ export {
|
|
|
186
190
|
type FieldRemapSampleId,
|
|
187
191
|
} from './field-remap/samples.js';
|
|
188
192
|
export {
|
|
193
|
+
SAMPLE_FIELD_REMAP_VIEW_HOST_FACTORY,
|
|
189
194
|
SampleFieldRemapView,
|
|
190
195
|
buildFieldRemapEditorUri,
|
|
191
196
|
parseFieldRemapEditorSampleId,
|
package/src/jdw/lab-view-data.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const SAMPLE_JDW_LAB_VIEW_RENDER_KIND = 'workbench-kit.samples.jdw.view' as const;
|
|
2
|
+
export const SAMPLE_JDW_LAB_VIEW_ID = 'workbench-kit.samples.jdw.panel' as const;
|
|
2
3
|
|
|
3
4
|
export interface SampleJdwLabViewRenderData {
|
|
4
5
|
readonly kind: typeof SAMPLE_JDW_LAB_VIEW_RENDER_KIND;
|
package/src/jdw/lab-view.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WorkbenchActionSidebar } from '@workbench-kit/react/layout';
|
|
2
|
+
import type { ViewHostFactory } from '@workbench-kit/workbench-core';
|
|
2
3
|
|
|
3
4
|
import './lab-view.css';
|
|
4
5
|
|
|
@@ -7,6 +8,7 @@ import { useActiveEditorTab } from '../editor/use-editor.js';
|
|
|
7
8
|
import { useActiveWorkspacePath } from '../explorer/use-active-workspace-path.js';
|
|
8
9
|
import {
|
|
9
10
|
isSampleJdwLabViewRenderData,
|
|
11
|
+
SAMPLE_JDW_LAB_VIEW_ID,
|
|
10
12
|
SAMPLE_JDW_LAB_VIEW_RENDER_KIND,
|
|
11
13
|
type SampleJdwLabViewRenderData,
|
|
12
14
|
} from './lab-view-data.js';
|
|
@@ -24,6 +26,31 @@ export interface SampleJdwLabViewProps {
|
|
|
24
26
|
readonly widgetTreePath?: string | undefined;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
export const SAMPLE_JDW_LAB_VIEW_HOST_FACTORY: ViewHostFactory = {
|
|
30
|
+
id: 'workbench-kit.samples.jdw.react-view-host',
|
|
31
|
+
priority: 100,
|
|
32
|
+
canCreate: ({ viewId }) => viewId === SAMPLE_JDW_LAB_VIEW_ID,
|
|
33
|
+
create: ({ provider }) => {
|
|
34
|
+
const host = provider.resolveViewHost();
|
|
35
|
+
const render = host.render.bind(host);
|
|
36
|
+
|
|
37
|
+
host.render = () => {
|
|
38
|
+
const renderData = render();
|
|
39
|
+
|
|
40
|
+
return isSampleJdwLabViewRenderData(renderData) ? (
|
|
41
|
+
<SampleJdwLabView
|
|
42
|
+
templateJdwPath={renderData.templateJdwPath}
|
|
43
|
+
widgetTreePath={renderData.widgetTreePath}
|
|
44
|
+
/>
|
|
45
|
+
) : (
|
|
46
|
+
renderData
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return host;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
27
54
|
function pathMatches(activePath: string | undefined, candidate: string): boolean {
|
|
28
55
|
if (!activePath) {
|
|
29
56
|
return false;
|
|
@@ -3,36 +3,27 @@ import {
|
|
|
3
3
|
type WorkbenchKeybindingDefinition,
|
|
4
4
|
} from '@workbench-kit/workbench-config';
|
|
5
5
|
import {
|
|
6
|
-
createBrowserWorkbenchStorage,
|
|
7
6
|
type WorkbenchStorageReader,
|
|
8
7
|
type WorkbenchStorageWriter,
|
|
9
8
|
} from '@workbench-kit/workbench-core';
|
|
10
9
|
|
|
10
|
+
import {
|
|
11
|
+
readLocalJsonStorage,
|
|
12
|
+
resolveLocalWorkbenchStorage,
|
|
13
|
+
writeLocalJsonStorage,
|
|
14
|
+
} from '../storage/local-json-storage.js';
|
|
15
|
+
|
|
11
16
|
export const DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY = 'workbench-kit/.workbench/keybindings';
|
|
12
17
|
|
|
13
18
|
export function isWorkbenchKeybindingPersistenceAvailable(): boolean {
|
|
14
|
-
return
|
|
19
|
+
return resolveLocalWorkbenchStorage() !== undefined;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
export function readPersistedKeybindingOverrides(
|
|
18
23
|
storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
|
|
19
24
|
storage?: WorkbenchStorageReader,
|
|
20
25
|
): readonly WorkbenchKeybindingDefinition[] {
|
|
21
|
-
|
|
22
|
-
if (!resolvedStorage) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const raw = resolvedStorage.getItem(storageKey);
|
|
28
|
-
if (!raw) {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return parseWorkbenchKeybindingsConfig(JSON.parse(raw) as unknown);
|
|
33
|
-
} catch {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
26
|
+
return readLocalJsonStorage(storageKey, parseWorkbenchKeybindingsConfig, () => [], storage);
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
export function writePersistedKeybindingOverrides(
|
|
@@ -40,10 +31,5 @@ export function writePersistedKeybindingOverrides(
|
|
|
40
31
|
storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
|
|
41
32
|
storage?: WorkbenchStorageWriter,
|
|
42
33
|
): void {
|
|
43
|
-
|
|
44
|
-
if (!resolvedStorage) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
resolvedStorage.setItem(storageKey, JSON.stringify(overrides, null, 2));
|
|
34
|
+
writeLocalJsonStorage(storageKey, overrides, storage, { errorMode: 'throw' });
|
|
49
35
|
}
|
|
@@ -3,37 +3,28 @@ import {
|
|
|
3
3
|
type WorkbenchSettingsConfig,
|
|
4
4
|
} from '@workbench-kit/workbench-config';
|
|
5
5
|
import {
|
|
6
|
-
createBrowserWorkbenchStorage,
|
|
7
6
|
type WorkbenchStorageReader,
|
|
8
7
|
type WorkbenchStorageWriter,
|
|
9
8
|
} from '@workbench-kit/workbench-core';
|
|
10
9
|
|
|
10
|
+
import {
|
|
11
|
+
readLocalJsonStorage,
|
|
12
|
+
resolveLocalWorkbenchStorage,
|
|
13
|
+
writeLocalJsonStorage,
|
|
14
|
+
} from '../storage/local-json-storage.js';
|
|
15
|
+
|
|
11
16
|
export const DEFAULT_WORKBENCH_LOCAL_PREFERENCE_STORAGE_KEY =
|
|
12
17
|
'workbench-kit/.workbench/settings.local';
|
|
13
18
|
|
|
14
19
|
export function isWorkbenchLocalPreferencePersistenceAvailable(): boolean {
|
|
15
|
-
return
|
|
20
|
+
return resolveLocalWorkbenchStorage() !== undefined;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export function readPersistedLocalPreferences(
|
|
19
24
|
storageKey = DEFAULT_WORKBENCH_LOCAL_PREFERENCE_STORAGE_KEY,
|
|
20
25
|
storage?: WorkbenchStorageReader,
|
|
21
26
|
): WorkbenchSettingsConfig {
|
|
22
|
-
|
|
23
|
-
if (!resolvedStorage) {
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const raw = resolvedStorage.getItem(storageKey);
|
|
29
|
-
if (!raw) {
|
|
30
|
-
return {};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return parseWorkbenchSettingsConfig(JSON.parse(raw) as unknown);
|
|
34
|
-
} catch {
|
|
35
|
-
return {};
|
|
36
|
-
}
|
|
27
|
+
return readLocalJsonStorage(storageKey, parseWorkbenchSettingsConfig, () => ({}), storage);
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
export function writePersistedLocalPreferences(
|
|
@@ -41,10 +32,5 @@ export function writePersistedLocalPreferences(
|
|
|
41
32
|
storageKey = DEFAULT_WORKBENCH_LOCAL_PREFERENCE_STORAGE_KEY,
|
|
42
33
|
storage?: WorkbenchStorageWriter,
|
|
43
34
|
): void {
|
|
44
|
-
|
|
45
|
-
if (!resolvedStorage) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
resolvedStorage.setItem(storageKey, JSON.stringify(values, null, 2));
|
|
35
|
+
writeLocalJsonStorage(storageKey, values, storage, { errorMode: 'throw' });
|
|
50
36
|
}
|
package/src/shell/provider.tsx
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
type WorkbenchEditorSavePort,
|
|
35
35
|
type WorkbenchExtensionDescription,
|
|
36
36
|
type WorkbenchLayoutStateInput,
|
|
37
|
+
type ViewHostFactory,
|
|
37
38
|
} from '@workbench-kit/workbench-core';
|
|
38
39
|
import {
|
|
39
40
|
ContextKeyService,
|
|
@@ -140,6 +141,8 @@ export interface WorkbenchProviderProps {
|
|
|
140
141
|
persistLayout?: boolean;
|
|
141
142
|
persistLocalPreferences?: boolean;
|
|
142
143
|
userCommands?: readonly WorkbenchUserCommandDefinition[];
|
|
144
|
+
/** Host-owned framework adapters registered before extension activation. */
|
|
145
|
+
viewHostFactories?: readonly ViewHostFactory[] | undefined;
|
|
143
146
|
workspaceHostPort?: WorkbenchWorkspaceHostPort | undefined;
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -212,6 +215,7 @@ export function WorkbenchProvider({
|
|
|
212
215
|
persistLayout,
|
|
213
216
|
persistLocalPreferences,
|
|
214
217
|
userCommands = [],
|
|
218
|
+
viewHostFactories,
|
|
215
219
|
workspaceHostPort,
|
|
216
220
|
}: WorkbenchProviderProps) {
|
|
217
221
|
const hostAvailableExtensions = availableExtensions ?? DEFAULT_AVAILABLE_EXTENSIONS;
|
|
@@ -335,6 +339,9 @@ export function WorkbenchProvider({
|
|
|
335
339
|
|
|
336
340
|
const services = useMemo<WorkbenchProviderServices>(() => {
|
|
337
341
|
const extensionRegistry = new ExtensionRegistry();
|
|
342
|
+
const viewHostFactoryDisposables = (viewHostFactories ?? []).map((factory) =>
|
|
343
|
+
extensionRegistry.viewHostFactories.register(factory),
|
|
344
|
+
);
|
|
338
345
|
const editorDocumentViewProviders = extensionRegistry.editorDocumentViews;
|
|
339
346
|
const editorDocumentViewProviderDisposables = [
|
|
340
347
|
...(includeDefaultDocumentViewProviders === false
|
|
@@ -433,6 +440,9 @@ export function WorkbenchProvider({
|
|
|
433
440
|
for (const disposable of editorDocumentViewProviderDisposables) {
|
|
434
441
|
disposable.dispose();
|
|
435
442
|
}
|
|
443
|
+
for (const disposable of viewHostFactoryDisposables) {
|
|
444
|
+
disposable.dispose();
|
|
445
|
+
}
|
|
436
446
|
extensionRegistry.dispose();
|
|
437
447
|
layoutService.dispose();
|
|
438
448
|
preferenceService.dispose();
|
|
@@ -465,6 +475,7 @@ export function WorkbenchProvider({
|
|
|
465
475
|
resolvedInitialLayout,
|
|
466
476
|
resolvedInitialLocalPreferences,
|
|
467
477
|
userCommands,
|
|
478
|
+
viewHostFactories,
|
|
468
479
|
workspaceHostPort,
|
|
469
480
|
]);
|
|
470
481
|
|
|
@@ -473,7 +484,11 @@ export function WorkbenchProvider({
|
|
|
473
484
|
return undefined;
|
|
474
485
|
}
|
|
475
486
|
|
|
476
|
-
const disposable = services.layoutService.onDidChangeLayout(({ state }) => {
|
|
487
|
+
const disposable = services.layoutService.onDidChangeLayout(({ state, transient }) => {
|
|
488
|
+
if (transient) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
477
492
|
writePersistedWorkbenchLayout(state, layoutStorageKey, layoutStorage);
|
|
478
493
|
});
|
|
479
494
|
|
package/src/shell/shell.tsx
CHANGED
|
@@ -70,7 +70,12 @@ import {
|
|
|
70
70
|
createWorkbenchShellActivityItems,
|
|
71
71
|
} from './model.js';
|
|
72
72
|
import { mergeWorkbenchStatusSections } from '../workbench/status-sections.js';
|
|
73
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
getVisibleWorkbenchViews,
|
|
75
|
+
renderDefaultAuxiliarySidebar,
|
|
76
|
+
renderDefaultBottomPanel,
|
|
77
|
+
renderDefaultPrimarySidebar,
|
|
78
|
+
} from './view-host.js';
|
|
74
79
|
import { WorkbenchShellTitleBarLayoutControls } from './titlebar-layout-controls.js';
|
|
75
80
|
import { WorkbenchProfileModal, type WorkbenchProfileInput } from '../workbench/profile-modal.js';
|
|
76
81
|
import { useContextKeyRevision } from '../commands/use-context-key-revision.js';
|
|
@@ -205,6 +210,10 @@ export function WorkbenchShell({
|
|
|
205
210
|
preferenceService,
|
|
206
211
|
} = useWorkbench();
|
|
207
212
|
const contextKeyRevision = useContextKeyRevision(contextKeyService);
|
|
213
|
+
const contextKeySnapshot = useMemo(
|
|
214
|
+
() => contextKeyService.createSnapshot(),
|
|
215
|
+
[contextKeyRevision, contextKeyService],
|
|
216
|
+
);
|
|
208
217
|
const forceRender = useForceRender();
|
|
209
218
|
const [preferenceRevision, bumpPreferenceRevision] = useReducer((count: number) => count + 1, 0);
|
|
210
219
|
const [isHelpOpen, setHelpOpen] = useState(false);
|
|
@@ -247,9 +256,9 @@ export function WorkbenchShell({
|
|
|
247
256
|
() =>
|
|
248
257
|
filterActivitiesByWhenClause(
|
|
249
258
|
extensionRegistry.activities.getActivities(),
|
|
250
|
-
|
|
259
|
+
contextKeySnapshot,
|
|
251
260
|
),
|
|
252
|
-
[
|
|
261
|
+
[contextKeySnapshot, extensionRegistry],
|
|
253
262
|
);
|
|
254
263
|
const activityItems = sortActivityBarItems(
|
|
255
264
|
createWorkbenchShellActivityItems({
|
|
@@ -429,20 +438,44 @@ export function WorkbenchShell({
|
|
|
429
438
|
return;
|
|
430
439
|
}
|
|
431
440
|
|
|
432
|
-
for (const view of
|
|
441
|
+
for (const view of getVisibleWorkbenchViews(
|
|
442
|
+
extensionRegistry,
|
|
443
|
+
activeViewContainerId,
|
|
444
|
+
contextKeySnapshot,
|
|
445
|
+
)) {
|
|
433
446
|
void extensionRegistry.activateView(view.id).then(forceRender);
|
|
434
447
|
}
|
|
435
|
-
}, [activeViewContainerId, extensionRegistry, forceRender]);
|
|
448
|
+
}, [activeViewContainerId, contextKeySnapshot, extensionRegistry, forceRender]);
|
|
436
449
|
|
|
437
450
|
useEffect(() => {
|
|
438
451
|
if (!activePanelViewContainerId) {
|
|
439
452
|
return;
|
|
440
453
|
}
|
|
441
454
|
|
|
442
|
-
for (const view of
|
|
455
|
+
for (const view of getVisibleWorkbenchViews(
|
|
456
|
+
extensionRegistry,
|
|
457
|
+
activePanelViewContainerId,
|
|
458
|
+
contextKeySnapshot,
|
|
459
|
+
)) {
|
|
443
460
|
void extensionRegistry.activateView(view.id).then(forceRender);
|
|
444
461
|
}
|
|
445
|
-
}, [activePanelViewContainerId, extensionRegistry, forceRender]);
|
|
462
|
+
}, [activePanelViewContainerId, contextKeySnapshot, extensionRegistry, forceRender]);
|
|
463
|
+
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (!layout.auxiliaryBar.visible) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
for (const container of extensionRegistry.views.getViewContainers('auxiliarybar')) {
|
|
470
|
+
for (const view of getVisibleWorkbenchViews(
|
|
471
|
+
extensionRegistry,
|
|
472
|
+
container.id,
|
|
473
|
+
contextKeySnapshot,
|
|
474
|
+
)) {
|
|
475
|
+
void extensionRegistry.activateView(view.id).then(forceRender);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}, [contextKeySnapshot, extensionRegistry, forceRender, layout.auxiliaryBar.visible]);
|
|
446
479
|
|
|
447
480
|
useEffect(() => {
|
|
448
481
|
if (extensionRegistry.capabilityRegistry.has(WORKBENCH_SETTINGS_CAPABILITY_ID)) {
|
|
@@ -601,6 +634,7 @@ export function WorkbenchShell({
|
|
|
601
634
|
activeViewContainerId,
|
|
602
635
|
catalogUrl,
|
|
603
636
|
catalogTrustPolicy,
|
|
637
|
+
contextKeySnapshot,
|
|
604
638
|
),
|
|
605
639
|
onSizePxChange: (sizePx) => {
|
|
606
640
|
layoutService.setSideBarSizePercent(
|
|
@@ -613,13 +647,19 @@ export function WorkbenchShell({
|
|
|
613
647
|
}}
|
|
614
648
|
auxiliarySidebar={{
|
|
615
649
|
isVisible: layout.auxiliaryBar.visible,
|
|
616
|
-
node:
|
|
650
|
+
node: renderDefaultAuxiliarySidebar(
|
|
651
|
+
extensionRegistry,
|
|
652
|
+
contextKeySnapshot,
|
|
653
|
+
catalogUrl,
|
|
654
|
+
catalogTrustPolicy,
|
|
655
|
+
),
|
|
617
656
|
}}
|
|
618
657
|
bottomPanel={{
|
|
619
658
|
isVisible: layout.panel.visible,
|
|
620
659
|
node: renderDefaultBottomPanel(extensionRegistry, activePanelViewContainerId, {
|
|
621
660
|
catalogTrustPolicy,
|
|
622
661
|
catalogUrl,
|
|
662
|
+
contextKeys: contextKeySnapshot,
|
|
623
663
|
onActiveViewContainerChange: (viewContainerId) => {
|
|
624
664
|
layoutService.setActivePanelViewContainer(viewContainerId);
|
|
625
665
|
if (!layout.panel.visible) {
|
package/src/shell/view-host.tsx
CHANGED
|
@@ -5,7 +5,12 @@ import type {
|
|
|
5
5
|
ViewHost,
|
|
6
6
|
ViewHostFactoryRegistry,
|
|
7
7
|
ViewProvider,
|
|
8
|
+
WorkbenchViewContribution,
|
|
8
9
|
} from '@workbench-kit/workbench-core';
|
|
10
|
+
import {
|
|
11
|
+
filterWorkbenchContributionsByWhenClause,
|
|
12
|
+
type WorkbenchContextKeySnapshot,
|
|
13
|
+
} from '@workbench-kit/platform';
|
|
9
14
|
|
|
10
15
|
import { BuiltinChatView } from '../chat/view.js';
|
|
11
16
|
import { isBuiltinChatViewRenderData } from '../chat/view-data.js';
|
|
@@ -15,10 +20,6 @@ import { BuiltinExplorerView } from '../explorer/view.js';
|
|
|
15
20
|
import { isBuiltinExplorerViewRenderData } from '../explorer/view-data.js';
|
|
16
21
|
import { BuiltinExtensionsView } from '../extensions/view.js';
|
|
17
22
|
import { isBuiltinExtensionsViewRenderData } from '../extensions/view-data.js';
|
|
18
|
-
import { SampleJdwLabView } from '../jdw/lab-view.js';
|
|
19
|
-
import { isSampleJdwLabViewRenderData } from '../jdw/lab-view-data.js';
|
|
20
|
-
import { SampleFieldRemapView } from '../field-remap/view.js';
|
|
21
|
-
import { isSampleFieldRemapViewRenderData } from '../field-remap/view-data.js';
|
|
22
23
|
import { BuiltinSearchView } from '../explorer/search-view.js';
|
|
23
24
|
import { isBuiltinSearchViewRenderData } from '../explorer/search-view-data.js';
|
|
24
25
|
|
|
@@ -27,10 +28,11 @@ export function renderDefaultPrimarySidebar(
|
|
|
27
28
|
activeViewContainerId: string | undefined,
|
|
28
29
|
catalogUrl?: string | undefined,
|
|
29
30
|
catalogTrustPolicy?: ExtensionCatalogTrustPolicy | undefined,
|
|
31
|
+
contextKeys: WorkbenchContextKeySnapshot = {},
|
|
30
32
|
) {
|
|
31
33
|
const views = activeViewContainerId
|
|
32
|
-
? extensionRegistry
|
|
33
|
-
: extensionRegistry.views.getViews();
|
|
34
|
+
? getVisibleWorkbenchViews(extensionRegistry, activeViewContainerId, contextKeys)
|
|
35
|
+
: filterWorkbenchContributionsByWhenClause(extensionRegistry.views.getViews(), contextKeys);
|
|
34
36
|
if (views.length === 0) {
|
|
35
37
|
return <aside aria-label="Primary sidebar" />;
|
|
36
38
|
}
|
|
@@ -38,20 +40,29 @@ export function renderDefaultPrimarySidebar(
|
|
|
38
40
|
return (
|
|
39
41
|
<aside
|
|
40
42
|
aria-label="Primary sidebar"
|
|
41
|
-
className="workbench-primary-side-bar shell-react-
|
|
43
|
+
className="workbench-primary-side-bar shell-react-sidebar-host"
|
|
42
44
|
>
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
{renderWorkbenchViews(extensionRegistry, views, { catalogTrustPolicy, catalogUrl })}
|
|
46
|
+
</aside>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function renderDefaultAuxiliarySidebar(
|
|
51
|
+
extensionRegistry: Pick<ExtensionRegistry, 'viewHostFactories' | 'views'>,
|
|
52
|
+
contextKeys: WorkbenchContextKeySnapshot,
|
|
53
|
+
catalogUrl?: string | undefined,
|
|
54
|
+
catalogTrustPolicy?: ExtensionCatalogTrustPolicy | undefined,
|
|
55
|
+
) {
|
|
56
|
+
const views = extensionRegistry.views
|
|
57
|
+
.getViewContainers('auxiliarybar')
|
|
58
|
+
.flatMap((container) => getVisibleWorkbenchViews(extensionRegistry, container.id, contextKeys));
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<aside
|
|
62
|
+
aria-label="Secondary Side Bar"
|
|
63
|
+
className="workbench-auxiliary-side-bar shell-react-sidebar-host"
|
|
64
|
+
>
|
|
65
|
+
{renderWorkbenchViews(extensionRegistry, views, { catalogTrustPolicy, catalogUrl })}
|
|
55
66
|
</aside>
|
|
56
67
|
);
|
|
57
68
|
}
|
|
@@ -62,6 +73,7 @@ export function renderDefaultBottomPanel(
|
|
|
62
73
|
options: {
|
|
63
74
|
catalogTrustPolicy?: ExtensionCatalogTrustPolicy | undefined;
|
|
64
75
|
catalogUrl?: string | undefined;
|
|
76
|
+
contextKeys?: WorkbenchContextKeySnapshot | undefined;
|
|
65
77
|
onActiveViewContainerChange?: ((viewContainerId: string) => void) | undefined;
|
|
66
78
|
} = {},
|
|
67
79
|
) {
|
|
@@ -80,7 +92,11 @@ export function renderDefaultBottomPanel(
|
|
|
80
92
|
? activeViewContainerId
|
|
81
93
|
: containers[0]?.id;
|
|
82
94
|
const views = resolvedActiveViewContainerId
|
|
83
|
-
?
|
|
95
|
+
? getVisibleWorkbenchViews(
|
|
96
|
+
extensionRegistry,
|
|
97
|
+
resolvedActiveViewContainerId,
|
|
98
|
+
options.contextKeys ?? {},
|
|
99
|
+
)
|
|
84
100
|
: [];
|
|
85
101
|
|
|
86
102
|
return (
|
|
@@ -111,24 +127,56 @@ export function renderDefaultBottomPanel(
|
|
|
111
127
|
{views.length === 0 ? (
|
|
112
128
|
<div className="workbench-bottom-panel__empty">No views in this panel container.</div>
|
|
113
129
|
) : (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fallback={view.name}
|
|
120
|
-
provider={extensionRegistry.views.getViewProvider(view.id)}
|
|
121
|
-
viewHostFactories={extensionRegistry.viewHostFactories}
|
|
122
|
-
viewId={view.id}
|
|
123
|
-
/>
|
|
124
|
-
</section>
|
|
125
|
-
))
|
|
130
|
+
renderWorkbenchViews(extensionRegistry, views, {
|
|
131
|
+
catalogTrustPolicy: options.catalogTrustPolicy,
|
|
132
|
+
catalogUrl: options.catalogUrl,
|
|
133
|
+
sectionClassName: 'workbench-bottom-panel__view',
|
|
134
|
+
})
|
|
126
135
|
)}
|
|
127
136
|
</div>
|
|
128
137
|
</section>
|
|
129
138
|
);
|
|
130
139
|
}
|
|
131
140
|
|
|
141
|
+
export function getVisibleWorkbenchViews(
|
|
142
|
+
extensionRegistry: Pick<ExtensionRegistry, 'views'>,
|
|
143
|
+
viewContainerId: string,
|
|
144
|
+
contextKeys: WorkbenchContextKeySnapshot,
|
|
145
|
+
): WorkbenchViewContribution[] {
|
|
146
|
+
return filterWorkbenchContributionsByWhenClause(
|
|
147
|
+
extensionRegistry.views.getViews(viewContainerId),
|
|
148
|
+
contextKeys,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function renderWorkbenchViews(
|
|
153
|
+
extensionRegistry: Pick<ExtensionRegistry, 'viewHostFactories' | 'views'>,
|
|
154
|
+
views: readonly WorkbenchViewContribution[],
|
|
155
|
+
options: {
|
|
156
|
+
catalogTrustPolicy?: ExtensionCatalogTrustPolicy | undefined;
|
|
157
|
+
catalogUrl?: string | undefined;
|
|
158
|
+
sectionClassName?: string | undefined;
|
|
159
|
+
},
|
|
160
|
+
): ReactNode {
|
|
161
|
+
return views.map((view) => (
|
|
162
|
+
<section
|
|
163
|
+
key={view.id}
|
|
164
|
+
className={options.sectionClassName}
|
|
165
|
+
data-view-container-id={view.containerId}
|
|
166
|
+
data-view-id={view.id}
|
|
167
|
+
>
|
|
168
|
+
<WorkbenchViewHost
|
|
169
|
+
catalogTrustPolicy={options.catalogTrustPolicy}
|
|
170
|
+
catalogUrl={options.catalogUrl}
|
|
171
|
+
fallback={view.name}
|
|
172
|
+
provider={extensionRegistry.views.getViewProvider(view.id)}
|
|
173
|
+
viewHostFactories={extensionRegistry.viewHostFactories}
|
|
174
|
+
viewId={view.id}
|
|
175
|
+
/>
|
|
176
|
+
</section>
|
|
177
|
+
));
|
|
178
|
+
}
|
|
179
|
+
|
|
132
180
|
export function WorkbenchViewHost({
|
|
133
181
|
catalogTrustPolicy,
|
|
134
182
|
catalogUrl,
|
|
@@ -268,18 +316,5 @@ export function toWorkbenchViewHostReactNode(
|
|
|
268
316
|
);
|
|
269
317
|
}
|
|
270
318
|
|
|
271
|
-
if (isSampleJdwLabViewRenderData(value)) {
|
|
272
|
-
return (
|
|
273
|
-
<SampleJdwLabView
|
|
274
|
-
templateJdwPath={value.templateJdwPath}
|
|
275
|
-
widgetTreePath={value.widgetTreePath}
|
|
276
|
-
/>
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (isSampleFieldRemapViewRenderData(value)) {
|
|
281
|
-
return <SampleFieldRemapView />;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
319
|
return toReactNode(value, fallback);
|
|
285
320
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBrowserWorkbenchStorage,
|
|
3
|
+
type WorkbenchStorageAdapter,
|
|
4
|
+
type WorkbenchStorageReader,
|
|
5
|
+
type WorkbenchStorageWriter,
|
|
6
|
+
} from '@workbench-kit/workbench-core';
|
|
7
|
+
|
|
8
|
+
export function resolveLocalWorkbenchStorage<
|
|
9
|
+
TStorage extends WorkbenchStorageReader | WorkbenchStorageWriter = WorkbenchStorageAdapter,
|
|
10
|
+
>(storage?: TStorage): TStorage | WorkbenchStorageAdapter | undefined {
|
|
11
|
+
return storage ?? createBrowserWorkbenchStorage({ kind: 'local' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readLocalJsonStorage<T>(
|
|
15
|
+
storageKey: string,
|
|
16
|
+
parse: (value: unknown) => T,
|
|
17
|
+
fallback: () => T,
|
|
18
|
+
storage?: WorkbenchStorageReader,
|
|
19
|
+
): T {
|
|
20
|
+
const resolvedStorage = resolveLocalWorkbenchStorage(storage);
|
|
21
|
+
if (!resolvedStorage) {
|
|
22
|
+
return fallback();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const raw = resolvedStorage.getItem(storageKey);
|
|
27
|
+
if (!raw) {
|
|
28
|
+
return fallback();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return parse(JSON.parse(raw) as unknown);
|
|
32
|
+
} catch {
|
|
33
|
+
return fallback();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function writeLocalJsonStorage<T>(
|
|
38
|
+
storageKey: string,
|
|
39
|
+
value: T,
|
|
40
|
+
storage?: WorkbenchStorageWriter,
|
|
41
|
+
options: {
|
|
42
|
+
readonly errorMode?: 'ignore' | 'throw';
|
|
43
|
+
readonly toStorageValue?: (value: T) => unknown;
|
|
44
|
+
} = {},
|
|
45
|
+
): void {
|
|
46
|
+
const resolvedStorage = resolveLocalWorkbenchStorage(storage);
|
|
47
|
+
if (!resolvedStorage) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
resolvedStorage.setItem(
|
|
53
|
+
storageKey,
|
|
54
|
+
JSON.stringify((options.toStorageValue ?? identity)(value), null, 2),
|
|
55
|
+
);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (options.errorMode === 'throw') {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
// Local storage is best-effort; quota, security, and serialization errors stay non-fatal.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function identity<T>(value: T): T {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
@@ -7,11 +7,16 @@ import {
|
|
|
7
7
|
type WorkbenchColorSchemePreference,
|
|
8
8
|
} from '@workbench-kit/react/workbench';
|
|
9
9
|
import {
|
|
10
|
-
createBrowserWorkbenchStorage,
|
|
11
10
|
type WorkbenchStorageReader,
|
|
12
11
|
type WorkbenchStorageWriter,
|
|
13
12
|
} from '@workbench-kit/workbench-core';
|
|
14
13
|
|
|
14
|
+
import {
|
|
15
|
+
readLocalJsonStorage,
|
|
16
|
+
resolveLocalWorkbenchStorage,
|
|
17
|
+
writeLocalJsonStorage,
|
|
18
|
+
} from '../storage/local-json-storage.js';
|
|
19
|
+
|
|
15
20
|
export type { WorkbenchAppearanceSettings } from '@workbench-kit/react/workbench/themePresets';
|
|
16
21
|
|
|
17
22
|
export const DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY = 'workbench-kit/.workbench/appearance';
|
|
@@ -24,28 +29,19 @@ export const DEFAULT_WORKBENCH_APPEARANCE: WorkbenchAppearanceSettings = {
|
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
export function isWorkbenchAppearancePersistenceAvailable(): boolean {
|
|
27
|
-
return
|
|
32
|
+
return resolveLocalWorkbenchStorage() !== undefined;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export function readPersistedWorkbenchAppearance(
|
|
31
36
|
storageKey = DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY,
|
|
32
37
|
storage?: WorkbenchStorageReader,
|
|
33
38
|
): WorkbenchAppearanceSettings {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const raw = resolvedStorage.getItem(storageKey);
|
|
41
|
-
if (!raw) {
|
|
42
|
-
return DEFAULT_WORKBENCH_APPEARANCE;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return normalizeWorkbenchAppearance(JSON.parse(raw) as unknown);
|
|
46
|
-
} catch {
|
|
47
|
-
return DEFAULT_WORKBENCH_APPEARANCE;
|
|
48
|
-
}
|
|
39
|
+
return readLocalJsonStorage(
|
|
40
|
+
storageKey,
|
|
41
|
+
normalizeWorkbenchAppearance,
|
|
42
|
+
() => DEFAULT_WORKBENCH_APPEARANCE,
|
|
43
|
+
storage,
|
|
44
|
+
);
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
export function writePersistedWorkbenchAppearance(
|
|
@@ -53,16 +49,7 @@ export function writePersistedWorkbenchAppearance(
|
|
|
53
49
|
storageKey = DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY,
|
|
54
50
|
storage?: WorkbenchStorageWriter,
|
|
55
51
|
): void {
|
|
56
|
-
|
|
57
|
-
if (!resolvedStorage) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
resolvedStorage.setItem(storageKey, JSON.stringify(settings, null, 2));
|
|
63
|
-
} catch {
|
|
64
|
-
// Ignore quota and security errors so the shell keeps working offline.
|
|
65
|
-
}
|
|
52
|
+
writeLocalJsonStorage(storageKey, settings, storage);
|
|
66
53
|
}
|
|
67
54
|
|
|
68
55
|
function normalizeWorkbenchAppearance(value: unknown): WorkbenchAppearanceSettings {
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
type WorkbenchLayoutConfig,
|
|
4
4
|
} from '@workbench-kit/workbench-config';
|
|
5
5
|
import {
|
|
6
|
-
createBrowserWorkbenchStorage,
|
|
7
6
|
createWorkbenchLayoutState,
|
|
8
7
|
type WorkbenchStorageReader,
|
|
9
8
|
type WorkbenchStorageWriter,
|
|
@@ -11,10 +10,16 @@ import {
|
|
|
11
10
|
type WorkbenchLayoutStateInput,
|
|
12
11
|
} from '@workbench-kit/workbench-core';
|
|
13
12
|
|
|
13
|
+
import {
|
|
14
|
+
readLocalJsonStorage,
|
|
15
|
+
resolveLocalWorkbenchStorage,
|
|
16
|
+
writeLocalJsonStorage,
|
|
17
|
+
} from '../storage/local-json-storage.js';
|
|
18
|
+
|
|
14
19
|
export const DEFAULT_WORKBENCH_LAYOUT_STORAGE_KEY = 'workbench-kit/.workbench/layout';
|
|
15
20
|
|
|
16
21
|
export function isWorkbenchLayoutPersistenceAvailable(): boolean {
|
|
17
|
-
return
|
|
22
|
+
return resolveLocalWorkbenchStorage() !== undefined;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
export function workbenchLayoutConfigToInput(
|
|
@@ -81,17 +86,12 @@ export function readPersistedWorkbenchLayout(
|
|
|
81
86
|
storageKey = DEFAULT_WORKBENCH_LAYOUT_STORAGE_KEY,
|
|
82
87
|
storage?: WorkbenchStorageReader,
|
|
83
88
|
): WorkbenchLayoutStateInput | undefined {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return workbenchLayoutConfigToInput(parseWorkbenchLayoutConfig(JSON.parse(raw) as unknown));
|
|
92
|
-
} catch {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
89
|
+
return readLocalJsonStorage(
|
|
90
|
+
storageKey,
|
|
91
|
+
(value) => workbenchLayoutConfigToInput(parseWorkbenchLayoutConfig(value)),
|
|
92
|
+
() => undefined,
|
|
93
|
+
storage,
|
|
94
|
+
);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
export function writePersistedWorkbenchLayout(
|
|
@@ -99,17 +99,9 @@ export function writePersistedWorkbenchLayout(
|
|
|
99
99
|
storageKey = DEFAULT_WORKBENCH_LAYOUT_STORAGE_KEY,
|
|
100
100
|
storage?: WorkbenchStorageWriter,
|
|
101
101
|
): void {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
resolvedStorage.setItem(
|
|
107
|
-
storageKey,
|
|
108
|
-
JSON.stringify(workbenchLayoutStateToStorageValue(state), null, 2),
|
|
109
|
-
);
|
|
110
|
-
} catch {
|
|
111
|
-
// Ignore quota and security errors so the shell keeps working offline.
|
|
112
|
-
}
|
|
102
|
+
writeLocalJsonStorage(storageKey, state, storage, {
|
|
103
|
+
toStorageValue: workbenchLayoutStateToStorageValue,
|
|
104
|
+
});
|
|
113
105
|
}
|
|
114
106
|
|
|
115
107
|
export function resolvePersistedWorkbenchLayout(
|