@workbench-kit/react 0.0.1-prototype.0 → 0.0.2-prototype.0.1.1
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 +33 -9
- package/src/index.ts +545 -43
- package/src/jdw/JdwPreview.tsx +38 -0
- package/src/jdw/JsonCodeEditorPane.tsx +274 -0
- package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +29 -0
- package/src/jdw/builtins/renderBuiltinWidgetNode.tsx +126 -0
- package/src/jdw/createBuiltinJdwRegistry.ts +111 -0
- package/src/jdw/cssRenderBackend.tsx +134 -0
- package/src/jdw/document.ts +38 -0
- package/src/jdw/fixtures/jdw-fixtures.ts +52 -0
- package/src/jdw/fixtures/jdw-sample-screens.ts +522 -0
- package/src/jdw/index.ts +45 -0
- package/src/jdw/parse.ts +1 -0
- package/src/jdw/renderJdw.tsx +32 -0
- package/src/json-config/JsonConfigWorkbench.tsx +316 -0
- package/src/json-config/index.ts +6 -0
- package/src/json-config/json-config-editor-state.ts +42 -0
- package/src/layout/Panel.tsx +146 -1
- package/src/layout/SideBarViewFrame.tsx +367 -328
- package/src/layout/WorkbenchCanvas.tsx +849 -0
- package/src/layout/WorkbenchFullscreen.tsx +368 -0
- package/src/layout/WorkbenchLayout.tsx +5 -0
- package/src/layout/WorkbenchLayoutBase.tsx +294 -0
- package/src/layout/WorkbenchPropertyPanel.tsx +497 -0
- package/src/layout/WorkbenchSidebarActions.tsx +274 -0
- package/src/layout/WorkbenchTree.tsx +351 -0
- package/src/layout/layoutHelpers.ts +25 -0
- package/src/modal/ConfirmDialog.tsx +61 -53
- package/src/modal/Modal.tsx +295 -35
- package/src/overlay/ContextMenu.tsx +146 -145
- package/src/primitives/AbsoluteBox.tsx +46 -0
- package/src/primitives/Badge.tsx +12 -12
- package/src/primitives/Button.tsx +44 -14
- package/src/primitives/Checkbox.tsx +11 -5
- package/src/primitives/EmptyState.tsx +27 -26
- package/src/primitives/Field.tsx +38 -38
- package/src/primitives/IconButton.tsx +5 -2
- package/src/primitives/List.tsx +111 -0
- package/src/primitives/NumberInput.tsx +38 -0
- package/src/primitives/ScrollArea.tsx +42 -0
- package/src/primitives/Select.tsx +1 -12
- package/src/primitives/StatusBar.tsx +46 -0
- package/src/primitives/TextArea.tsx +36 -0
- package/src/primitives/TextInput.tsx +11 -2
- package/src/primitives/WorkbenchChrome.tsx +424 -0
- package/src/primitives/WorkbenchEditor.tsx +394 -0
- package/src/primitives/index.ts +105 -0
- package/src/primitives/select/Select.tsx +266 -0
- package/src/primitives/select/index.ts +2 -0
- package/src/primitives/select/options.ts +38 -0
- package/src/primitives/select/overlay.ts +63 -0
- package/src/primitives/select/select.app.css +9 -0
- package/src/primitives/select/select.css +140 -0
- package/src/primitives/select/types.ts +18 -0
- package/src/primitives/styles.css +955 -0
- package/src/scrollbars.css +69 -0
- package/src/styles.css +7738 -1762
- package/src/utils/codicon.ts +17 -0
- package/src/widget-asset/WidgetAssetEditor.tsx +140 -0
- package/src/widget-asset/WidgetAssetMetadataForm.tsx +82 -0
- package/src/widget-asset/WidgetAssetModeControls.tsx +42 -0
- package/src/widget-asset/WidgetAssetSourceEditor.tsx +50 -0
- package/src/widget-asset/WidgetAssetWorkbench.tsx +112 -0
- package/src/widget-asset/index.ts +17 -0
- package/src/widget-asset/widget-asset-document.ts +37 -0
- package/src/widget-asset/widget-asset-mode.ts +3 -0
- package/src/widget-studio/builtin-widget-asset-catalog.ts +12 -0
- package/src/widget-studio/create-widget-studio-workspace-editor.tsx +79 -0
- package/src/widget-studio/index.ts +7 -0
- package/src/widget-studio/resolve-widget-studio-asset-catalog.ts +26 -0
- package/src/widget-tree/WidgetAssetPalette.tsx +77 -0
- package/src/widget-tree/WidgetInspectorPanel.tsx +266 -0
- package/src/widget-tree/WidgetSourceEditor.tsx +56 -0
- package/src/widget-tree/WidgetTreeLab.tsx +272 -0
- package/src/widget-tree/WidgetTreeModeControls.tsx +44 -0
- package/src/widget-tree/WidgetTreeSidePanel.tsx +49 -0
- package/src/widget-tree/WidgetTreeView.tsx +97 -0
- package/src/widget-tree/WidgetTreeWorkbench.tsx +111 -0
- package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +154 -0
- package/src/widget-tree/demo-registry.ts +32 -0
- package/src/widget-tree/demo-widget-assets.ts +4 -0
- package/src/widget-tree/index.ts +27 -0
- package/src/widget-tree/widget-tree-document.ts +20 -0
- package/src/widget-tree/widget-tree-layout.ts +31 -0
- package/src/widget-tree/widget-tree-mode.ts +7 -0
- package/src/widget-tree/widget-tree-play-helpers.ts +74 -0
- package/src/workbench/ActivityBar.tsx +52 -52
- package/src/workbench/ArtifactShell.tsx +397 -0
- package/src/workbench/CommandPalette.tsx +819 -0
- package/src/workbench/ConfirmationFlow.tsx +245 -0
- package/src/workbench/MarkdownPreview.tsx +151 -0
- package/src/workbench/ShortcutCommandBridge.tsx +368 -0
- package/src/workbench/SplitView.tsx +136 -136
- package/src/workbench/StatusBar.tsx +178 -123
- package/src/workbench/StructuredArtifactEditor.tsx +205 -0
- package/src/workbench/Timeline.tsx +302 -0
- package/src/workbench/WorkbenchCanvasShell.tsx +941 -0
- package/src/workbench/WorkbenchShell.tsx +74 -70
- package/src/workbench/WorkbenchStandaloneShell.tsx +318 -291
- package/src/workbench/auth/WorkbenchAuthGate.tsx +61 -0
- package/src/workbench/auth/WorkbenchLoginView.tsx +635 -0
- package/src/workbench/auth/index.ts +19 -0
- package/src/workbench/chat/ChatComposer.tsx +165 -148
- package/src/workbench/chat/ChatMessageItem.tsx +10 -1
- package/src/workbench/chat/index.ts +9 -0
- package/src/workbench/chat/slashCommand.ts +28 -0
- package/src/workbench/chat/useChatRuntimeState.ts +76 -0
- package/src/workbench/chat/useSlashCommandSuggest.ts +101 -0
- package/src/workbench/commands.ts +520 -490
- package/src/workbench/index.ts +443 -98
- package/src/workbench/keyboard.ts +53 -0
- package/src/workbench/schema/index.ts +48 -0
- package/src/workbench/schema/workbenchDocument.ts +31 -0
- package/src/workbench/schema/workbenchDocumentActions.ts +6 -0
- package/src/workbench/schema/workbenchDocumentAdapter.ts +9 -0
- package/src/workbench/schema/workbenchDocumentPatch.ts +13 -0
- package/src/workbench/schema/workbenchDocumentRenderer.tsx +702 -0
- package/src/workbench/settings/NavigationPanel.tsx +47 -0
- package/src/workbench/settings/SchemaForm.tsx +420 -0
- package/src/workbench/settings/SectionedPanel.tsx +415 -0
- package/src/workbench/settings/StructuredDataForm.tsx +539 -0
- package/src/workbench/settings/StructuredDataSchemaPanel.tsx +559 -0
- package/src/workbench/settings/StructuredDataSchemaPanelEmbed.tsx +21 -0
- package/src/workbench/settings/StructuredDataSchemaPanelFrame.tsx +76 -0
- package/src/workbench/settings/StructuredDataTableView.tsx +127 -0
- package/src/workbench/settings/WorkbenchPanelRegion.tsx +46 -0
- package/src/workbench/settings/WorkbenchSettingsModal.tsx +21 -12
- package/src/workbench/settings/WorkbenchSettingsNav.tsx +38 -22
- package/src/workbench/settings/index.ts +135 -0
- package/src/workbench/settings/panel-region.css +21 -0
- package/src/workbench/settings/sectionedPanelScrollSpy.ts +143 -0
- package/src/workbench/settings/structured-data-schema-panel.css +330 -0
- package/src/workbench/settings/structured-data-table-view.css +85 -0
- package/src/workbench/settings/structuredDataSchema.ts +886 -0
- package/src/workbench/shell.ts +15 -0
- package/src/workbench/shellState.ts +203 -203
- package/src/workbench/standalone.ts +101 -94
- package/src/workbench/status.ts +129 -0
- package/src/workbench/theme.ts +44 -0
- package/src/workbench/workspace/MultiProviderExplorer.tsx +625 -0
- package/src/workbench/workspace/WorkspaceDraftsContext.tsx +111 -0
- package/src/workbench/workspace/WorkspaceEditor.tsx +47 -12
- package/src/workbench/workspace/WorkspaceEditorPanel.tsx +334 -353
- package/src/workbench/workspace/WorkspaceExplorer.tsx +532 -524
- package/src/workbench/workspace/WorkspaceFileIcon.tsx +32 -2
- package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -113
- package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -47
- package/src/workbench/workspace/index.ts +156 -98
- package/src/workbench/workspace/mimeType.ts +45 -0
- package/src/workbench/workspace/path.ts +10 -10
- package/src/workbench/workspace/search.ts +6 -6
- package/src/workbench/workspace/tree.ts +1 -1
- package/src/workbench/workspace/types.ts +10 -10
- package/src/workbench/workspace/useVirtualWorkspace.ts +111 -98
- package/src/workbench/workspace/workspaceJsonDiagnostics.ts +83 -0
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
-
import { cx } from '../utils/cx';
|
|
3
|
-
|
|
4
|
-
export interface ActivityBarItem {
|
|
5
|
-
active?: boolean;
|
|
6
|
-
disabled?: boolean;
|
|
7
|
-
icon: ReactNode;
|
|
8
|
-
id: string;
|
|
9
|
-
label: string;
|
|
10
|
-
title?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ActivityBarProps extends Omit<ComponentPropsWithoutRef<'nav'>, 'children'> {
|
|
14
|
-
items: ActivityBarItem[];
|
|
15
|
-
onItemActivate?: (item: ActivityBarItem) => void;
|
|
16
|
-
secondaryItems?: ActivityBarItem[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function ActivityBar({
|
|
20
|
-
'aria-label': ariaLabel = 'Activity bar',
|
|
21
|
-
className,
|
|
22
|
-
items,
|
|
23
|
-
onItemActivate,
|
|
24
|
-
secondaryItems = [],
|
|
25
|
-
...props
|
|
26
|
-
}: ActivityBarProps) {
|
|
27
|
-
const renderItem = (item: ActivityBarItem) => (
|
|
28
|
-
<button
|
|
29
|
-
key={item.id}
|
|
30
|
-
type="button"
|
|
31
|
-
aria-label={item.label}
|
|
32
|
-
aria-pressed={item.active}
|
|
33
|
-
className={cx(
|
|
34
|
-
'ui-workbench-activity-bar__item',
|
|
35
|
-
item.active && 'ui-workbench-activity-bar__item--active',
|
|
36
|
-
)}
|
|
37
|
-
disabled={item.disabled}
|
|
38
|
-
title={item.title ?? item.label}
|
|
39
|
-
onClick={() => onItemActivate?.(item)}
|
|
40
|
-
>
|
|
41
|
-
<span className="ui-workbench-activity-bar__icon">{item.icon}</span>
|
|
42
|
-
</button>
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<nav aria-label={ariaLabel} className={cx('ui-workbench-activity-bar', className)} {...props}>
|
|
47
|
-
{items.map(renderItem)}
|
|
48
|
-
{secondaryItems.length ? <span className="ui-workbench-activity-bar__spacer" /> : null}
|
|
49
|
-
{secondaryItems.map(renderItem)}
|
|
50
|
-
</nav>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
import { cx } from '../utils/cx';
|
|
3
|
+
|
|
4
|
+
export interface ActivityBarItem {
|
|
5
|
+
active?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
icon: ReactNode;
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ActivityBarProps extends Omit<ComponentPropsWithoutRef<'nav'>, 'children'> {
|
|
14
|
+
items: ActivityBarItem[];
|
|
15
|
+
onItemActivate?: (item: ActivityBarItem) => void;
|
|
16
|
+
secondaryItems?: ActivityBarItem[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ActivityBar({
|
|
20
|
+
'aria-label': ariaLabel = 'Activity bar',
|
|
21
|
+
className,
|
|
22
|
+
items,
|
|
23
|
+
onItemActivate,
|
|
24
|
+
secondaryItems = [],
|
|
25
|
+
...props
|
|
26
|
+
}: ActivityBarProps) {
|
|
27
|
+
const renderItem = (item: ActivityBarItem) => (
|
|
28
|
+
<button
|
|
29
|
+
key={item.id}
|
|
30
|
+
type="button"
|
|
31
|
+
aria-label={item.label}
|
|
32
|
+
aria-pressed={item.active}
|
|
33
|
+
className={cx(
|
|
34
|
+
'ui-workbench-activity-bar__item',
|
|
35
|
+
item.active && 'ui-workbench-activity-bar__item--active',
|
|
36
|
+
)}
|
|
37
|
+
disabled={item.disabled}
|
|
38
|
+
title={item.title ?? item.label}
|
|
39
|
+
onClick={() => onItemActivate?.(item)}
|
|
40
|
+
>
|
|
41
|
+
<span className="ui-workbench-activity-bar__icon">{item.icon}</span>
|
|
42
|
+
</button>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<nav aria-label={ariaLabel} className={cx('ui-workbench-activity-bar', className)} {...props}>
|
|
47
|
+
{items.map(renderItem)}
|
|
48
|
+
{secondaryItems.length ? <span className="ui-workbench-activity-bar__spacer" /> : null}
|
|
49
|
+
{secondaryItems.map(renderItem)}
|
|
50
|
+
</nav>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { useState, type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
|
+
import { EmptyState } from '../primitives/EmptyState';
|
|
3
|
+
import { IconButton } from '../primitives/IconButton';
|
|
4
|
+
import { cx } from '../utils/cx';
|
|
5
|
+
import { SplitView } from './SplitView';
|
|
6
|
+
|
|
7
|
+
export type WorkbenchArtifactMode = 'code' | 'preview' | 'split';
|
|
8
|
+
|
|
9
|
+
export interface WorkbenchArtifactDescriptor {
|
|
10
|
+
artifactKind?: string;
|
|
11
|
+
content?: unknown;
|
|
12
|
+
extension?: string;
|
|
13
|
+
id: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
mimeType?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
title?: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type WorkbenchPreviewRendererMatchReason =
|
|
22
|
+
| 'artifact-kind'
|
|
23
|
+
| 'custom'
|
|
24
|
+
| 'extension'
|
|
25
|
+
| 'fallback'
|
|
26
|
+
| 'mime-type';
|
|
27
|
+
|
|
28
|
+
export interface WorkbenchPreviewRendererMatch {
|
|
29
|
+
priority: number;
|
|
30
|
+
reason: WorkbenchPreviewRendererMatchReason;
|
|
31
|
+
renderer: WorkbenchPreviewRenderer;
|
|
32
|
+
score: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WorkbenchPreviewRendererContext {
|
|
36
|
+
artifact: WorkbenchArtifactDescriptor;
|
|
37
|
+
match?: WorkbenchPreviewRendererMatch | undefined;
|
|
38
|
+
renderer?: WorkbenchPreviewRenderer | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WorkbenchPreviewRenderer {
|
|
42
|
+
artifactKinds?: readonly string[];
|
|
43
|
+
canRender?: (artifact: WorkbenchArtifactDescriptor) => boolean;
|
|
44
|
+
extensions?: readonly string[];
|
|
45
|
+
fallback?: boolean;
|
|
46
|
+
id: string;
|
|
47
|
+
label?: string;
|
|
48
|
+
mimeTypes?: readonly string[];
|
|
49
|
+
priority?: number;
|
|
50
|
+
render: (
|
|
51
|
+
artifact: WorkbenchArtifactDescriptor,
|
|
52
|
+
context: WorkbenchPreviewRendererContext,
|
|
53
|
+
) => ReactNode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type WorkbenchArtifactShellRenderCode = (artifact: WorkbenchArtifactDescriptor) => ReactNode;
|
|
57
|
+
|
|
58
|
+
export type WorkbenchArtifactShellRenderPreview = (
|
|
59
|
+
artifact: WorkbenchArtifactDescriptor,
|
|
60
|
+
context: WorkbenchPreviewRendererContext,
|
|
61
|
+
) => ReactNode;
|
|
62
|
+
|
|
63
|
+
export interface WorkbenchArtifactModeControlsProps extends ComponentPropsWithRef<'div'> {
|
|
64
|
+
codeLabel?: string | undefined;
|
|
65
|
+
mode: WorkbenchArtifactMode;
|
|
66
|
+
onModeChange: (mode: WorkbenchArtifactMode) => void;
|
|
67
|
+
previewLabel?: string | undefined;
|
|
68
|
+
splitLabel?: string | undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeToken(value: string | undefined) {
|
|
72
|
+
return value?.trim().toLocaleLowerCase() ?? '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeExtension(value: string | undefined) {
|
|
76
|
+
const normalized = normalizeToken(value);
|
|
77
|
+
return normalized.startsWith('.') ? normalized.slice(1) : normalized;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extensionFromPath(path: string | undefined) {
|
|
81
|
+
const fileName = path?.split(/[\\/]/).pop() ?? '';
|
|
82
|
+
const dotIndex = fileName.lastIndexOf('.');
|
|
83
|
+
return dotIndex > 0 ? fileName.slice(dotIndex + 1) : '';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function matchesMimeType(candidate: string, mimeType: string) {
|
|
87
|
+
const normalizedCandidate = normalizeToken(candidate);
|
|
88
|
+
const normalizedMimeType = normalizeToken(mimeType);
|
|
89
|
+
if (!normalizedCandidate || !normalizedMimeType) return false;
|
|
90
|
+
if (normalizedCandidate === normalizedMimeType) return true;
|
|
91
|
+
if (!normalizedCandidate.endsWith('/*')) return false;
|
|
92
|
+
|
|
93
|
+
return normalizedMimeType.startsWith(normalizedCandidate.slice(0, -1));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getWorkbenchArtifactExtension(artifact: WorkbenchArtifactDescriptor) {
|
|
97
|
+
return (
|
|
98
|
+
normalizeExtension(artifact.extension) || normalizeExtension(extensionFromPath(artifact.path))
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getWorkbenchArtifactTitle(artifact: WorkbenchArtifactDescriptor): ReactNode {
|
|
103
|
+
return artifact.title ?? artifact.name ?? artifact.path ?? artifact.id;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function formatWorkbenchArtifactContent(content: unknown): string {
|
|
107
|
+
if (content === undefined || content === null) return '';
|
|
108
|
+
if (typeof content === 'string') return content;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
return JSON.stringify(content, null, 2);
|
|
112
|
+
} catch {
|
|
113
|
+
return String(content);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getWorkbenchPreviewRendererMatch(
|
|
118
|
+
artifact: WorkbenchArtifactDescriptor,
|
|
119
|
+
renderer: WorkbenchPreviewRenderer,
|
|
120
|
+
): WorkbenchPreviewRendererMatch | undefined {
|
|
121
|
+
const customMatch = renderer.canRender?.(artifact);
|
|
122
|
+
if (customMatch === false) return undefined;
|
|
123
|
+
|
|
124
|
+
const priority = renderer.priority ?? 0;
|
|
125
|
+
const artifactKind = normalizeToken(artifact.artifactKind);
|
|
126
|
+
const mimeType = normalizeToken(artifact.mimeType);
|
|
127
|
+
const extension = getWorkbenchArtifactExtension(artifact);
|
|
128
|
+
|
|
129
|
+
if (customMatch) {
|
|
130
|
+
return { priority, reason: 'custom', renderer, score: 100 };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
artifactKind &&
|
|
135
|
+
renderer.artifactKinds?.some((candidate) => normalizeToken(candidate) === artifactKind)
|
|
136
|
+
) {
|
|
137
|
+
return { priority, reason: 'artifact-kind', renderer, score: 80 };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (mimeType && renderer.mimeTypes?.some((candidate) => matchesMimeType(candidate, mimeType))) {
|
|
141
|
+
return { priority, reason: 'mime-type', renderer, score: 70 };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
extension &&
|
|
146
|
+
renderer.extensions?.some((candidate) => normalizeExtension(candidate) === extension)
|
|
147
|
+
) {
|
|
148
|
+
return { priority, reason: 'extension', renderer, score: 60 };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (renderer.fallback) {
|
|
152
|
+
return { priority, reason: 'fallback', renderer, score: 1 };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function selectWorkbenchPreviewRenderer(
|
|
159
|
+
artifact: WorkbenchArtifactDescriptor,
|
|
160
|
+
renderers: readonly WorkbenchPreviewRenderer[],
|
|
161
|
+
): WorkbenchPreviewRendererMatch | undefined {
|
|
162
|
+
return renderers
|
|
163
|
+
.map((renderer) => getWorkbenchPreviewRendererMatch(artifact, renderer))
|
|
164
|
+
.filter((match): match is WorkbenchPreviewRendererMatch => Boolean(match))
|
|
165
|
+
.sort(
|
|
166
|
+
(left, right) =>
|
|
167
|
+
right.score - left.score ||
|
|
168
|
+
right.priority - left.priority ||
|
|
169
|
+
left.renderer.id.localeCompare(right.renderer.id),
|
|
170
|
+
)[0];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function getWorkbenchPreviewRenderer(
|
|
174
|
+
artifact: WorkbenchArtifactDescriptor,
|
|
175
|
+
renderers: readonly WorkbenchPreviewRenderer[],
|
|
176
|
+
) {
|
|
177
|
+
return selectWorkbenchPreviewRenderer(artifact, renderers)?.renderer;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function defaultCodePane(artifact: WorkbenchArtifactDescriptor) {
|
|
181
|
+
return (
|
|
182
|
+
<pre className="ui-workbench-artifact-shell__code ui-workbench-scrollbar">
|
|
183
|
+
<code>{formatWorkbenchArtifactContent(artifact.content)}</code>
|
|
184
|
+
</pre>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function WorkbenchArtifactModeControls({
|
|
189
|
+
className,
|
|
190
|
+
codeLabel = 'Code',
|
|
191
|
+
mode,
|
|
192
|
+
onModeChange,
|
|
193
|
+
previewLabel = 'Preview',
|
|
194
|
+
splitLabel = 'Split',
|
|
195
|
+
...props
|
|
196
|
+
}: WorkbenchArtifactModeControlsProps) {
|
|
197
|
+
return (
|
|
198
|
+
<div className={cx('ui-workbench-artifact-shell__modes', className)} {...props}>
|
|
199
|
+
<IconButton
|
|
200
|
+
aria-pressed={mode === 'code'}
|
|
201
|
+
className={cx(
|
|
202
|
+
'ui-workbench-artifact-shell__mode',
|
|
203
|
+
mode === 'code' && 'ui-workbench-artifact-shell__mode--active',
|
|
204
|
+
)}
|
|
205
|
+
icon="codicon-code"
|
|
206
|
+
label={codeLabel}
|
|
207
|
+
onClick={() => onModeChange('code')}
|
|
208
|
+
/>
|
|
209
|
+
<IconButton
|
|
210
|
+
aria-pressed={mode === 'preview'}
|
|
211
|
+
className={cx(
|
|
212
|
+
'ui-workbench-artifact-shell__mode',
|
|
213
|
+
mode === 'preview' && 'ui-workbench-artifact-shell__mode--active',
|
|
214
|
+
)}
|
|
215
|
+
icon="codicon-open-preview"
|
|
216
|
+
label={previewLabel}
|
|
217
|
+
onClick={() => onModeChange('preview')}
|
|
218
|
+
/>
|
|
219
|
+
<IconButton
|
|
220
|
+
aria-pressed={mode === 'split'}
|
|
221
|
+
className={cx(
|
|
222
|
+
'ui-workbench-artifact-shell__mode',
|
|
223
|
+
mode === 'split' && 'ui-workbench-artifact-shell__mode--active',
|
|
224
|
+
)}
|
|
225
|
+
icon="codicon-split-horizontal"
|
|
226
|
+
label={splitLabel}
|
|
227
|
+
onClick={() => onModeChange('split')}
|
|
228
|
+
/>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface WorkbenchArtifactPreviewProps extends ComponentPropsWithRef<'div'> {
|
|
234
|
+
artifact: WorkbenchArtifactDescriptor;
|
|
235
|
+
renderPreview?: WorkbenchArtifactShellRenderPreview | undefined;
|
|
236
|
+
renderers?: readonly WorkbenchPreviewRenderer[] | undefined;
|
|
237
|
+
unsupportedLabel?: ReactNode | undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function WorkbenchArtifactPreview({
|
|
241
|
+
artifact,
|
|
242
|
+
className,
|
|
243
|
+
renderPreview,
|
|
244
|
+
renderers = [],
|
|
245
|
+
unsupportedLabel = 'Preview is not available for this artifact',
|
|
246
|
+
...props
|
|
247
|
+
}: WorkbenchArtifactPreviewProps) {
|
|
248
|
+
const match = selectWorkbenchPreviewRenderer(artifact, renderers);
|
|
249
|
+
const context: WorkbenchPreviewRendererContext = {
|
|
250
|
+
artifact,
|
|
251
|
+
match,
|
|
252
|
+
renderer: match?.renderer,
|
|
253
|
+
};
|
|
254
|
+
const preview =
|
|
255
|
+
renderPreview !== undefined
|
|
256
|
+
? renderPreview(artifact, context)
|
|
257
|
+
: match?.renderer.render(artifact, context);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div className={cx('ui-workbench-artifact-preview', className)} {...props}>
|
|
261
|
+
{preview !== undefined && preview !== null ? (
|
|
262
|
+
preview
|
|
263
|
+
) : (
|
|
264
|
+
<EmptyState compact icon="codicon-open-preview">
|
|
265
|
+
{unsupportedLabel}
|
|
266
|
+
</EmptyState>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface WorkbenchArtifactShellProps extends Omit<
|
|
273
|
+
ComponentPropsWithRef<'div'>,
|
|
274
|
+
'children'
|
|
275
|
+
> {
|
|
276
|
+
artifact?: WorkbenchArtifactDescriptor | undefined;
|
|
277
|
+
codeLabel?: string | undefined;
|
|
278
|
+
defaultMode?: WorkbenchArtifactMode | undefined;
|
|
279
|
+
emptyLabel?: ReactNode | undefined;
|
|
280
|
+
mode?: WorkbenchArtifactMode | undefined;
|
|
281
|
+
onModeChange?: ((mode: WorkbenchArtifactMode) => void) | undefined;
|
|
282
|
+
previewLabel?: string | undefined;
|
|
283
|
+
previewRenderers?: readonly WorkbenchPreviewRenderer[] | undefined;
|
|
284
|
+
renderCode?: WorkbenchArtifactShellRenderCode | undefined;
|
|
285
|
+
renderPreview?: WorkbenchArtifactShellRenderPreview | undefined;
|
|
286
|
+
showHeader?: boolean | undefined;
|
|
287
|
+
splitLabel?: string | undefined;
|
|
288
|
+
unsupportedPreviewLabel?: ReactNode | undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function WorkbenchArtifactShell({
|
|
292
|
+
artifact,
|
|
293
|
+
className,
|
|
294
|
+
codeLabel = 'Code',
|
|
295
|
+
defaultMode = 'code',
|
|
296
|
+
emptyLabel = 'No artifact selected',
|
|
297
|
+
mode,
|
|
298
|
+
onModeChange,
|
|
299
|
+
previewLabel = 'Preview',
|
|
300
|
+
previewRenderers,
|
|
301
|
+
renderCode,
|
|
302
|
+
renderPreview,
|
|
303
|
+
showHeader = true,
|
|
304
|
+
splitLabel = 'Split',
|
|
305
|
+
unsupportedPreviewLabel,
|
|
306
|
+
...props
|
|
307
|
+
}: WorkbenchArtifactShellProps) {
|
|
308
|
+
const [uncontrolledMode, setUncontrolledMode] = useState(defaultMode);
|
|
309
|
+
const resolvedMode = mode ?? uncontrolledMode;
|
|
310
|
+
|
|
311
|
+
const setMode = (nextMode: WorkbenchArtifactMode) => {
|
|
312
|
+
if (mode === undefined) {
|
|
313
|
+
setUncontrolledMode(nextMode);
|
|
314
|
+
}
|
|
315
|
+
onModeChange?.(nextMode);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
if (!artifact) {
|
|
319
|
+
return (
|
|
320
|
+
<div className={cx('ui-workbench-artifact-shell', className)} {...props}>
|
|
321
|
+
<EmptyState icon="codicon-open-preview">{emptyLabel}</EmptyState>
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const codePane = renderCode ? renderCode(artifact) : defaultCodePane(artifact);
|
|
327
|
+
const previewPane = (
|
|
328
|
+
<WorkbenchArtifactPreview
|
|
329
|
+
artifact={artifact}
|
|
330
|
+
renderPreview={renderPreview}
|
|
331
|
+
renderers={previewRenderers}
|
|
332
|
+
unsupportedLabel={unsupportedPreviewLabel}
|
|
333
|
+
/>
|
|
334
|
+
);
|
|
335
|
+
const title = getWorkbenchArtifactTitle(artifact);
|
|
336
|
+
const meta =
|
|
337
|
+
artifact.mimeType ?? artifact.artifactKind ?? getWorkbenchArtifactExtension(artifact);
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div
|
|
341
|
+
className={cx('ui-workbench-artifact-shell', className)}
|
|
342
|
+
data-header={showHeader ? 'visible' : 'hidden'}
|
|
343
|
+
data-mode={resolvedMode}
|
|
344
|
+
{...props}
|
|
345
|
+
>
|
|
346
|
+
{showHeader ? (
|
|
347
|
+
<div className="ui-workbench-artifact-shell__header">
|
|
348
|
+
<div className="ui-workbench-artifact-shell__title-group">
|
|
349
|
+
<span className="ui-workbench-artifact-shell__title">{title}</span>
|
|
350
|
+
{artifact.path ? (
|
|
351
|
+
<span className="ui-workbench-artifact-shell__path">{artifact.path}</span>
|
|
352
|
+
) : null}
|
|
353
|
+
</div>
|
|
354
|
+
<div className="ui-workbench-artifact-shell__meta">
|
|
355
|
+
{meta ? <span className="ui-workbench-artifact-shell__chip">{meta}</span> : null}
|
|
356
|
+
<WorkbenchArtifactModeControls
|
|
357
|
+
codeLabel={codeLabel}
|
|
358
|
+
mode={resolvedMode}
|
|
359
|
+
previewLabel={previewLabel}
|
|
360
|
+
splitLabel={splitLabel}
|
|
361
|
+
onModeChange={setMode}
|
|
362
|
+
/>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
) : null}
|
|
366
|
+
<div className="ui-workbench-artifact-shell__body">
|
|
367
|
+
{resolvedMode === 'code' ? (
|
|
368
|
+
<section className="ui-workbench-artifact-shell__pane" aria-label={codeLabel}>
|
|
369
|
+
{codePane}
|
|
370
|
+
</section>
|
|
371
|
+
) : null}
|
|
372
|
+
{resolvedMode === 'preview' ? (
|
|
373
|
+
<section className="ui-workbench-artifact-shell__pane" aria-label={previewLabel}>
|
|
374
|
+
{previewPane}
|
|
375
|
+
</section>
|
|
376
|
+
) : null}
|
|
377
|
+
{resolvedMode === 'split' ? (
|
|
378
|
+
<SplitView
|
|
379
|
+
className="ui-workbench-artifact-shell__split"
|
|
380
|
+
defaultPrimarySizePercent={50}
|
|
381
|
+
minPrimarySizePercent={20}
|
|
382
|
+
primary={
|
|
383
|
+
<section className="ui-workbench-artifact-shell__pane" aria-label={codeLabel}>
|
|
384
|
+
{codePane}
|
|
385
|
+
</section>
|
|
386
|
+
}
|
|
387
|
+
secondary={
|
|
388
|
+
<section className="ui-workbench-artifact-shell__pane" aria-label={previewLabel}>
|
|
389
|
+
{previewPane}
|
|
390
|
+
</section>
|
|
391
|
+
}
|
|
392
|
+
/>
|
|
393
|
+
) : null}
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
}
|