@workbench-kit/shell-react 0.0.2-prototype.0.2.10

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.
Files changed (105) hide show
  1. package/README.md +98 -0
  2. package/package.json +51 -0
  3. package/src/chat/ai-mock-proposals.ts +39 -0
  4. package/src/chat/command-input.ts +52 -0
  5. package/src/chat/command-policy.ts +54 -0
  6. package/src/chat/command-surface.tsx +155 -0
  7. package/src/chat/use-chat-command-proposals.ts +194 -0
  8. package/src/chat/view-data.ts +20 -0
  9. package/src/chat/view.tsx +220 -0
  10. package/src/commands/inspector-surface.tsx +46 -0
  11. package/src/commands/use-command-descriptors.ts +41 -0
  12. package/src/commands/use-context-key-revision.ts +18 -0
  13. package/src/commands/view-data.ts +17 -0
  14. package/src/commands/view.tsx +43 -0
  15. package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
  16. package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
  17. package/src/devtools/index.ts +11 -0
  18. package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
  19. package/src/devtools/workbench-devtools-snapshot.ts +140 -0
  20. package/src/devtools/workbench-devtools.css +82 -0
  21. package/src/editor/area-dnd.ts +69 -0
  22. package/src/editor/area-model.ts +71 -0
  23. package/src/editor/area.css +454 -0
  24. package/src/editor/area.tsx +676 -0
  25. package/src/editor/host-surface.tsx +691 -0
  26. package/src/editor/markdown-document-view-data.ts +16 -0
  27. package/src/editor/pane-visibility.ts +130 -0
  28. package/src/editor/resource.ts +36 -0
  29. package/src/editor/state-storage.ts +182 -0
  30. package/src/editor/tab-context-menu.ts +118 -0
  31. package/src/editor/use-editor.ts +138 -0
  32. package/src/editor/view-providers.tsx +504 -0
  33. package/src/editor/workspace-file-availability.ts +15 -0
  34. package/src/editor/workspace-reconcile.tsx +31 -0
  35. package/src/explorer/context-menu.ts +114 -0
  36. package/src/explorer/create-command-workspace-explorer-port.ts +86 -0
  37. package/src/explorer/reveal.ts +112 -0
  38. package/src/explorer/search-view-data.ts +15 -0
  39. package/src/explorer/search-view.tsx +65 -0
  40. package/src/explorer/use-active-workspace-path.ts +15 -0
  41. package/src/explorer/view-data.ts +58 -0
  42. package/src/explorer/view.tsx +227 -0
  43. package/src/extensions/context-menu.ts +52 -0
  44. package/src/extensions/management-model.ts +313 -0
  45. package/src/extensions/use-extension-management.ts +241 -0
  46. package/src/extensions/view-data.ts +19 -0
  47. package/src/extensions/view.tsx +63 -0
  48. package/src/field-remap/chrome-labels.ts +74 -0
  49. package/src/field-remap/convert-note-editor.tsx +206 -0
  50. package/src/field-remap/convert-palette.tsx +126 -0
  51. package/src/field-remap/demo.tsx +105 -0
  52. package/src/field-remap/detail-panel.tsx +659 -0
  53. package/src/field-remap/editor-surface.tsx +36 -0
  54. package/src/field-remap/flow-adapter.ts +838 -0
  55. package/src/field-remap/flow-ops.ts +442 -0
  56. package/src/field-remap/flow.tsx +948 -0
  57. package/src/field-remap/graph.tsx +559 -0
  58. package/src/field-remap/index.ts +88 -0
  59. package/src/field-remap/io-class-browse.tsx +145 -0
  60. package/src/field-remap/jsonata-transform.ts +166 -0
  61. package/src/field-remap/panel.tsx +466 -0
  62. package/src/field-remap/samples.ts +507 -0
  63. package/src/field-remap/shape-io-editor.tsx +170 -0
  64. package/src/field-remap/table-mapping-adapter.ts +59 -0
  65. package/src/field-remap/transform-options-editor.tsx +312 -0
  66. package/src/field-remap/tree.tsx +404 -0
  67. package/src/field-remap/view-data.ts +16 -0
  68. package/src/field-remap/view.css +1367 -0
  69. package/src/field-remap/view.tsx +86 -0
  70. package/src/index.ts +294 -0
  71. package/src/jdw/document-view-data.ts +31 -0
  72. package/src/jdw/json-form-source-patch.ts +323 -0
  73. package/src/jdw/lab-view-data.ts +25 -0
  74. package/src/jdw/lab-view.css +21 -0
  75. package/src/jdw/lab-view.tsx +79 -0
  76. package/src/jdw/widget-form-view.tsx +68 -0
  77. package/src/jdw/widget-preview-view.tsx +54 -0
  78. package/src/management/keybinding-overrides-storage.ts +49 -0
  79. package/src/management/keybinding-settings.tsx +16 -0
  80. package/src/management/palette-commands.ts +38 -0
  81. package/src/management/preference-settings-storage.ts +50 -0
  82. package/src/management/settings-ids.ts +12 -0
  83. package/src/management/settings.tsx +96 -0
  84. package/src/management/use-command-management.ts +191 -0
  85. package/src/management/use-keybinding-management.ts +130 -0
  86. package/src/shell/model.tsx +149 -0
  87. package/src/shell/provider.tsx +606 -0
  88. package/src/shell/secondary-actions.tsx +59 -0
  89. package/src/shell/settings-constants.ts +9 -0
  90. package/src/shell/settings.tsx +599 -0
  91. package/src/shell/shell.tsx +680 -0
  92. package/src/shell/titlebar-layout-controls.tsx +4 -0
  93. package/src/shell/view-host.tsx +212 -0
  94. package/src/workbench/appearance-storage.ts +107 -0
  95. package/src/workbench/command-host.tsx +243 -0
  96. package/src/workbench/command-palette.ts +208 -0
  97. package/src/workbench/keybinding-bridge.ts +54 -0
  98. package/src/workbench/layout-storage.ts +134 -0
  99. package/src/workbench/profile-modal.tsx +118 -0
  100. package/src/workbench/shell-command-registration.ts +58 -0
  101. package/src/workbench/startup-gate.tsx +136 -0
  102. package/src/workbench/status-sections.ts +43 -0
  103. package/src/workbench/use-persisted-appearance.ts +33 -0
  104. package/src/workbench/user-commands.ts +46 -0
  105. package/src/workbench/workspace-view-state.ts +35 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Host-overridable chrome strings for Field Remap Flow / palette surfaces.
