@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
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useMemo,
|
|
3
|
+
useState,
|
|
4
|
+
type ComponentPropsWithRef,
|
|
5
|
+
type MouseEvent,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { WorkbenchSidebarSection } from '../../layout/WorkbenchSidebarActions';
|
|
9
|
+
import { SideBarList, SideBarListItem } from '../../layout/SideBarViewFrame';
|
|
10
|
+
import { EmptyState } from '../../primitives/EmptyState';
|
|
11
|
+
import { IconButton } from '../../primitives/IconButton';
|
|
12
|
+
import { cxCodicon } from '../../utils/codicon';
|
|
13
|
+
import { cx } from '../../utils/cx';
|
|
14
|
+
import {
|
|
15
|
+
getWorkbenchStatusLabel,
|
|
16
|
+
isWorkbenchStatusBusy,
|
|
17
|
+
isWorkbenchStatusDisabled,
|
|
18
|
+
type WorkbenchStatus,
|
|
19
|
+
} from '../status';
|
|
20
|
+
|
|
21
|
+
export type WorkbenchExplorerProviderKind =
|
|
22
|
+
| 'artifacts'
|
|
23
|
+
| 'config'
|
|
24
|
+
| 'files'
|
|
25
|
+
| 'session'
|
|
26
|
+
| 'state'
|
|
27
|
+
| 'virtual'
|
|
28
|
+
| (string & {});
|
|
29
|
+
|
|
30
|
+
export type WorkbenchExplorerEntryKind =
|
|
31
|
+
| 'artifact'
|
|
32
|
+
| 'config'
|
|
33
|
+
| 'file'
|
|
34
|
+
| 'folder'
|
|
35
|
+
| 'group'
|
|
36
|
+
| 'session'
|
|
37
|
+
| 'state'
|
|
38
|
+
| 'virtual'
|
|
39
|
+
| (string & {});
|
|
40
|
+
|
|
41
|
+
export interface WorkbenchExplorerActionDescriptor {
|
|
42
|
+
danger?: boolean;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
disabledReason?: ReactNode;
|
|
45
|
+
icon?: string;
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
status?: WorkbenchStatus;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WorkbenchExplorerEntryDescriptor {
|
|
53
|
+
children?: readonly WorkbenchExplorerEntryDescriptor[];
|
|
54
|
+
description?: ReactNode;
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
disabledReason?: ReactNode;
|
|
57
|
+
icon?: string;
|
|
58
|
+
id: string;
|
|
59
|
+
kind?: WorkbenchExplorerEntryKind;
|
|
60
|
+
label: ReactNode;
|
|
61
|
+
metadata?: Record<string, unknown>;
|
|
62
|
+
path?: string;
|
|
63
|
+
selectable?: boolean;
|
|
64
|
+
status?: WorkbenchStatus;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface WorkbenchExplorerProviderDescriptor {
|
|
68
|
+
actions?: readonly WorkbenchExplorerActionDescriptor[];
|
|
69
|
+
collapsible?: boolean;
|
|
70
|
+
collapsed?: boolean;
|
|
71
|
+
count?: number;
|
|
72
|
+
defaultCollapsed?: boolean;
|
|
73
|
+
description?: ReactNode;
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
disabledReason?: ReactNode;
|
|
76
|
+
emptyLabel?: ReactNode;
|
|
77
|
+
entries?: readonly WorkbenchExplorerEntryDescriptor[];
|
|
78
|
+
icon?: string;
|
|
79
|
+
id: string;
|
|
80
|
+
kind?: WorkbenchExplorerProviderKind;
|
|
81
|
+
label: ReactNode;
|
|
82
|
+
metadata?: Record<string, unknown>;
|
|
83
|
+
status?: WorkbenchStatus;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface WorkbenchExplorerEntryRef {
|
|
87
|
+
entryId: string;
|
|
88
|
+
providerId: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface WorkbenchExplorerFlattenedEntry {
|
|
92
|
+
depth: number;
|
|
93
|
+
entry: WorkbenchExplorerEntryDescriptor;
|
|
94
|
+
entryId: string;
|
|
95
|
+
key: string;
|
|
96
|
+
parentEntryIds: readonly string[];
|
|
97
|
+
provider: WorkbenchExplorerProviderDescriptor;
|
|
98
|
+
providerId: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface WorkbenchExplorerEntryContext extends WorkbenchExplorerFlattenedEntry {
|
|
102
|
+
active: boolean;
|
|
103
|
+
expanded: boolean;
|
|
104
|
+
selected: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface WorkbenchExplorerEntryContextInput extends WorkbenchExplorerFlattenedEntry {
|
|
108
|
+
activeEntry?: WorkbenchExplorerEntryRef;
|
|
109
|
+
expandedEntries?: Iterable<WorkbenchExplorerEntryRef>;
|
|
110
|
+
selectedEntries?: Iterable<WorkbenchExplorerEntryRef>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface WorkbenchExplorerEntrySelectContext extends WorkbenchExplorerEntryContext {
|
|
114
|
+
event: MouseEvent<HTMLButtonElement>;
|
|
115
|
+
reason: 'click';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface WorkbenchExplorerEntryToggleContext extends WorkbenchExplorerEntryContext {
|
|
119
|
+
event: MouseEvent<HTMLButtonElement>;
|
|
120
|
+
nextExpanded: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface WorkbenchExplorerProviderActionContext {
|
|
124
|
+
action: WorkbenchExplorerActionDescriptor;
|
|
125
|
+
event: MouseEvent<HTMLButtonElement>;
|
|
126
|
+
provider: WorkbenchExplorerProviderDescriptor;
|
|
127
|
+
providerId: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface WorkbenchExplorerProviderCollapseContext {
|
|
131
|
+
collapsed: boolean;
|
|
132
|
+
provider: WorkbenchExplorerProviderDescriptor;
|
|
133
|
+
providerId: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface WorkbenchMultiProviderExplorerProps extends Omit<
|
|
137
|
+
ComponentPropsWithRef<'div'>,
|
|
138
|
+
'children' | 'onSelect'
|
|
139
|
+
> {
|
|
140
|
+
activeEntry?: WorkbenchExplorerEntryRef;
|
|
141
|
+
defaultExpandedEntries?: Iterable<WorkbenchExplorerEntryRef>;
|
|
142
|
+
emptyLabel?: ReactNode;
|
|
143
|
+
expandedEntries?: Iterable<WorkbenchExplorerEntryRef>;
|
|
144
|
+
onEntrySelect?: (
|
|
145
|
+
entry: WorkbenchExplorerEntryDescriptor,
|
|
146
|
+
context: WorkbenchExplorerEntrySelectContext,
|
|
147
|
+
) => void;
|
|
148
|
+
onEntryToggle?: (
|
|
149
|
+
entry: WorkbenchExplorerEntryDescriptor,
|
|
150
|
+
context: WorkbenchExplorerEntryToggleContext,
|
|
151
|
+
) => void;
|
|
152
|
+
onExpandedEntriesChange?: (
|
|
153
|
+
expandedEntries: WorkbenchExplorerEntryRef[],
|
|
154
|
+
context: WorkbenchExplorerEntryToggleContext,
|
|
155
|
+
) => void;
|
|
156
|
+
onProviderAction?: (
|
|
157
|
+
action: WorkbenchExplorerActionDescriptor,
|
|
158
|
+
context: WorkbenchExplorerProviderActionContext,
|
|
159
|
+
) => void;
|
|
160
|
+
onProviderCollapsedChange?: (
|
|
161
|
+
provider: WorkbenchExplorerProviderDescriptor,
|
|
162
|
+
context: WorkbenchExplorerProviderCollapseContext,
|
|
163
|
+
) => void;
|
|
164
|
+
providers: readonly WorkbenchExplorerProviderDescriptor[];
|
|
165
|
+
selectedEntries?: Iterable<WorkbenchExplorerEntryRef>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getWorkbenchExplorerEntryKey({ entryId, providerId }: WorkbenchExplorerEntryRef) {
|
|
169
|
+
return `${encodeURIComponent(providerId)}:${encodeURIComponent(entryId)}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function normalizeWorkbenchExplorerEntryKeys(
|
|
173
|
+
entries: Iterable<WorkbenchExplorerEntryRef> = [],
|
|
174
|
+
) {
|
|
175
|
+
const keys = new Set<string>();
|
|
176
|
+
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
keys.add(getWorkbenchExplorerEntryKey(entry));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return keys;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function countWorkbenchExplorerEntries(
|
|
185
|
+
entries: readonly WorkbenchExplorerEntryDescriptor[] = [],
|
|
186
|
+
): number {
|
|
187
|
+
return entries.reduce(
|
|
188
|
+
(count, entry) => count + 1 + countWorkbenchExplorerEntries(entry.children),
|
|
189
|
+
0,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getWorkbenchExplorerProviderEntryCount(
|
|
194
|
+
provider: WorkbenchExplorerProviderDescriptor,
|
|
195
|
+
) {
|
|
196
|
+
return provider.count ?? countWorkbenchExplorerEntries(provider.entries);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function isWorkbenchExplorerProviderDisabled(provider: WorkbenchExplorerProviderDescriptor) {
|
|
200
|
+
return Boolean(
|
|
201
|
+
provider.disabled || (provider.status && isWorkbenchStatusDisabled(provider.status)),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function isWorkbenchExplorerEntryDisabled(entry: WorkbenchExplorerEntryDescriptor) {
|
|
206
|
+
return Boolean(entry.disabled || (entry.status && isWorkbenchStatusDisabled(entry.status)));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function isWorkbenchExplorerActionDisabled(
|
|
210
|
+
action: WorkbenchExplorerActionDescriptor,
|
|
211
|
+
providerDisabled = false,
|
|
212
|
+
) {
|
|
213
|
+
return Boolean(
|
|
214
|
+
providerDisabled ||
|
|
215
|
+
action.disabled ||
|
|
216
|
+
(action.status && isWorkbenchStatusDisabled(action.status)),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function isWorkbenchExplorerEntrySelectable(entry: WorkbenchExplorerEntryDescriptor) {
|
|
221
|
+
return entry.selectable ?? !entry.children?.length;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function collectWorkbenchExplorerEntryRefs(
|
|
225
|
+
providers: readonly WorkbenchExplorerProviderDescriptor[],
|
|
226
|
+
): WorkbenchExplorerEntryRef[] {
|
|
227
|
+
const refs: WorkbenchExplorerEntryRef[] = [];
|
|
228
|
+
|
|
229
|
+
const collectEntries = (
|
|
230
|
+
providerId: string,
|
|
231
|
+
entries: readonly WorkbenchExplorerEntryDescriptor[] = [],
|
|
232
|
+
) => {
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
refs.push({ entryId: entry.id, providerId });
|
|
235
|
+
collectEntries(providerId, entry.children);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
for (const provider of providers) {
|
|
240
|
+
collectEntries(provider.id, provider.entries);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return refs;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function flattenWorkbenchExplorerProviders(
|
|
247
|
+
providers: readonly WorkbenchExplorerProviderDescriptor[],
|
|
248
|
+
expandedEntries: Iterable<WorkbenchExplorerEntryRef> = [],
|
|
249
|
+
): WorkbenchExplorerFlattenedEntry[] {
|
|
250
|
+
const expandedEntryKeys = normalizeWorkbenchExplorerEntryKeys(expandedEntries);
|
|
251
|
+
const flattenedEntries: WorkbenchExplorerFlattenedEntry[] = [];
|
|
252
|
+
|
|
253
|
+
const flattenEntries = ({
|
|
254
|
+
depth,
|
|
255
|
+
entries = [],
|
|
256
|
+
parentEntryIds,
|
|
257
|
+
provider,
|
|
258
|
+
}: {
|
|
259
|
+
depth: number;
|
|
260
|
+
entries?: readonly WorkbenchExplorerEntryDescriptor[];
|
|
261
|
+
parentEntryIds: readonly string[];
|
|
262
|
+
provider: WorkbenchExplorerProviderDescriptor;
|
|
263
|
+
}) => {
|
|
264
|
+
for (const entry of entries) {
|
|
265
|
+
const key = getWorkbenchExplorerEntryKey({
|
|
266
|
+
entryId: entry.id,
|
|
267
|
+
providerId: provider.id,
|
|
268
|
+
});
|
|
269
|
+
const flattenedEntry: WorkbenchExplorerFlattenedEntry = {
|
|
270
|
+
depth,
|
|
271
|
+
entry,
|
|
272
|
+
entryId: entry.id,
|
|
273
|
+
key,
|
|
274
|
+
parentEntryIds,
|
|
275
|
+
provider,
|
|
276
|
+
providerId: provider.id,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
flattenedEntries.push(flattenedEntry);
|
|
280
|
+
|
|
281
|
+
if (entry.children?.length && expandedEntryKeys.has(key)) {
|
|
282
|
+
flattenEntries({
|
|
283
|
+
depth: depth + 1,
|
|
284
|
+
entries: entry.children,
|
|
285
|
+
parentEntryIds: [...parentEntryIds, entry.id],
|
|
286
|
+
provider,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
for (const provider of providers) {
|
|
293
|
+
flattenEntries({ depth: 0, entries: provider.entries, parentEntryIds: [], provider });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return flattenedEntries;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function getWorkbenchExplorerEntryContext({
|
|
300
|
+
activeEntry,
|
|
301
|
+
expandedEntries,
|
|
302
|
+
selectedEntries,
|
|
303
|
+
...entry
|
|
304
|
+
}: WorkbenchExplorerEntryContextInput): WorkbenchExplorerEntryContext {
|
|
305
|
+
const activeEntryKey = activeEntry ? getWorkbenchExplorerEntryKey(activeEntry) : undefined;
|
|
306
|
+
const expandedEntryKeys = normalizeWorkbenchExplorerEntryKeys(expandedEntries);
|
|
307
|
+
const selectedEntryKeys = normalizeWorkbenchExplorerEntryKeys(selectedEntries);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
...entry,
|
|
311
|
+
active: activeEntryKey === entry.key,
|
|
312
|
+
expanded: expandedEntryKeys.has(entry.key),
|
|
313
|
+
selected: selectedEntryKeys.has(entry.key),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function WorkbenchMultiProviderExplorer({
|
|
318
|
+
activeEntry,
|
|
319
|
+
className,
|
|
320
|
+
defaultExpandedEntries = [],
|
|
321
|
+
emptyLabel = 'No explorer providers',
|
|
322
|
+
expandedEntries,
|
|
323
|
+
onEntrySelect,
|
|
324
|
+
onEntryToggle,
|
|
325
|
+
onExpandedEntriesChange,
|
|
326
|
+
onProviderAction,
|
|
327
|
+
onProviderCollapsedChange,
|
|
328
|
+
providers,
|
|
329
|
+
selectedEntries = [],
|
|
330
|
+
...props
|
|
331
|
+
}: WorkbenchMultiProviderExplorerProps) {
|
|
332
|
+
const [uncontrolledExpandedEntryKeys, setUncontrolledExpandedEntryKeys] = useState(() =>
|
|
333
|
+
normalizeWorkbenchExplorerEntryKeys(defaultExpandedEntries),
|
|
334
|
+
);
|
|
335
|
+
const controlledExpandedEntryKeys = useMemo(
|
|
336
|
+
() => (expandedEntries ? normalizeWorkbenchExplorerEntryKeys(expandedEntries) : undefined),
|
|
337
|
+
[expandedEntries],
|
|
338
|
+
);
|
|
339
|
+
const expandedEntryKeys = controlledExpandedEntryKeys ?? uncontrolledExpandedEntryKeys;
|
|
340
|
+
const selectedEntryKeys = useMemo(
|
|
341
|
+
() => normalizeWorkbenchExplorerEntryKeys(selectedEntries),
|
|
342
|
+
[selectedEntries],
|
|
343
|
+
);
|
|
344
|
+
const activeEntryKey = activeEntry ? getWorkbenchExplorerEntryKey(activeEntry) : undefined;
|
|
345
|
+
const allEntryRefs = useMemo(() => collectWorkbenchExplorerEntryRefs(providers), [providers]);
|
|
346
|
+
|
|
347
|
+
const entryRefsFromKeys = (keys: Set<string>) =>
|
|
348
|
+
allEntryRefs.filter((entry) => keys.has(getWorkbenchExplorerEntryKey(entry)));
|
|
349
|
+
|
|
350
|
+
const createContext = (
|
|
351
|
+
flattenedEntry: WorkbenchExplorerFlattenedEntry,
|
|
352
|
+
): WorkbenchExplorerEntryContext => ({
|
|
353
|
+
...flattenedEntry,
|
|
354
|
+
active: activeEntryKey === flattenedEntry.key,
|
|
355
|
+
expanded: expandedEntryKeys.has(flattenedEntry.key),
|
|
356
|
+
selected: selectedEntryKeys.has(flattenedEntry.key),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const toggleEntry = (
|
|
360
|
+
event: MouseEvent<HTMLButtonElement>,
|
|
361
|
+
flattenedEntry: WorkbenchExplorerFlattenedEntry,
|
|
362
|
+
) => {
|
|
363
|
+
const context = createContext(flattenedEntry);
|
|
364
|
+
const nextExpanded = !context.expanded;
|
|
365
|
+
const nextExpandedEntryKeys = new Set(expandedEntryKeys);
|
|
366
|
+
|
|
367
|
+
if (nextExpanded) {
|
|
368
|
+
nextExpandedEntryKeys.add(flattenedEntry.key);
|
|
369
|
+
} else {
|
|
370
|
+
nextExpandedEntryKeys.delete(flattenedEntry.key);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const toggleContext: WorkbenchExplorerEntryToggleContext = {
|
|
374
|
+
...context,
|
|
375
|
+
event,
|
|
376
|
+
nextExpanded,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
if (expandedEntries === undefined) {
|
|
380
|
+
setUncontrolledExpandedEntryKeys(nextExpandedEntryKeys);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
onEntryToggle?.(flattenedEntry.entry, toggleContext);
|
|
384
|
+
onExpandedEntriesChange?.(entryRefsFromKeys(nextExpandedEntryKeys), toggleContext);
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const selectEntry = (
|
|
388
|
+
event: MouseEvent<HTMLButtonElement>,
|
|
389
|
+
flattenedEntry: WorkbenchExplorerFlattenedEntry,
|
|
390
|
+
) => {
|
|
391
|
+
const context = createContext(flattenedEntry);
|
|
392
|
+
|
|
393
|
+
onEntrySelect?.(flattenedEntry.entry, {
|
|
394
|
+
...context,
|
|
395
|
+
event,
|
|
396
|
+
reason: 'click',
|
|
397
|
+
});
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
if (providers.length === 0) {
|
|
401
|
+
return (
|
|
402
|
+
<div
|
|
403
|
+
aria-label={props['aria-label'] ?? 'Workbench explorer'}
|
|
404
|
+
className={cx('ui-workbench-multi-provider-explorer', className)}
|
|
405
|
+
{...props}
|
|
406
|
+
>
|
|
407
|
+
<EmptyState compact icon="codicon-files">
|
|
408
|
+
{emptyLabel}
|
|
409
|
+
</EmptyState>
|
|
410
|
+
</div>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<div
|
|
416
|
+
aria-label={props['aria-label'] ?? 'Workbench explorer'}
|
|
417
|
+
className={cx('ui-workbench-multi-provider-explorer', className)}
|
|
418
|
+
role="tree"
|
|
419
|
+
{...props}
|
|
420
|
+
>
|
|
421
|
+
{providers.map((provider) => {
|
|
422
|
+
const providerDisabled = isWorkbenchExplorerProviderDisabled(provider);
|
|
423
|
+
const flattenedEntries = flattenWorkbenchExplorerProviders(
|
|
424
|
+
[provider],
|
|
425
|
+
entryRefsFromKeys(expandedEntryKeys),
|
|
426
|
+
);
|
|
427
|
+
const providerCount = getWorkbenchExplorerProviderEntryCount(provider);
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<WorkbenchSidebarSection
|
|
431
|
+
key={provider.id}
|
|
432
|
+
actions={
|
|
433
|
+
provider.actions?.length ? (
|
|
434
|
+
<div className="ui-workbench-multi-provider-explorer__provider-actions">
|
|
435
|
+
{provider.actions.map((action) => {
|
|
436
|
+
const actionDisabled = isWorkbenchExplorerActionDisabled(
|
|
437
|
+
action,
|
|
438
|
+
providerDisabled,
|
|
439
|
+
);
|
|
440
|
+
const status = action.status ?? (actionDisabled ? 'disabled' : 'idle');
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<IconButton
|
|
444
|
+
key={action.id}
|
|
445
|
+
aria-busy={isWorkbenchStatusBusy(status) ? true : undefined}
|
|
446
|
+
className="ui-workbench-multi-provider-explorer__provider-action"
|
|
447
|
+
data-danger={action.danger ? 'true' : undefined}
|
|
448
|
+
data-status={status}
|
|
449
|
+
disabled={actionDisabled}
|
|
450
|
+
icon={action.icon ?? 'codicon-run'}
|
|
451
|
+
label={action.label}
|
|
452
|
+
variant={action.danger ? 'danger' : 'default'}
|
|
453
|
+
onClick={(event) =>
|
|
454
|
+
onProviderAction?.(action, {
|
|
455
|
+
action,
|
|
456
|
+
event,
|
|
457
|
+
provider,
|
|
458
|
+
providerId: provider.id,
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
/>
|
|
462
|
+
);
|
|
463
|
+
})}
|
|
464
|
+
</div>
|
|
465
|
+
) : undefined
|
|
466
|
+
}
|
|
467
|
+
className="ui-workbench-multi-provider-explorer__provider"
|
|
468
|
+
collapsed={provider.collapsed}
|
|
469
|
+
collapsible={provider.collapsible}
|
|
470
|
+
count={providerCount}
|
|
471
|
+
data-disabled={providerDisabled ? 'true' : undefined}
|
|
472
|
+
data-provider-id={provider.id}
|
|
473
|
+
data-provider-kind={provider.kind}
|
|
474
|
+
data-status={provider.status ?? (providerDisabled ? 'disabled' : 'idle')}
|
|
475
|
+
defaultCollapsed={provider.defaultCollapsed}
|
|
476
|
+
title={
|
|
477
|
+
<span className="ui-workbench-multi-provider-explorer__provider-title">
|
|
478
|
+
{provider.icon ? (
|
|
479
|
+
<i
|
|
480
|
+
aria-hidden="true"
|
|
481
|
+
className={cxCodicon(
|
|
482
|
+
provider.icon,
|
|
483
|
+
'ui-workbench-multi-provider-explorer__provider-icon',
|
|
484
|
+
)}
|
|
485
|
+
/>
|
|
486
|
+
) : null}
|
|
487
|
+
<span>{provider.label}</span>
|
|
488
|
+
</span>
|
|
489
|
+
}
|
|
490
|
+
onCollapsedChange={(collapsed) =>
|
|
491
|
+
onProviderCollapsedChange?.(provider, {
|
|
492
|
+
collapsed,
|
|
493
|
+
provider,
|
|
494
|
+
providerId: provider.id,
|
|
495
|
+
})
|
|
496
|
+
}
|
|
497
|
+
>
|
|
498
|
+
{provider.description || provider.disabledReason ? (
|
|
499
|
+
<div className="ui-workbench-multi-provider-explorer__provider-description">
|
|
500
|
+
{provider.disabledReason ?? provider.description}
|
|
501
|
+
</div>
|
|
502
|
+
) : null}
|
|
503
|
+
<SideBarList
|
|
504
|
+
aria-label={`${providerLabelText(provider.label)} entries`}
|
|
505
|
+
className="ui-workbench-multi-provider-explorer__entries"
|
|
506
|
+
>
|
|
507
|
+
{flattenedEntries.length === 0 ? (
|
|
508
|
+
<li className="ui-side-bar-list-entry">
|
|
509
|
+
<div className="ui-workbench-multi-provider-explorer__empty">
|
|
510
|
+
{provider.emptyLabel ?? 'No entries'}
|
|
511
|
+
</div>
|
|
512
|
+
</li>
|
|
513
|
+
) : (
|
|
514
|
+
flattenedEntries.map((flattenedEntry) => {
|
|
515
|
+
const context = createContext(flattenedEntry);
|
|
516
|
+
const { entry } = flattenedEntry;
|
|
517
|
+
const hasChildren = Boolean(entry.children?.length);
|
|
518
|
+
const entryDisabled = providerDisabled || isWorkbenchExplorerEntryDisabled(entry);
|
|
519
|
+
const status = entry.status ?? (entryDisabled ? 'disabled' : 'idle');
|
|
520
|
+
const selectable = isWorkbenchExplorerEntrySelectable(entry);
|
|
521
|
+
const disabledReasonId = entry.disabledReason
|
|
522
|
+
? `${provider.id}-${entry.id}-disabled-reason`
|
|
523
|
+
: undefined;
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<SideBarListItem
|
|
527
|
+
key={flattenedEntry.key}
|
|
528
|
+
active={context.active}
|
|
529
|
+
aria-busy={isWorkbenchStatusBusy(status) ? true : undefined}
|
|
530
|
+
aria-describedby={disabledReasonId}
|
|
531
|
+
aria-expanded={hasChildren ? context.expanded : undefined}
|
|
532
|
+
className={cx(
|
|
533
|
+
'ui-workbench-multi-provider-explorer__entry',
|
|
534
|
+
Boolean(entry.description) &&
|
|
535
|
+
'ui-workbench-multi-provider-explorer__entry--with-description',
|
|
536
|
+
)}
|
|
537
|
+
data-entry-id={entry.id}
|
|
538
|
+
data-entry-kind={entry.kind}
|
|
539
|
+
data-entry-path={entry.path}
|
|
540
|
+
data-provider-id={provider.id}
|
|
541
|
+
data-status={status}
|
|
542
|
+
depth={flattenedEntry.depth}
|
|
543
|
+
disabled={entryDisabled}
|
|
544
|
+
role="treeitem"
|
|
545
|
+
selected={context.selected}
|
|
546
|
+
title={
|
|
547
|
+
typeof entry.disabledReason === 'string' ? entry.disabledReason : undefined
|
|
548
|
+
}
|
|
549
|
+
onClick={(event) => {
|
|
550
|
+
if (entryDisabled) return;
|
|
551
|
+
|
|
552
|
+
if (hasChildren) {
|
|
553
|
+
toggleEntry(event, flattenedEntry);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (selectable) {
|
|
558
|
+
selectEntry(event, flattenedEntry);
|
|
559
|
+
}
|
|
560
|
+
}}
|
|
561
|
+
>
|
|
562
|
+
<span className="ui-workbench-multi-provider-explorer__entry-prefix">
|
|
563
|
+
{hasChildren ? (
|
|
564
|
+
<i
|
|
565
|
+
aria-hidden="true"
|
|
566
|
+
className={cxCodicon(
|
|
567
|
+
context.expanded ? 'chevron-down' : 'chevron-right',
|
|
568
|
+
'ui-workbench-multi-provider-explorer__entry-chevron',
|
|
569
|
+
)}
|
|
570
|
+
/>
|
|
571
|
+
) : (
|
|
572
|
+
<span className="ui-workbench-multi-provider-explorer__entry-spacer" />
|
|
573
|
+
)}
|
|
574
|
+
{entry.icon ? (
|
|
575
|
+
<i
|
|
576
|
+
aria-hidden="true"
|
|
577
|
+
className={cxCodicon(
|
|
578
|
+
entry.icon,
|
|
579
|
+
'ui-workbench-multi-provider-explorer__entry-icon',
|
|
580
|
+
)}
|
|
581
|
+
/>
|
|
582
|
+
) : null}
|
|
583
|
+
</span>
|
|
584
|
+
<span className="ui-workbench-multi-provider-explorer__entry-content">
|
|
585
|
+
<span className="ui-workbench-multi-provider-explorer__entry-label">
|
|
586
|
+
{entry.label}
|
|
587
|
+
</span>
|
|
588
|
+
{entry.description ? (
|
|
589
|
+
<span className="ui-workbench-multi-provider-explorer__entry-description">
|
|
590
|
+
{entry.description}
|
|
591
|
+
</span>
|
|
592
|
+
) : null}
|
|
593
|
+
{entry.disabledReason ? (
|
|
594
|
+
<span id={disabledReasonId} className="ui-visually-hidden">
|
|
595
|
+
{entry.disabledReason}
|
|
596
|
+
</span>
|
|
597
|
+
) : null}
|
|
598
|
+
</span>
|
|
599
|
+
<span className="ui-workbench-multi-provider-explorer__entry-meta">
|
|
600
|
+
<span
|
|
601
|
+
aria-label={getWorkbenchStatusLabel(status)}
|
|
602
|
+
className="ui-workbench-multi-provider-explorer__entry-status"
|
|
603
|
+
title={getWorkbenchStatusLabel(status)}
|
|
604
|
+
>
|
|
605
|
+
<span
|
|
606
|
+
aria-hidden="true"
|
|
607
|
+
className="ui-workbench-multi-provider-explorer__entry-status-dot"
|
|
608
|
+
/>
|
|
609
|
+
</span>
|
|
610
|
+
</span>
|
|
611
|
+
</SideBarListItem>
|
|
612
|
+
);
|
|
613
|
+
})
|
|
614
|
+
)}
|
|
615
|
+
</SideBarList>
|
|
616
|
+
</WorkbenchSidebarSection>
|
|
617
|
+
);
|
|
618
|
+
})}
|
|
619
|
+
</div>
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function providerLabelText(label: ReactNode) {
|
|
624
|
+
return typeof label === 'string' ? label : 'Provider';
|
|
625
|
+
}
|