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