3
+ * Domain vocabulary (`MappingEdge`, binding runtime) stays unchanged.
4
+ *
5
+ * Resolution order per string: `labels[key]` → `t(capabilityId, default)` → English default.
6
+ */
7
+
8
+ export type FieldRemapTranslate = (
9
+ key: string,
10
+ fallback: string,
11
+ params?: Readonly<Record<string, string | number>>,
12
+ ) => string;
13
+
14
+ export interface FieldRemapChromeLabels {
15
+ /** Edge-list heading under the Flow canvas (product noun; default “Bindings”). */
16
+ readonly bindingsTitle: string;
17
+ readonly convertPaletteTitle: string;
18
+ readonly convertPaletteDescription: string;
19
+ readonly convertPaletteAriaLabel: string;
20
+ readonly convertsListAriaLabel: string;
21
+ readonly placeConvert: string;
22
+ readonly operatorsTitle: string;
23
+ readonly operatorsDescription: string;
24
+ }
25
+
26
+ export const defaultFieldRemapChromeLabels: FieldRemapChromeLabels = {
27
+ bindingsTitle: 'Bindings',
28
+ convertPaletteTitle: 'Convert palette',
29
+ convertPaletteDescription:
30
+ 'Place a convert first, then wire source → draft → target. Drafts stay off the document until both ports bind.',
31
+ convertPaletteAriaLabel: 'Convert palette',
32
+ convertsListAriaLabel: 'Converts',
33
+ placeConvert: 'Place convert',
34
+ operatorsTitle: 'n→m operators',
35
+ operatorsDescription:
36
+ 'Create combine (n→1) or split (1→n), then wire ports or edit in the side rail.',
37
+ };
38
+
39
+ /** Stable capability ids for optional `t()` injection (not free prose). */
40
+ export const fieldRemapChromeLabelKeys = {
41
+ bindingsTitle: 'fieldRemap.bindingsTitle',
42
+ convertPaletteTitle: 'fieldRemap.convertPaletteTitle',
43
+ convertPaletteDescription: 'fieldRemap.convertPaletteDescription',
44
+ convertPaletteAriaLabel: 'fieldRemap.convertPaletteAriaLabel',
45
+ convertsListAriaLabel: 'fieldRemap.convertsListAriaLabel',
46
+ placeConvert: 'fieldRemap.placeConvert',
47
+ operatorsTitle: 'fieldRemap.operatorsTitle',
48
+ operatorsDescription: 'fieldRemap.operatorsDescription',
49
+ } as const satisfies Record<keyof FieldRemapChromeLabels, string>;
50
+
51
+ export function resolveFieldRemapChromeLabels(
52
+ labels?: Partial<FieldRemapChromeLabels> | undefined,
53
+ t?: FieldRemapTranslate | undefined,
54
+ ): FieldRemapChromeLabels {
55
+ const resolve = <K extends keyof FieldRemapChromeLabels>(key: K): string => {
56
+ const override = labels?.[key];
57
+ if (override !== undefined) {
58
+ return override;
59
+ }
60
+ const fallback = defaultFieldRemapChromeLabels[key];
61
+ return t?.(fieldRemapChromeLabelKeys[key], fallback) ?? fallback;
62
+ };
63
+
64
+ return {
65
+ bindingsTitle: resolve('bindingsTitle'),
66
+ convertPaletteTitle: resolve('convertPaletteTitle'),
67
+ convertPaletteDescription: resolve('convertPaletteDescription'),
68
+ convertPaletteAriaLabel: resolve('convertPaletteAriaLabel'),
69
+ convertsListAriaLabel: resolve('convertsListAriaLabel'),
70
+ placeConvert: resolve('placeConvert'),
71
+ operatorsTitle: resolve('operatorsTitle'),
72
+ operatorsDescription: resolve('operatorsDescription'),
73
+ };
74
+ }
@@ -0,0 +1,206 @@
1
+ import { useMemo, type JSX } from 'react';
2
+ import { Button } from '@workbench-kit/react/primitives';
3
+ import {
4
+ optionFieldsForStep,
5
+ resolveOptionSteps,
6
+ type MappingEdge,
7
+ type SourceField,
8
+ type TargetSlot,
9
+ type ValueTransformRegistry,
10
+ } from '@workbench-kit/field-remap';
11
+
12
+ import {
13
+ edgePortTypes,
14
+ listCompatibleTransforms,
15
+ removeTransformStepFromEdge,
16
+ replaceTransformStepOptionsOnEdge,
17
+ setTransformStepIdOnEdge,
18
+ type FieldRemapSelection,
19
+ } from './flow-ops.js';
20
+ import { TransformOptionsEditor } from './transform-options-editor.js';
21
+
22
+ export interface ConvertNoteEditorProps {
23
+ readonly edge: MappingEdge;
24
+ readonly stepIndex: number;
25
+ readonly sources: readonly SourceField[];
26
+ readonly targets: readonly TargetSlot[];
27
+ readonly transforms: ValueTransformRegistry;
28
+ readonly onEdgesChange: (edges: readonly MappingEdge[]) => void;
29
+ readonly edges: readonly MappingEdge[];
30
+ readonly onSelectionChange: (next: FieldRemapSelection) => void;
31
+ }
32
+
33
+ /**
34
+ * Dedicated convert-note authoring surface for a selected `xf:*` step.
35
+ * Kept separate from binding / list-context chrome on the Flow detail rail.
36
+ */
37
+ export function ConvertNoteEditor({
38
+ edge,
39
+ stepIndex,
40
+ sources,
41
+ targets,
42
+ transforms,
43
+ edges,
44
+ onEdgesChange,
45
+ onSelectionChange,
46
+ }: ConvertNoteEditorProps): JSX.Element | null {
47
+ const chain = edge.transformIds ?? [];
48
+ const transformId = chain[stepIndex];
49
+
50
+ const portTypes = useMemo(() => edgePortTypes(edge, sources, targets), [edge, sources, targets]);
51
+
52
+ const optionFields = useMemo(
53
+ () => optionFieldsForStep(transforms, transformId),
54
+ [transforms, transformId],
55
+ );
56
+
57
+ const optionValue = useMemo(() => {
58
+ if (!transformId) {
59
+ return {};
60
+ }
61
+ return (
62
+ resolveOptionSteps(chain, edge.transformOptionSteps, edge.transformOptions)[stepIndex] ?? {}
63
+ );
64
+ }, [chain, edge.transformOptionSteps, edge.transformOptions, stepIndex, transformId]);
65
+
66
+ const replaceCatalog = useMemo(
67
+ () =>
68
+ listCompatibleTransforms({
69
+ registry: transforms,
70
+ edge,
71
+ stepIndex,
72
+ sourceType: portTypes.sourceType,
73
+ targetType: portTypes.targetType,
74
+ mode: 'replace',
75
+ }),
76
+ [edge, portTypes.sourceType, portTypes.targetType, stepIndex, transforms],
77
+ );
78
+
79
+ if (!transformId) {
80
+ return null;
81
+ }
82
+
83
+ const definition = transforms.get(transformId);
84
+ const stepLabel = definition?.label ?? transformId;
85
+
86
+ const applyEdge = (next: MappingEdge | null) => {
87
+ if (!next) {
88
+ return;
89
+ }
90
+ onEdgesChange(edges.map((item) => (item.id === edge.id ? next : item)));
91
+ };
92
+
93
+ return (
94
+ <aside
95
+ className="workbench-field-remap-convert-note"
96
+ data-testid="field-remap-convert-note"
97
+ aria-label="Convert note editor"
98
+ >
99
+ <div className="workbench-field-remap-convert-note__header">
100
+ <div>
101
+ <p className="workbench-field-remap-convert-note__eyebrow">Convert</p>
102
+ <h4 data-testid="field-remap-convert-note-title">{stepLabel}</h4>
103
+ <code
104
+ className="workbench-field-remap-convert-note__binding"
105
+ data-testid="field-remap-convert-note-binding"
106
+ >
107
+ {edge.sourceFieldId} → {edge.targetSlotId}
108
+ </code>
109
+ <p className="workbench-field-remap-convert-note__muted">
110
+ Step {stepIndex + 1} of {chain.length} on this binding
111
+ </p>
112
+ </div>
113
+ <div className="workbench-field-remap-convert-note__actions">
114
+ <Button
115
+ compact
116
+ type="button"
117
+ data-testid="field-remap-convert-note-back"
118
+ onClick={() => onSelectionChange({ kind: 'edge', edgeId: edge.id })}
119
+ >
120
+ Binding
121
+ </Button>
122
+ <Button
123
+ compact
124
+ type="button"
125
+ data-testid="field-remap-convert-note-clear"
126
+ onClick={() => onSelectionChange(null)}
127
+ >
128
+ Close
129
+ </Button>
130
+ </div>
131
+ </div>
132
+
133
+ <section
134
+ className="workbench-field-remap-convert-note__section"
135
+ aria-labelledby="field-remap-convert-note-registry"
136
+ data-testid="field-remap-step-settings"
137
+ >
138
+ <h5 id="field-remap-convert-note-registry">Transform</h5>
139
+ <label className="workbench-field-remap-convert-note__field">
140
+ <span>Registry id</span>
141
+ <select
142
+ aria-label="Convert transform id"
143
+ data-testid="field-remap-step-id"
144
+ value={transformId}
145
+ onChange={(event) => {
146
+ applyEdge(
147
+ setTransformStepIdOnEdge(edge, stepIndex, event.target.value, {
148
+ registry: transforms,
149
+ sourceType: portTypes.sourceType,
150
+ targetType: portTypes.targetType,
151
+ }),
152
+ );
153
+ }}
154
+ >
155
+ {!replaceCatalog.some((item) => item.id === transformId) ? (
156
+ <option value={transformId}>{transformId}</option>
157
+ ) : null}
158
+ {replaceCatalog.map((item) => (
159
+ <option key={item.id} value={item.id}>
160
+ {item.label}
161
+ </option>
162
+ ))}
163
+ </select>
164
+ </label>
165
+ </section>
166
+
167
+ <section
168
+ className="workbench-field-remap-convert-note__section"
169
+ aria-labelledby="field-remap-convert-note-options"
170
+ >
171
+ <h5 id="field-remap-convert-note-options">Options</h5>
172
+ <TransformOptionsEditor
173
+ fields={optionFields}
174
+ value={optionValue}
175
+ onChange={(nextOptions) => {
176
+ applyEdge(replaceTransformStepOptionsOnEdge(edge, stepIndex, nextOptions));
177
+ }}
178
+ />
179
+ </section>
180
+
181
+ <div className="workbench-field-remap-convert-note__footer">
182
+ <Button
183
+ compact
184
+ type="button"
185
+ data-testid="field-remap-convert-note-remove"
186
+ onClick={() => {
187
+ const next = removeTransformStepFromEdge(edge, stepIndex);
188
+ applyEdge(next);
189
+ const nextLen = next.transformIds?.length ?? 0;
190
+ if (nextLen === 0) {
191
+ onSelectionChange({ kind: 'edge', edgeId: edge.id });
192
+ } else {
193
+ onSelectionChange({
194
+ kind: 'transformStep',
195
+ edgeId: edge.id,
196
+ stepIndex: Math.min(stepIndex, nextLen - 1),
197
+ });
198
+ }
199
+ }}
200
+ >
201
+ Remove convert
202
+ </Button>
203
+ </div>
204
+ </aside>
205
+ );
206
+ }
@@ -0,0 +1,126 @@
1
+ import { useMemo, type JSX } from 'react';
2
+ import { Button } from '@workbench-kit/react/primitives';
3
+ import type { ValueTransformRegistry } from '@workbench-kit/field-remap';
4
+
5
+ import { defaultFieldRemapChromeLabels, type FieldRemapChromeLabels } from './chrome-labels.js';
6
+
7
+ export interface FieldRemapConvertPaletteProps {
8
+ readonly transforms: ValueTransformRegistry;
9
+ readonly selectedTransformId: string;
10
+ readonly onSelectedTransformIdChange: (transformId: string) => void;
11
+ readonly onPlaceDraft: (transformId: string) => void;
12
+ readonly onAddCombine?: (() => void) | undefined;
13
+ readonly onAddSplit?: (() => void) | undefined;
14
+ /** Resolved chrome labels (defaults when omitted). */
15
+ readonly chromeLabels?: FieldRemapChromeLabels | undefined;
16
+ }
17
+
18
+ /**
19
+ * Primary convert chrome for Field Remap Flow: place-then-wire drafts first,
20
+ * with optional combine/split authoring entry points.
21
+ */
22
+ export function FieldRemapConvertPalette({
23
+ transforms,
24
+ selectedTransformId,
25
+ onSelectedTransformIdChange,
26
+ onPlaceDraft,
27
+ onAddCombine,
28
+ onAddSplit,
29
+ chromeLabels = defaultFieldRemapChromeLabels,
30
+ }: FieldRemapConvertPaletteProps): JSX.Element {
31
+ const catalog = useMemo(
32
+ () => transforms.list().filter((definition) => definition.id !== 'identity'),
33
+ [transforms],
34
+ );
35
+
36
+ return (
37
+ <aside
38
+ className="workbench-field-remap-convert-palette"
39
+ data-testid="field-remap-convert-palette"
40
+ aria-label={chromeLabels.convertPaletteAriaLabel}
41
+ >
42
+ <header className="workbench-field-remap-convert-palette__header">
43
+ <h3>{chromeLabels.convertPaletteTitle}</h3>
44
+ <p>{chromeLabels.convertPaletteDescription}</p>
45
+ </header>
46
+
47
+ <div className="workbench-field-remap-convert-palette__place">
48
+ <Button
49
+ type="button"
50
+ data-testid="field-remap-place-draft"
51
+ disabled={!selectedTransformId}
52
+ onClick={() => {
53
+ if (!selectedTransformId) {
54
+ return;
55
+ }
56
+ onPlaceDraft(selectedTransformId);
57
+ }}
58
+ >
59
+ {chromeLabels.placeConvert}
60
+ </Button>
61
+ </div>
62
+
63
+ <ul
64
+ className="workbench-field-remap-convert-palette__list"
65
+ role="listbox"
66
+ aria-label={chromeLabels.convertsListAriaLabel}
67
+ >
68
+ {catalog.map((definition) => {
69
+ const selected = definition.id === selectedTransformId;
70
+ return (
71
+ <li key={definition.id}>
72
+ <button
73
+ type="button"
74
+ role="option"
75
+ aria-selected={selected}
76
+ className={
77
+ selected
78
+ ? 'workbench-field-remap-convert-palette__item is-selected'
79
+ : 'workbench-field-remap-convert-palette__item'
80
+ }
81
+ data-testid={`field-remap-palette-item-${definition.id}`}
82
+ onClick={() => onSelectedTransformIdChange(definition.id)}
83
+ onDoubleClick={() => onPlaceDraft(definition.id)}
84
+ >
85
+ <strong>{definition.label}</strong>
86
+ <code>{definition.id}</code>
87
+ </button>
88
+ </li>
89
+ );
90
+ })}
91
+ </ul>
92
+
93
+ {onAddCombine || onAddSplit ? (
94
+ <div
95
+ className="workbench-field-remap-convert-palette__operators"
96
+ data-testid="field-remap-operator-palette"
97
+ >
98
+ <h4>{chromeLabels.operatorsTitle}</h4>
99
+ <p>{chromeLabels.operatorsDescription}</p>
100
+ <div className="workbench-field-remap-convert-palette__operator-actions">
101
+ {onAddCombine ? (
102
+ <Button
103
+ compact
104
+ type="button"
105
+ data-testid="field-remap-add-combine"
106
+ onClick={onAddCombine}
107
+ >
108
+ Add combine
109
+ </Button>
110
+ ) : null}
111
+ {onAddSplit ? (
112
+ <Button
113
+ compact
114
+ type="button"
115
+ data-testid="field-remap-add-split"
116
+ onClick={onAddSplit}
117
+ >
118
+ Add split
119
+ </Button>
120
+ ) : null}
121
+ </div>
122
+ </div>
123
+ ) : null}
124
+ </aside>
125
+ );
126
+ }
@@ -0,0 +1,105 @@
1
+ import { useEffect, useRef, useState, type JSX } from 'react';
2
+ import { Button } from '@workbench-kit/react/primitives';
3
+
4
+ import type { FieldRemapChromeLabels, FieldRemapTranslate } from './chrome-labels.js';
5
+ import type { FieldRemapFlowActions } from './flow.js';
6
+ import type { FieldRemapIoChrome } from './io-class-browse.js';
7
+ import { FieldRemapPanel } from './panel.js';
8
+ import {
9
+ getFieldRemapBrowseDemoShapes,
10
+ getFieldRemapSample,
11
+ type FieldRemapSampleId,
12
+ } from './samples.js';
13
+
14
+ export interface SampleFieldRemapDemoProps {
15
+ readonly sampleId?: FieldRemapSampleId | string | undefined;
16
+ /** Forwarded to Flow mapper (default true). */
17
+ readonly showMinimap?: boolean | undefined;
18
+ /** When true, show host-chrome demo controls (minimap toggle + fit view). */
19
+ readonly showHostChromeDemo?: boolean | undefined;
20
+ /** Prefer `browse` for I/O class/field inspection demos. */
21
+ readonly ioChrome?: FieldRemapIoChrome | undefined;
22
+ /** When true with `ioChrome: 'browse'`, seed classRef + hidden leaf shapes. */
23
+ readonly browseSeedShapes?: boolean | undefined;
24
+ readonly labels?: Partial<FieldRemapChromeLabels> | undefined;
25
+ readonly t?: FieldRemapTranslate | undefined;
26
+ }
27
+
28
+ /**
29
+ * Sample host wrapper around {@link FieldRemapPanel}.
30
+ */
31
+ export function SampleFieldRemapDemo({
32
+ sampleId = 'nested-ab',
33
+ showMinimap: showMinimapProp,
34
+ showHostChromeDemo = false,
35
+ ioChrome,
36
+ browseSeedShapes = false,
37
+ labels,
38
+ t,
39
+ }: SampleFieldRemapDemoProps = {}): JSX.Element {
40
+ const sample = getFieldRemapSample(sampleId);
41
+ const browseShapes = browseSeedShapes ? getFieldRemapBrowseDemoShapes() : null;
42
+ const flowActionsRef = useRef<FieldRemapFlowActions | null>(null);
43
+ const [showMinimap, setShowMinimap] = useState(showMinimapProp ?? true);
44
+ const [lastPaneMenu, setLastPaneMenu] = useState<string | null>(null);
45
+
46
+ useEffect(() => {
47
+ if (showMinimapProp !== undefined) {
48
+ setShowMinimap(showMinimapProp);
49
+ }
50
+ }, [showMinimapProp]);
51
+
52
+ return (
53
+ <div data-testid="field-remap-demo-host">
54
+ {showHostChromeDemo ? (
55
+ <div
56
+ className="workbench-field-remap-demo__host-chrome"
57
+ data-testid="field-remap-host-chrome"
58
+ style={{ display: 'flex', gap: '0.5rem', marginBottom: '0.75rem', flexWrap: 'wrap' }}
59
+ >
60
+ <Button
61
+ compact
62
+ type="button"
63
+ data-testid="field-remap-toggle-minimap"
64
+ onClick={() => setShowMinimap((current) => !current)}
65
+ >
66
+ {showMinimap ? 'Hide MiniMap' : 'Show MiniMap'}
67
+ </Button>
68
+ <Button
69
+ compact
70
+ type="button"
71
+ data-testid="field-remap-fit-view"
72
+ onClick={() => flowActionsRef.current?.fitView()}
73
+ >
74
+ Fit view
75
+ </Button>
76
+ {lastPaneMenu ? (
77
+ <span data-testid="field-remap-pane-menu-log" role="status">
78
+ Pane menu: {lastPaneMenu}
79
+ </span>
80
+ ) : null}
81
+ </div>
82
+ ) : null}
83
+ <FieldRemapPanel
84
+ key={`${sample.id}:${ioChrome ?? 'default'}:${browseSeedShapes ? 'seed' : 'plain'}`}
85
+ sample={sample}
86
+ showMinimap={showMinimap}
87
+ ioChrome={ioChrome}
88
+ editableShapes={ioChrome === 'browse' ? false : undefined}
89
+ sources={browseShapes?.sources}
90
+ targets={browseShapes?.targets}
91
+ labels={labels}
92
+ t={t}
93
+ flowActionsRef={showHostChromeDemo ? flowActionsRef : undefined}
94
+ onPaneContextMenu={
95
+ showHostChromeDemo
96
+ ? (event) => {
97
+ event.preventDefault();
98
+ setLastPaneMenu(new Date().toISOString());
99
+ }
100
+ : undefined
101
+ }
102
+ />
103
+ </div>
104
+ );
105
+ }