@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.
- package/README.md +98 -0
- package/package.json +51 -0
- package/src/chat/ai-mock-proposals.ts +39 -0
- package/src/chat/command-input.ts +52 -0
- package/src/chat/command-policy.ts +54 -0
- package/src/chat/command-surface.tsx +155 -0
- package/src/chat/use-chat-command-proposals.ts +194 -0
- package/src/chat/view-data.ts +20 -0
- package/src/chat/view.tsx +220 -0
- package/src/commands/inspector-surface.tsx +46 -0
- package/src/commands/use-command-descriptors.ts +41 -0
- package/src/commands/use-context-key-revision.ts +18 -0
- package/src/commands/view-data.ts +17 -0
- package/src/commands/view.tsx +43 -0
- package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
- package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
- package/src/devtools/index.ts +11 -0
- package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
- package/src/devtools/workbench-devtools-snapshot.ts +140 -0
- package/src/devtools/workbench-devtools.css +82 -0
- package/src/editor/area-dnd.ts +69 -0
- package/src/editor/area-model.ts +71 -0
- package/src/editor/area.css +454 -0
- package/src/editor/area.tsx +676 -0
- package/src/editor/host-surface.tsx +691 -0
- package/src/editor/markdown-document-view-data.ts +16 -0
- package/src/editor/pane-visibility.ts +130 -0
- package/src/editor/resource.ts +36 -0
- package/src/editor/state-storage.ts +182 -0
- package/src/editor/tab-context-menu.ts +118 -0
- package/src/editor/use-editor.ts +138 -0
- package/src/editor/view-providers.tsx +504 -0
- package/src/editor/workspace-file-availability.ts +15 -0
- package/src/editor/workspace-reconcile.tsx +31 -0
- package/src/explorer/context-menu.ts +114 -0
- package/src/explorer/create-command-workspace-explorer-port.ts +86 -0
- package/src/explorer/reveal.ts +112 -0
- package/src/explorer/search-view-data.ts +15 -0
- package/src/explorer/search-view.tsx +65 -0
- package/src/explorer/use-active-workspace-path.ts +15 -0
- package/src/explorer/view-data.ts +58 -0
- package/src/explorer/view.tsx +227 -0
- package/src/extensions/context-menu.ts +52 -0
- package/src/extensions/management-model.ts +313 -0
- package/src/extensions/use-extension-management.ts +241 -0
- package/src/extensions/view-data.ts +19 -0
- package/src/extensions/view.tsx +63 -0
- package/src/field-remap/chrome-labels.ts +74 -0
- package/src/field-remap/convert-note-editor.tsx +206 -0
- package/src/field-remap/convert-palette.tsx +126 -0
- package/src/field-remap/demo.tsx +105 -0
- package/src/field-remap/detail-panel.tsx +659 -0
- package/src/field-remap/editor-surface.tsx +36 -0
- package/src/field-remap/flow-adapter.ts +838 -0
- package/src/field-remap/flow-ops.ts +442 -0
- package/src/field-remap/flow.tsx +948 -0
- package/src/field-remap/graph.tsx +559 -0
- package/src/field-remap/index.ts +88 -0
- package/src/field-remap/io-class-browse.tsx +145 -0
- package/src/field-remap/jsonata-transform.ts +166 -0
- package/src/field-remap/panel.tsx +466 -0
- package/src/field-remap/samples.ts +507 -0
- package/src/field-remap/shape-io-editor.tsx +170 -0
- package/src/field-remap/table-mapping-adapter.ts +59 -0
- package/src/field-remap/transform-options-editor.tsx +312 -0
- package/src/field-remap/tree.tsx +404 -0
- package/src/field-remap/view-data.ts +16 -0
- package/src/field-remap/view.css +1367 -0
- package/src/field-remap/view.tsx +86 -0
- package/src/index.ts +294 -0
- package/src/jdw/document-view-data.ts +31 -0
- package/src/jdw/json-form-source-patch.ts +323 -0
- package/src/jdw/lab-view-data.ts +25 -0
- package/src/jdw/lab-view.css +21 -0
- package/src/jdw/lab-view.tsx +79 -0
- package/src/jdw/widget-form-view.tsx +68 -0
- package/src/jdw/widget-preview-view.tsx +54 -0
- package/src/management/keybinding-overrides-storage.ts +49 -0
- package/src/management/keybinding-settings.tsx +16 -0
- package/src/management/palette-commands.ts +38 -0
- package/src/management/preference-settings-storage.ts +50 -0
- package/src/management/settings-ids.ts +12 -0
- package/src/management/settings.tsx +96 -0
- package/src/management/use-command-management.ts +191 -0
- package/src/management/use-keybinding-management.ts +130 -0
- package/src/shell/model.tsx +149 -0
- package/src/shell/provider.tsx +606 -0
- package/src/shell/secondary-actions.tsx +59 -0
- package/src/shell/settings-constants.ts +9 -0
- package/src/shell/settings.tsx +599 -0
- package/src/shell/shell.tsx +680 -0
- package/src/shell/titlebar-layout-controls.tsx +4 -0
- package/src/shell/view-host.tsx +212 -0
- package/src/workbench/appearance-storage.ts +107 -0
- package/src/workbench/command-host.tsx +243 -0
- package/src/workbench/command-palette.ts +208 -0
- package/src/workbench/keybinding-bridge.ts +54 -0
- package/src/workbench/layout-storage.ts +134 -0
- package/src/workbench/profile-modal.tsx +118 -0
- package/src/workbench/shell-command-registration.ts +58 -0
- package/src/workbench/startup-gate.tsx +136 -0
- package/src/workbench/status-sections.ts +43 -0
- package/src/workbench/use-persisted-appearance.ts +33 -0
- package/src/workbench/user-commands.ts +46 -0
- package/src/workbench/workspace-view-state.ts +35 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidElement,
|
|
3
|
+
lazy,
|
|
4
|
+
Suspense,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { createPortal } from 'react-dom';
|
|
13
|
+
import { IconButton, ScrollArea } from '@workbench-kit/react/primitives';
|
|
14
|
+
import { WorkbenchMarkdownPreview } from '@workbench-kit/react/workbench/markdown-preview';
|
|
15
|
+
import { SplitView } from '@workbench-kit/react/workbench/split-view';
|
|
16
|
+
import type { WorkspaceEditorTheme } from '@workbench-kit/react/workbench/workspace/editor';
|
|
17
|
+
import {
|
|
18
|
+
EDITOR_SAVE_COMMAND_ID,
|
|
19
|
+
type EditorHost,
|
|
20
|
+
type EditorTabState,
|
|
21
|
+
} from '@workbench-kit/workbench-core';
|
|
22
|
+
|
|
23
|
+
import { CommandInspectorSurface } from '../commands/inspector-surface.js';
|
|
24
|
+
import { resolveEditorDocumentViews, type EditorDocumentViewProvider } from './view-providers.js';
|
|
25
|
+
import { isBuiltinEditorMarkdownPreviewRenderData } from './markdown-document-view-data.js';
|
|
26
|
+
import { mimeTypeForResource, pathForResource } from './resource.js';
|
|
27
|
+
import { FieldRemapEditorSurface } from '../field-remap/editor-surface.js';
|
|
28
|
+
import {
|
|
29
|
+
isSampleJdwWidgetFormRenderData,
|
|
30
|
+
isSampleJdwWidgetPreviewRenderData,
|
|
31
|
+
} from '../jdw/document-view-data.js';
|
|
32
|
+
import { JdwWidgetFormView } from '../jdw/widget-form-view.js';
|
|
33
|
+
import { JdwWidgetPreviewView } from '../jdw/widget-preview-view.js';
|
|
34
|
+
import {
|
|
35
|
+
editorViewModeToPaneVisibility,
|
|
36
|
+
getVisibleEditorPaneKinds,
|
|
37
|
+
paneVisibilityToPrimaryViewMode,
|
|
38
|
+
resolveDefaultEditorPaneVisibility,
|
|
39
|
+
resolveEffectiveEditorPaneVisibility,
|
|
40
|
+
type EditorPaneKind,
|
|
41
|
+
type EditorPaneVisibility,
|
|
42
|
+
type EditorViewMode,
|
|
43
|
+
} from './pane-visibility.js';
|
|
44
|
+
import { useWorkbench } from '../shell/provider.js';
|
|
45
|
+
import { useEditorHost, useEditorService } from './use-editor.js';
|
|
46
|
+
|
|
47
|
+
export type { EditorViewMode } from './pane-visibility.js';
|
|
48
|
+
|
|
49
|
+
const WorkspaceEditor = lazy(async () => {
|
|
50
|
+
const module = await import('@workbench-kit/react/workbench/workspace/editor');
|
|
51
|
+
return { default: module.WorkspaceEditor };
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export interface EditorHostSurfaceProps {
|
|
55
|
+
activeTab: EditorTabState | undefined;
|
|
56
|
+
defaultViewModeForResource: ((resourceUri: string) => EditorViewMode | undefined) | undefined;
|
|
57
|
+
modeToolbarHost: HTMLDivElement | null;
|
|
58
|
+
onModeToolbarVisibleChange: (visible: boolean) => void;
|
|
59
|
+
theme: WorkspaceEditorTheme | undefined;
|
|
60
|
+
viewProviders: readonly EditorDocumentViewProvider[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function EditorHostSurface({
|
|
64
|
+
activeTab,
|
|
65
|
+
defaultViewModeForResource,
|
|
66
|
+
modeToolbarHost,
|
|
67
|
+
onModeToolbarVisibleChange,
|
|
68
|
+
theme,
|
|
69
|
+
viewProviders,
|
|
70
|
+
}: EditorHostSurfaceProps) {
|
|
71
|
+
const editorService = useEditorService();
|
|
72
|
+
const host = useEditorHost(activeTab?.id);
|
|
73
|
+
const hostFrameRef = useRef<HTMLDivElement>(null);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!host || !activeTab) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
host.onDidChangeDirty = (dirty) => {
|
|
81
|
+
editorService.setDirty(activeTab.id, dirty);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return () => {
|
|
85
|
+
host.onDidChangeDirty = undefined;
|
|
86
|
+
};
|
|
87
|
+
}, [activeTab, editorService, host]);
|
|
88
|
+
|
|
89
|
+
if (!activeTab || !host) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const rendered = host.render();
|
|
94
|
+
|
|
95
|
+
if (isMissingResourceEditorRenderPayload(rendered)) {
|
|
96
|
+
return (
|
|
97
|
+
<MissingResourceEditorSurface
|
|
98
|
+
message={rendered.message}
|
|
99
|
+
resourceUri={rendered.resourceUri}
|
|
100
|
+
tabId={activeTab.id}
|
|
101
|
+
title={host.title ?? activeTab.title}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isCommandInspectorEditorRenderPayload(rendered)) {
|
|
107
|
+
return (
|
|
108
|
+
<CommandInspectorSurface
|
|
109
|
+
commandId={rendered.commandId}
|
|
110
|
+
resourceUri={rendered.resourceUri}
|
|
111
|
+
tabId={activeTab.id}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (isFieldRemapEditorRenderPayload(rendered)) {
|
|
117
|
+
return (
|
|
118
|
+
<FieldRemapEditorSurface
|
|
119
|
+
resourceUri={rendered.resourceUri}
|
|
120
|
+
surfaceId={rendered.surfaceId}
|
|
121
|
+
tabId={activeTab.id}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (isTextEditorRenderPayload(rendered)) {
|
|
127
|
+
return (
|
|
128
|
+
<TextEditorSurface
|
|
129
|
+
defaultViewModeForResource={defaultViewModeForResource}
|
|
130
|
+
host={host as TextEditorHostLike}
|
|
131
|
+
initialContent={rendered.initialContent}
|
|
132
|
+
modeToolbarHost={modeToolbarHost}
|
|
133
|
+
mimeType={rendered.mimeType}
|
|
134
|
+
onModeToolbarVisibleChange={onModeToolbarVisibleChange}
|
|
135
|
+
resourceUri={rendered.resourceUri}
|
|
136
|
+
tabId={activeTab.id}
|
|
137
|
+
theme={theme}
|
|
138
|
+
viewProviders={viewProviders}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div
|
|
145
|
+
ref={hostFrameRef}
|
|
146
|
+
aria-label={host.title ?? activeTab.title ?? 'Editor'}
|
|
147
|
+
className="workbench-editor-area__host"
|
|
148
|
+
data-editor-host-id={activeTab.id}
|
|
149
|
+
>
|
|
150
|
+
{toReactNode(rendered)}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface TextEditorHostLike extends EditorHost {
|
|
156
|
+
getContent?(): string;
|
|
157
|
+
markDirty?(): void;
|
|
158
|
+
setContent?(content: string): void;
|
|
159
|
+
setDirty?(dirty: boolean): void;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface TextEditorRenderPayload {
|
|
163
|
+
initialContent: string;
|
|
164
|
+
kind: 'workbench-kit.builtin.editor/text';
|
|
165
|
+
mimeType?: string | undefined;
|
|
166
|
+
resourceUri: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function TextEditorSurface({
|
|
170
|
+
defaultViewModeForResource,
|
|
171
|
+
host,
|
|
172
|
+
initialContent,
|
|
173
|
+
modeToolbarHost,
|
|
174
|
+
mimeType,
|
|
175
|
+
onModeToolbarVisibleChange,
|
|
176
|
+
resourceUri,
|
|
177
|
+
tabId,
|
|
178
|
+
theme,
|
|
179
|
+
viewProviders,
|
|
180
|
+
}: {
|
|
181
|
+
defaultViewModeForResource: ((resourceUri: string) => EditorViewMode | undefined) | undefined;
|
|
182
|
+
host: TextEditorHostLike;
|
|
183
|
+
initialContent: string;
|
|
184
|
+
modeToolbarHost: HTMLDivElement | null;
|
|
185
|
+
mimeType?: string | undefined;
|
|
186
|
+
onModeToolbarVisibleChange: (visible: boolean) => void;
|
|
187
|
+
resourceUri: string;
|
|
188
|
+
tabId: string;
|
|
189
|
+
theme: WorkspaceEditorTheme | undefined;
|
|
190
|
+
viewProviders: readonly EditorDocumentViewProvider[];
|
|
191
|
+
}) {
|
|
192
|
+
const editorService = useEditorService();
|
|
193
|
+
const { executeCommand, workspaceHostPort } = useWorkbench();
|
|
194
|
+
const [content, setContent] = useState(initialContent);
|
|
195
|
+
const previousResourceUriRef = useRef<string | undefined>(undefined);
|
|
196
|
+
const appliedLateFormDefaultRef = useRef(false);
|
|
197
|
+
const splitSizesRef = useRef<Record<string, number>>({});
|
|
198
|
+
const editorDocument = useMemo(
|
|
199
|
+
() => ({
|
|
200
|
+
content,
|
|
201
|
+
mimeType: mimeType ?? mimeTypeForResource(resourceUri),
|
|
202
|
+
path: pathForResource(resourceUri),
|
|
203
|
+
resourceUri,
|
|
204
|
+
}),
|
|
205
|
+
[content, mimeType, resourceUri],
|
|
206
|
+
);
|
|
207
|
+
const { formProvider, previewProvider } = useMemo(
|
|
208
|
+
() => resolveEditorDocumentViews(editorDocument, viewProviders),
|
|
209
|
+
[editorDocument, viewProviders],
|
|
210
|
+
);
|
|
211
|
+
const formEligible = Boolean(formProvider);
|
|
212
|
+
const previewEligible = Boolean(previewProvider);
|
|
213
|
+
const [panePreference, setPanePreference] = useState<EditorPaneVisibility>(() =>
|
|
214
|
+
resolveDefaultEditorPaneVisibility(defaultViewModeForResource?.(resourceUri), {
|
|
215
|
+
formEligible,
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
const paneVisibility = useMemo(
|
|
219
|
+
() => resolveEffectiveEditorPaneVisibility(panePreference, { formEligible, previewEligible }),
|
|
220
|
+
[formEligible, panePreference, previewEligible],
|
|
221
|
+
);
|
|
222
|
+
const codeIcon = isJsonSourceDocument(editorDocument) ? 'codicon-json' : 'codicon-code';
|
|
223
|
+
const modeToolbarVisible = formEligible || previewEligible;
|
|
224
|
+
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
const resourceChanged = previousResourceUriRef.current !== resourceUri;
|
|
227
|
+
previousResourceUriRef.current = resourceUri;
|
|
228
|
+
|
|
229
|
+
if (resourceChanged) {
|
|
230
|
+
appliedLateFormDefaultRef.current = false;
|
|
231
|
+
setContent(initialContent);
|
|
232
|
+
setPanePreference(
|
|
233
|
+
resolveDefaultEditorPaneVisibility(defaultViewModeForResource?.(resourceUri), {
|
|
234
|
+
formEligible,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
setContent((current) => (current === initialContent ? current : initialContent));
|
|
241
|
+
|
|
242
|
+
// Document-view providers may register after onStartup activate. Promote the
|
|
243
|
+
// default form pane once — do not fight later user toggles back to Code.
|
|
244
|
+
if (
|
|
245
|
+
!appliedLateFormDefaultRef.current &&
|
|
246
|
+
formEligible &&
|
|
247
|
+
defaultViewModeForResource?.(resourceUri) === undefined &&
|
|
248
|
+
panePreference.code &&
|
|
249
|
+
!panePreference.form &&
|
|
250
|
+
!panePreference.preview
|
|
251
|
+
) {
|
|
252
|
+
appliedLateFormDefaultRef.current = true;
|
|
253
|
+
setPanePreference(
|
|
254
|
+
resolveDefaultEditorPaneVisibility(undefined, {
|
|
255
|
+
formEligible: true,
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}, [
|
|
260
|
+
defaultViewModeForResource,
|
|
261
|
+
formEligible,
|
|
262
|
+
initialContent,
|
|
263
|
+
panePreference.code,
|
|
264
|
+
panePreference.form,
|
|
265
|
+
panePreference.preview,
|
|
266
|
+
resourceUri,
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
onModeToolbarVisibleChange(modeToolbarVisible);
|
|
271
|
+
}, [modeToolbarVisible, onModeToolbarVisibleChange]);
|
|
272
|
+
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
return () => {
|
|
275
|
+
onModeToolbarVisibleChange(false);
|
|
276
|
+
};
|
|
277
|
+
}, [onModeToolbarVisibleChange]);
|
|
278
|
+
|
|
279
|
+
const getSplitSize = useCallback((key: string, defaultPercent: number) => {
|
|
280
|
+
return splitSizesRef.current[key] ?? defaultPercent;
|
|
281
|
+
}, []);
|
|
282
|
+
|
|
283
|
+
const setSplitSize = useCallback((key: string, percent: number) => {
|
|
284
|
+
splitSizesRef.current[key] = percent;
|
|
285
|
+
}, []);
|
|
286
|
+
|
|
287
|
+
const handleChange = useCallback(
|
|
288
|
+
(nextContent: string) => {
|
|
289
|
+
setContent(nextContent);
|
|
290
|
+
host.setContent?.(nextContent);
|
|
291
|
+
host.markDirty?.();
|
|
292
|
+
editorService.promotePreviewOnEdit(tabId);
|
|
293
|
+
},
|
|
294
|
+
[editorService, host, tabId],
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const handleSave = useCallback(
|
|
298
|
+
(nextContent: string) => {
|
|
299
|
+
if (nextContent !== content) {
|
|
300
|
+
handleChange(nextContent);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
void executeCommand(EDITOR_SAVE_COMMAND_ID);
|
|
304
|
+
},
|
|
305
|
+
[content, executeCommand, handleChange],
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const sourcePane = (
|
|
309
|
+
<div className="workbench-editor-area__source-pane">
|
|
310
|
+
<Suspense
|
|
311
|
+
fallback={
|
|
312
|
+
<div className="workbench-editor-area__editor-loading" role="status">
|
|
313
|
+
Loading editor...
|
|
314
|
+
</div>
|
|
315
|
+
}
|
|
316
|
+
>
|
|
317
|
+
<WorkspaceEditor
|
|
318
|
+
file={editorDocument}
|
|
319
|
+
showFileBar
|
|
320
|
+
showHeader={false}
|
|
321
|
+
theme={theme}
|
|
322
|
+
value={content}
|
|
323
|
+
onChange={handleChange}
|
|
324
|
+
onSave={workspaceHostPort ? handleSave : undefined}
|
|
325
|
+
/>
|
|
326
|
+
</Suspense>
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const previewRendered = previewProvider?.render({
|
|
331
|
+
document: editorDocument,
|
|
332
|
+
onContentChange: handleChange,
|
|
333
|
+
});
|
|
334
|
+
const formRendered = formProvider?.render({
|
|
335
|
+
document: editorDocument,
|
|
336
|
+
onContentChange: handleChange,
|
|
337
|
+
});
|
|
338
|
+
const previewPane = previewProvider ? (
|
|
339
|
+
<ScrollArea
|
|
340
|
+
aria-label="Preview"
|
|
341
|
+
as="section"
|
|
342
|
+
className="workbench-editor-area__preview-pane"
|
|
343
|
+
orientation="vertical"
|
|
344
|
+
>
|
|
345
|
+
{isSampleJdwWidgetPreviewRenderData(previewRendered) ? (
|
|
346
|
+
<JdwWidgetPreviewView
|
|
347
|
+
className="workbench-editor-area__jdw-preview-viewport"
|
|
348
|
+
content={content}
|
|
349
|
+
path={editorDocument.path}
|
|
350
|
+
/>
|
|
351
|
+
) : isBuiltinEditorMarkdownPreviewRenderData(previewRendered) ? (
|
|
352
|
+
<WorkbenchMarkdownPreview source={content} />
|
|
353
|
+
) : (
|
|
354
|
+
toReactNode(previewRendered)
|
|
355
|
+
)}
|
|
356
|
+
</ScrollArea>
|
|
357
|
+
) : null;
|
|
358
|
+
const formPane = formProvider ? (
|
|
359
|
+
isSampleJdwWidgetFormRenderData(formRendered) ? (
|
|
360
|
+
<JdwWidgetFormView
|
|
361
|
+
content={content}
|
|
362
|
+
path={editorDocument.path}
|
|
363
|
+
onContentChange={handleChange}
|
|
364
|
+
/>
|
|
365
|
+
) : (
|
|
366
|
+
toReactNode(formRendered)
|
|
367
|
+
)
|
|
368
|
+
) : null;
|
|
369
|
+
const paneByKind: Record<EditorPaneKind, ReactNode | null> = {
|
|
370
|
+
code: sourcePane,
|
|
371
|
+
form: formPane,
|
|
372
|
+
preview: previewPane,
|
|
373
|
+
};
|
|
374
|
+
const visiblePaneEntries = getVisibleEditorPaneKinds(paneVisibility)
|
|
375
|
+
.map((kind) => ({ kind, node: paneByKind[kind] }))
|
|
376
|
+
.filter((entry): entry is { kind: EditorPaneKind; node: ReactNode } => entry.node !== null);
|
|
377
|
+
const editorBody = composeEditorPaneLayout(visiblePaneEntries, getSplitSize, setSplitSize);
|
|
378
|
+
const modeToolbar =
|
|
379
|
+
modeToolbarVisible && modeToolbarHost
|
|
380
|
+
? createPortal(
|
|
381
|
+
<EditorViewModeIconSelect
|
|
382
|
+
codeIcon={codeIcon}
|
|
383
|
+
formEligible={formEligible}
|
|
384
|
+
previewEligible={previewEligible}
|
|
385
|
+
value={paneVisibilityToPrimaryViewMode(paneVisibility)}
|
|
386
|
+
onValueChange={(mode) => {
|
|
387
|
+
setPanePreference(editorViewModeToPaneVisibility(mode));
|
|
388
|
+
}}
|
|
389
|
+
/>,
|
|
390
|
+
modeToolbarHost,
|
|
391
|
+
)
|
|
392
|
+
: null;
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<>
|
|
396
|
+
{modeToolbar}
|
|
397
|
+
<section
|
|
398
|
+
aria-label={host.title ?? resourceUri}
|
|
399
|
+
className="workbench-editor-area__text-editor"
|
|
400
|
+
data-resource-uri={resourceUri}
|
|
401
|
+
>
|
|
402
|
+
<div className="workbench-editor-area__text-editor-body">{editorBody}</div>
|
|
403
|
+
</section>
|
|
404
|
+
</>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function composeEditorPaneLayout(
|
|
409
|
+
panes: readonly { kind: EditorPaneKind; node: ReactNode }[],
|
|
410
|
+
getSplitSize: (key: string, defaultPercent: number) => number,
|
|
411
|
+
onSplitSizeChange: (key: string, percent: number) => void,
|
|
412
|
+
): ReactNode {
|
|
413
|
+
if (panes.length === 0) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (panes.length === 1) {
|
|
418
|
+
return panes[0]?.node ?? null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const layoutSignature = panes.map((pane) => pane.kind).join('|');
|
|
422
|
+
|
|
423
|
+
return panes.slice(1).reduce<ReactNode>((primary, entry, splitIndex) => {
|
|
424
|
+
const primaryKind = panes[0]?.kind;
|
|
425
|
+
const primaryLabel = primaryKind ? editorPaneKindLabel(primaryKind) : 'Editor pane';
|
|
426
|
+
const splitKey = `${layoutSignature}#${splitIndex}`;
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<SplitView
|
|
430
|
+
key={splitKey}
|
|
431
|
+
className="workbench-editor-area__split"
|
|
432
|
+
defaultPrimarySizePercent={50}
|
|
433
|
+
minPrimarySizePercent={25}
|
|
434
|
+
primarySizePercent={getSplitSize(splitKey, 50)}
|
|
435
|
+
onPrimarySizePercentChange={(percent) => {
|
|
436
|
+
onSplitSizeChange(splitKey, percent);
|
|
437
|
+
}}
|
|
438
|
+
primary={
|
|
439
|
+
<section aria-label={primaryLabel} className="workbench-editor-area__split-pane">
|
|
440
|
+
{primary}
|
|
441
|
+
</section>
|
|
442
|
+
}
|
|
443
|
+
secondary={
|
|
444
|
+
<section
|
|
445
|
+
aria-label={editorPaneKindLabel(entry.kind)}
|
|
446
|
+
className="workbench-editor-area__split-pane"
|
|
447
|
+
>
|
|
448
|
+
{entry.node}
|
|
449
|
+
</section>
|
|
450
|
+
}
|
|
451
|
+
/>
|
|
452
|
+
);
|
|
453
|
+
}, panes[0]?.node ?? null);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function editorPaneKindLabel(kind: EditorPaneKind): string {
|
|
457
|
+
switch (kind) {
|
|
458
|
+
case 'code':
|
|
459
|
+
return 'Code';
|
|
460
|
+
case 'form':
|
|
461
|
+
return 'Form';
|
|
462
|
+
case 'preview':
|
|
463
|
+
return 'Preview';
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function EditorViewModeIconSelect({
|
|
468
|
+
codeIcon,
|
|
469
|
+
formEligible,
|
|
470
|
+
onValueChange,
|
|
471
|
+
previewEligible,
|
|
472
|
+
value,
|
|
473
|
+
}: {
|
|
474
|
+
codeIcon: string;
|
|
475
|
+
formEligible: boolean;
|
|
476
|
+
onValueChange: (mode: EditorViewMode) => void;
|
|
477
|
+
previewEligible: boolean;
|
|
478
|
+
value: EditorViewMode;
|
|
479
|
+
}) {
|
|
480
|
+
const resolvedValue =
|
|
481
|
+
(value === 'form' && !formEligible) || (value === 'preview' && !previewEligible)
|
|
482
|
+
? 'code'
|
|
483
|
+
: value;
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
<div
|
|
487
|
+
aria-label="Editor view mode"
|
|
488
|
+
className="workbench-editor-area__view-toolbar"
|
|
489
|
+
data-testid="editor-view-mode-select"
|
|
490
|
+
role="radiogroup"
|
|
491
|
+
>
|
|
492
|
+
<EditorViewModeIconButton
|
|
493
|
+
checked={resolvedValue === 'code'}
|
|
494
|
+
icon={codeIcon}
|
|
495
|
+
label="Code"
|
|
496
|
+
onSelect={() => {
|
|
497
|
+
onValueChange('code');
|
|
498
|
+
}}
|
|
499
|
+
/>
|
|
500
|
+
{formEligible ? (
|
|
501
|
+
<EditorViewModeIconButton
|
|
502
|
+
checked={resolvedValue === 'form'}
|
|
503
|
+
icon="codicon-symbol-field"
|
|
504
|
+
label="Form"
|
|
505
|
+
onSelect={() => {
|
|
506
|
+
onValueChange('form');
|
|
507
|
+
}}
|
|
508
|
+
/>
|
|
509
|
+
) : null}
|
|
510
|
+
{previewEligible ? (
|
|
511
|
+
<EditorViewModeIconButton
|
|
512
|
+
checked={resolvedValue === 'preview'}
|
|
513
|
+
icon="codicon-preview"
|
|
514
|
+
label="Preview"
|
|
515
|
+
onSelect={() => {
|
|
516
|
+
onValueChange('preview');
|
|
517
|
+
}}
|
|
518
|
+
/>
|
|
519
|
+
) : null}
|
|
520
|
+
</div>
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function EditorViewModeIconButton({
|
|
525
|
+
checked,
|
|
526
|
+
icon,
|
|
527
|
+
label,
|
|
528
|
+
onSelect,
|
|
529
|
+
}: {
|
|
530
|
+
checked: boolean;
|
|
531
|
+
icon: string;
|
|
532
|
+
label: string;
|
|
533
|
+
onSelect: () => void;
|
|
534
|
+
}) {
|
|
535
|
+
return (
|
|
536
|
+
<IconButton
|
|
537
|
+
aria-checked={checked}
|
|
538
|
+
className={[
|
|
539
|
+
'workbench-editor-area__view-button',
|
|
540
|
+
checked && 'workbench-editor-area__view-button--active',
|
|
541
|
+
]
|
|
542
|
+
.filter(Boolean)
|
|
543
|
+
.join(' ')}
|
|
544
|
+
compact
|
|
545
|
+
icon={icon}
|
|
546
|
+
label={label}
|
|
547
|
+
role="radio"
|
|
548
|
+
onClick={onSelect}
|
|
549
|
+
/>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function isJsonSourceDocument(document: { mimeType?: string | undefined; path: string }): boolean {
|
|
554
|
+
const mimeType = document.mimeType?.toLowerCase();
|
|
555
|
+
return document.path.toLowerCase().endsWith('.json') || Boolean(mimeType?.includes('json'));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
interface CommandInspectorEditorRenderPayload {
|
|
559
|
+
readonly commandId: string;
|
|
560
|
+
readonly kind: 'workbench-kit.builtin.commands/inspector';
|
|
561
|
+
readonly resourceUri: string;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function isCommandInspectorEditorRenderPayload(
|
|
565
|
+
value: unknown,
|
|
566
|
+
): value is CommandInspectorEditorRenderPayload {
|
|
567
|
+
if (typeof value !== 'object' || value === null) {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const candidate = value as Partial<CommandInspectorEditorRenderPayload>;
|
|
572
|
+
return (
|
|
573
|
+
candidate.kind === 'workbench-kit.builtin.commands/inspector' &&
|
|
574
|
+
typeof candidate.commandId === 'string' &&
|
|
575
|
+
typeof candidate.resourceUri === 'string'
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
interface FieldRemapEditorRenderPayload {
|
|
580
|
+
readonly kind: 'workbench-kit.samples.field-remap/editor';
|
|
581
|
+
readonly resourceUri: string;
|
|
582
|
+
readonly surfaceId: string;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function isFieldRemapEditorRenderPayload(value: unknown): value is FieldRemapEditorRenderPayload {
|
|
586
|
+
if (typeof value !== 'object' || value === null) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const candidate = value as Partial<FieldRemapEditorRenderPayload>;
|
|
591
|
+
return (
|
|
592
|
+
candidate.kind === 'workbench-kit.samples.field-remap/editor' &&
|
|
593
|
+
typeof candidate.resourceUri === 'string' &&
|
|
594
|
+
typeof candidate.surfaceId === 'string' &&
|
|
595
|
+
candidate.surfaceId.length > 0
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function isTextEditorRenderPayload(value: unknown): value is TextEditorRenderPayload {
|
|
600
|
+
if (typeof value !== 'object' || value === null) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const candidate = value as Partial<TextEditorRenderPayload>;
|
|
605
|
+
return (
|
|
606
|
+
candidate.kind === 'workbench-kit.builtin.editor/text' &&
|
|
607
|
+
typeof candidate.resourceUri === 'string' &&
|
|
608
|
+
typeof candidate.initialContent === 'string'
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
interface MissingResourceEditorRenderPayload {
|
|
613
|
+
readonly kind: 'workbench-kit.builtin.editor/missing-resource';
|
|
614
|
+
readonly message: string;
|
|
615
|
+
readonly resourceUri: string;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function isMissingResourceEditorRenderPayload(
|
|
619
|
+
value: unknown,
|
|
620
|
+
): value is MissingResourceEditorRenderPayload {
|
|
621
|
+
if (typeof value !== 'object' || value === null) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const candidate = value as Partial<MissingResourceEditorRenderPayload>;
|
|
626
|
+
return (
|
|
627
|
+
candidate.kind === 'workbench-kit.builtin.editor/missing-resource' &&
|
|
628
|
+
typeof candidate.resourceUri === 'string' &&
|
|
629
|
+
typeof candidate.message === 'string'
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function MissingResourceEditorSurface({
|
|
634
|
+
message,
|
|
635
|
+
resourceUri,
|
|
636
|
+
tabId,
|
|
637
|
+
title,
|
|
638
|
+
}: {
|
|
639
|
+
message: string;
|
|
640
|
+
resourceUri: string;
|
|
641
|
+
tabId: string;
|
|
642
|
+
title?: string | undefined;
|
|
643
|
+
}) {
|
|
644
|
+
const editorService = useEditorService();
|
|
645
|
+
const path = pathForResource(resourceUri);
|
|
646
|
+
|
|
647
|
+
return (
|
|
648
|
+
<section
|
|
649
|
+
aria-label={title ?? path}
|
|
650
|
+
className="workbench-editor-area__missing-resource"
|
|
651
|
+
data-resource-uri={resourceUri}
|
|
652
|
+
>
|
|
653
|
+
<div className="workbench-editor-area__missing-resource-body" role="alert">
|
|
654
|
+
<p className="workbench-editor-area__missing-resource-title">Unable to open editor</p>
|
|
655
|
+
<p className="workbench-editor-area__missing-resource-message">{message}</p>
|
|
656
|
+
<p className="workbench-editor-area__missing-resource-path">{path}</p>
|
|
657
|
+
<button
|
|
658
|
+
className="workbench-editor-area__missing-resource-close"
|
|
659
|
+
type="button"
|
|
660
|
+
onClick={() => {
|
|
661
|
+
editorService.closeEditor(tabId);
|
|
662
|
+
}}
|
|
663
|
+
>
|
|
664
|
+
Close tab
|
|
665
|
+
</button>
|
|
666
|
+
</div>
|
|
667
|
+
</section>
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function toReactNode(value: unknown): ReactNode {
|
|
672
|
+
if (
|
|
673
|
+
value === undefined ||
|
|
674
|
+
value === null ||
|
|
675
|
+
typeof value === 'boolean' ||
|
|
676
|
+
typeof value === 'symbol' ||
|
|
677
|
+
typeof value === 'function'
|
|
678
|
+
) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (typeof value === 'string' || typeof value === 'number' || isValidElement(value)) {
|
|
683
|
+
return value;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (Array.isArray(value)) {
|
|
687
|
+
return value as ReactNode;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const BUILTIN_EDITOR_MARKDOWN_PREVIEW_RENDER_KIND =
|
|
2
|
+
'workbench-kit.builtin.editor.markdown-preview' as const;
|
|
3
|
+
|
|
4
|
+
export interface BuiltinEditorMarkdownPreviewRenderData {
|
|
5
|
+
readonly kind: typeof BUILTIN_EDITOR_MARKDOWN_PREVIEW_RENDER_KIND;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isBuiltinEditorMarkdownPreviewRenderData(
|
|
9
|
+
value: unknown,
|
|
10
|
+
): value is BuiltinEditorMarkdownPreviewRenderData {
|
|
11
|
+
return (
|
|
12
|
+
typeof value === 'object' &&
|
|
13
|
+
value !== null &&
|
|
14
|
+
(value as { kind?: unknown }).kind === BUILTIN_EDITOR_MARKDOWN_PREVIEW_RENDER_KIND
|
|
15
|
+
);
|
|
16
|
+
}
|