@workbench-kit/shell-react 0.0.2-prototype.0.2.13 → 0.0.2-prototype.0.2.15
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 +15 -0
- package/package.json +11 -8
- package/src/chat/command-error-message.ts +7 -0
- package/src/chat/command-surface.tsx +1 -8
- package/src/chat/use-chat-command-proposals.ts +1 -8
- package/src/editor/state-storage.ts +10 -21
- package/src/field-remap/convert-note-editor.tsx +11 -16
- package/src/field-remap/convert-palette.tsx +4 -5
- package/src/field-remap/flow-sync-key.ts +149 -0
- package/src/field-remap/flow.tsx +35 -19
- package/src/field-remap/samples.ts +1 -14
- package/src/field-remap/view.css +12 -1
- package/src/index.ts +8 -0
- package/src/management/keybinding-overrides-storage.ts +9 -23
- package/src/management/preference-settings-storage.ts +9 -23
- package/src/shell/chrome-labels.ts +98 -0
- package/src/shell/model.tsx +63 -1
- package/src/shell/secondary-actions.tsx +10 -3
- package/src/shell/shell.tsx +106 -12
- package/src/shell/view-host.tsx +73 -0
- package/src/storage/local-json-storage.ts +66 -0
- package/src/workbench/appearance-storage.ts +14 -27
- package/src/workbench/command-host.tsx +145 -8
- package/src/workbench/layout-storage.ts +22 -24
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-overridable chrome strings for WorkbenchShell surfaces
|
|
3
|
+
* (ActivityBar / StatusBar aria, secondary Profile/Settings, command palette,
|
|
4
|
+
* Quick Open).
|
|
5
|
+
*
|
|
6
|
+
* Resolution order per string: `labels[key]` → `t(capabilityId, default)` → English default.
|
|
7
|
+
* Kit does not ship locale packs — hosts inject `t` or partial `labels`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type WorkbenchTranslate = (
|
|
11
|
+
key: string,
|
|
12
|
+
fallback: string,
|
|
13
|
+
params?: Readonly<Record<string, string | number>>,
|
|
14
|
+
) => string;
|
|
15
|
+
|
|
16
|
+
/** Optional host i18n bag (same `t` signature as Field Remap chrome). */
|
|
17
|
+
export interface WorkbenchI18n {
|
|
18
|
+
readonly t: WorkbenchTranslate;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WorkbenchShellChromeLabels {
|
|
22
|
+
readonly activityBarAriaLabel: string;
|
|
23
|
+
readonly statusBarAriaLabel: string;
|
|
24
|
+
readonly profileLabel: string;
|
|
25
|
+
readonly profileTitle: string;
|
|
26
|
+
readonly settingsLabel: string;
|
|
27
|
+
readonly commandPaletteTitle: string;
|
|
28
|
+
readonly commandPalettePlaceholder: string;
|
|
29
|
+
readonly commandPaletteCloseLabel: string;
|
|
30
|
+
readonly commandPaletteEmptyLabel: string;
|
|
31
|
+
readonly quickOpenTitle: string;
|
|
32
|
+
readonly quickOpenPlaceholder: string;
|
|
33
|
+
readonly quickOpenCloseLabel: string;
|
|
34
|
+
readonly quickOpenEmptyLabel: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const defaultWorkbenchShellChromeLabels: WorkbenchShellChromeLabels = {
|
|
38
|
+
activityBarAriaLabel: 'Activity bar',
|
|
39
|
+
statusBarAriaLabel: 'Status bar',
|
|
40
|
+
profileLabel: 'Profile',
|
|
41
|
+
profileTitle: 'Open profile',
|
|
42
|
+
settingsLabel: 'Settings',
|
|
43
|
+
commandPaletteTitle: 'Command Palette',
|
|
44
|
+
commandPalettePlaceholder: 'Search commands',
|
|
45
|
+
commandPaletteCloseLabel: 'Close command palette',
|
|
46
|
+
commandPaletteEmptyLabel: 'No commands match your search',
|
|
47
|
+
quickOpenTitle: 'Quick Open',
|
|
48
|
+
quickOpenPlaceholder: 'Search files by name',
|
|
49
|
+
quickOpenCloseLabel: 'Close Quick Open',
|
|
50
|
+
quickOpenEmptyLabel: 'No matching files',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Stable capability ids for optional `t()` injection (not free prose). */
|
|
54
|
+
export const workbenchShellChromeLabelKeys = {
|
|
55
|
+
activityBarAriaLabel: 'shell.activityBar',
|
|
56
|
+
statusBarAriaLabel: 'shell.statusBar',
|
|
57
|
+
profileLabel: 'shell.profile',
|
|
58
|
+
profileTitle: 'shell.profileTitle',
|
|
59
|
+
settingsLabel: 'shell.settings',
|
|
60
|
+
commandPaletteTitle: 'commandPalette.title',
|
|
61
|
+
commandPalettePlaceholder: 'commandPalette.placeholder',
|
|
62
|
+
commandPaletteCloseLabel: 'commandPalette.close',
|
|
63
|
+
commandPaletteEmptyLabel: 'commandPalette.empty',
|
|
64
|
+
quickOpenTitle: 'quickOpen.title',
|
|
65
|
+
quickOpenPlaceholder: 'quickOpen.placeholder',
|
|
66
|
+
quickOpenCloseLabel: 'quickOpen.close',
|
|
67
|
+
quickOpenEmptyLabel: 'quickOpen.empty',
|
|
68
|
+
} as const satisfies Record<keyof WorkbenchShellChromeLabels, string>;
|
|
69
|
+
|
|
70
|
+
export function resolveWorkbenchShellChromeLabels(
|
|
71
|
+
labels?: Partial<WorkbenchShellChromeLabels> | undefined,
|
|
72
|
+
t?: WorkbenchTranslate | undefined,
|
|
73
|
+
): WorkbenchShellChromeLabels {
|
|
74
|
+
const resolve = <K extends keyof WorkbenchShellChromeLabels>(key: K): string => {
|
|
75
|
+
const override = labels?.[key];
|
|
76
|
+
if (override !== undefined) {
|
|
77
|
+
return override;
|
|
78
|
+
}
|
|
79
|
+
const fallback = defaultWorkbenchShellChromeLabels[key];
|
|
80
|
+
return t?.(workbenchShellChromeLabelKeys[key], fallback) ?? fallback;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
activityBarAriaLabel: resolve('activityBarAriaLabel'),
|
|
85
|
+
statusBarAriaLabel: resolve('statusBarAriaLabel'),
|
|
86
|
+
profileLabel: resolve('profileLabel'),
|
|
87
|
+
profileTitle: resolve('profileTitle'),
|
|
88
|
+
settingsLabel: resolve('settingsLabel'),
|
|
89
|
+
commandPaletteTitle: resolve('commandPaletteTitle'),
|
|
90
|
+
commandPalettePlaceholder: resolve('commandPalettePlaceholder'),
|
|
91
|
+
commandPaletteCloseLabel: resolve('commandPaletteCloseLabel'),
|
|
92
|
+
commandPaletteEmptyLabel: resolve('commandPaletteEmptyLabel'),
|
|
93
|
+
quickOpenTitle: resolve('quickOpenTitle'),
|
|
94
|
+
quickOpenPlaceholder: resolve('quickOpenPlaceholder'),
|
|
95
|
+
quickOpenCloseLabel: resolve('quickOpenCloseLabel'),
|
|
96
|
+
quickOpenEmptyLabel: resolve('quickOpenEmptyLabel'),
|
|
97
|
+
};
|
|
98
|
+
}
|
package/src/shell/model.tsx
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ActivityBarItem,
|
|
4
|
+
StatusBarItemModel,
|
|
5
|
+
StatusBarSectionModel,
|
|
6
|
+
} from '@workbench-kit/react/workbench/shell';
|
|
3
7
|
import type {
|
|
4
8
|
ExtensionDependencyDiagnosticSeverity,
|
|
5
9
|
WorkbenchActivityContribution,
|
|
10
|
+
WorkbenchStatusBarContribution,
|
|
6
11
|
WorkbenchViewContainerContribution,
|
|
7
12
|
WorkbenchViewContribution,
|
|
8
13
|
} from '@workbench-kit/workbench-core';
|
|
@@ -65,6 +70,63 @@ export function createWorkbenchShellActivityItems({
|
|
|
65
70
|
});
|
|
66
71
|
}
|
|
67
72
|
|
|
73
|
+
function compareStatusBarPriority(
|
|
74
|
+
left: WorkbenchStatusBarContribution,
|
|
75
|
+
right: WorkbenchStatusBarContribution,
|
|
76
|
+
): number {
|
|
77
|
+
const leftPriority = left.priority ?? 0;
|
|
78
|
+
const rightPriority = right.priority ?? 0;
|
|
79
|
+
if (leftPriority !== rightPriority) {
|
|
80
|
+
return rightPriority - leftPriority;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return left.id.localeCompare(right.id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toStatusBarItemModel(item: WorkbenchStatusBarContribution): StatusBarItemModel {
|
|
87
|
+
return {
|
|
88
|
+
id: item.id,
|
|
89
|
+
label: item.text,
|
|
90
|
+
order: item.priority,
|
|
91
|
+
title: item.text,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Map extension `contributes.statusBar` items into shell status sections. */
|
|
96
|
+
export function createContributedWorkbenchStatusSections(
|
|
97
|
+
items: readonly WorkbenchStatusBarContribution[],
|
|
98
|
+
): StatusBarSectionModel[] {
|
|
99
|
+
const leftItems = items
|
|
100
|
+
.filter((item) => item.alignment === 'left')
|
|
101
|
+
.sort(compareStatusBarPriority)
|
|
102
|
+
.map(toStatusBarItemModel);
|
|
103
|
+
const rightItems = items
|
|
104
|
+
.filter((item) => item.alignment === 'right')
|
|
105
|
+
.sort(compareStatusBarPriority)
|
|
106
|
+
.map(toStatusBarItemModel);
|
|
107
|
+
|
|
108
|
+
return [
|
|
109
|
+
...(leftItems.length > 0
|
|
110
|
+
? [
|
|
111
|
+
{
|
|
112
|
+
align: 'start' as const,
|
|
113
|
+
id: 'extension-status-left',
|
|
114
|
+
items: leftItems,
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
: []),
|
|
118
|
+
...(rightItems.length > 0
|
|
119
|
+
? [
|
|
120
|
+
{
|
|
121
|
+
align: 'end' as const,
|
|
122
|
+
id: 'extension-status-right',
|
|
123
|
+
items: rightItems,
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
: []),
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
68
130
|
export function createDefaultWorkbenchStatusSections({
|
|
69
131
|
dependencyDiagnostics,
|
|
70
132
|
extensionCount,
|
|
@@ -11,6 +11,10 @@ export interface WorkbenchSecondaryActivityItemsInput {
|
|
|
11
11
|
isProfileOpen: boolean;
|
|
12
12
|
isSettingsOpen: boolean;
|
|
13
13
|
showSettings?: boolean | undefined;
|
|
14
|
+
/** Resolved chrome labels (defaults match English kit copy). */
|
|
15
|
+
profileLabel?: string | undefined;
|
|
16
|
+
profileTitle?: string | undefined;
|
|
17
|
+
settingsLabel?: string | undefined;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
export function createWorkbenchSecondaryActivityItems({
|
|
@@ -18,6 +22,9 @@ export function createWorkbenchSecondaryActivityItems({
|
|
|
18
22
|
isProfileOpen,
|
|
19
23
|
isSettingsOpen,
|
|
20
24
|
showSettings = true,
|
|
25
|
+
profileLabel = 'Profile',
|
|
26
|
+
profileTitle = 'Open profile',
|
|
27
|
+
settingsLabel = 'Settings',
|
|
21
28
|
}: WorkbenchSecondaryActivityItemsInput): ActivityBarItem[] {
|
|
22
29
|
return [
|
|
23
30
|
...(hasProfile
|
|
@@ -26,8 +33,8 @@ export function createWorkbenchSecondaryActivityItems({
|
|
|
26
33
|
active: isProfileOpen,
|
|
27
34
|
icon: <i aria-hidden="true" className="codicon codicon-account" />,
|
|
28
35
|
id: WORKBENCH_PROFILE_ACTIVITY_ITEM_ID,
|
|
29
|
-
label:
|
|
30
|
-
title:
|
|
36
|
+
label: profileLabel,
|
|
37
|
+
title: profileTitle,
|
|
31
38
|
},
|
|
32
39
|
]
|
|
33
40
|
: []),
|
|
@@ -37,7 +44,7 @@ export function createWorkbenchSecondaryActivityItems({
|
|
|
37
44
|
active: isSettingsOpen,
|
|
38
45
|
icon: <i aria-hidden="true" className="codicon codicon-settings-gear" />,
|
|
39
46
|
id: WORKBENCH_SETTINGS_ACTIVITY_ITEM_ID,
|
|
40
|
-
label:
|
|
47
|
+
label: settingsLabel,
|
|
41
48
|
},
|
|
42
49
|
]
|
|
43
50
|
: []),
|
package/src/shell/shell.tsx
CHANGED
|
@@ -35,6 +35,11 @@ import {
|
|
|
35
35
|
sortActivityBarItems,
|
|
36
36
|
} from '@workbench-kit/react/workbench/activityBarOrder';
|
|
37
37
|
import { useWorkbench } from './provider.js';
|
|
38
|
+
import {
|
|
39
|
+
resolveWorkbenchShellChromeLabels,
|
|
40
|
+
type WorkbenchShellChromeLabels,
|
|
41
|
+
type WorkbenchTranslate,
|
|
42
|
+
} from './chrome-labels.js';
|
|
38
43
|
import { WorkbenchCommandHost, type WorkbenchCommandHostProps } from '../workbench/command-host.js';
|
|
39
44
|
import {
|
|
40
45
|
MANAGE_ACCOUNTS_COMMAND_ID,
|
|
@@ -60,10 +65,12 @@ import {
|
|
|
60
65
|
import { SETTINGS_EXTENSION_ID, WORKBENCH_PREFERENCE_SCOPES } from './settings-constants.js';
|
|
61
66
|
import { createSettingsCategories, type WorkbenchThemeOption } from './settings.js';
|
|
62
67
|
import {
|
|
68
|
+
createContributedWorkbenchStatusSections,
|
|
63
69
|
createDefaultWorkbenchStatusSections,
|
|
64
70
|
createWorkbenchShellActivityItems,
|
|
65
71
|
} from './model.js';
|
|
66
|
-
import {
|
|
72
|
+
import { mergeWorkbenchStatusSections } from '../workbench/status-sections.js';
|
|
73
|
+
import { renderDefaultBottomPanel, renderDefaultPrimarySidebar } from './view-host.js';
|
|
67
74
|
import { WorkbenchShellTitleBarLayoutControls } from './titlebar-layout-controls.js';
|
|
68
75
|
import { WorkbenchProfileModal, type WorkbenchProfileInput } from '../workbench/profile-modal.js';
|
|
69
76
|
import { useContextKeyRevision } from '../commands/use-context-key-revision.js';
|
|
@@ -102,6 +109,11 @@ export interface WorkbenchShellProps {
|
|
|
102
109
|
editorArea?: ReactNode;
|
|
103
110
|
helpContent?: ReactNode;
|
|
104
111
|
helpTitle?: ReactNode;
|
|
112
|
+
/**
|
|
113
|
+
* Partial chrome label overrides for ActivityBar / StatusBar / secondary items /
|
|
114
|
+
* command palette. Wins over `t` when both are set for the same key.
|
|
115
|
+
*/
|
|
116
|
+
labels?: Partial<WorkbenchShellChromeLabels> | undefined;
|
|
105
117
|
lightPreset?: string | undefined;
|
|
106
118
|
onDarkPresetChange?: ((preset: string) => void) | undefined;
|
|
107
119
|
onLightPresetChange?: ((preset: string) => void) | undefined;
|
|
@@ -116,6 +128,11 @@ export interface WorkbenchShellProps {
|
|
|
116
128
|
rootClassName?: string;
|
|
117
129
|
shellPreset?: string | undefined;
|
|
118
130
|
statusSections?: StatusBarSectionModel[];
|
|
131
|
+
/**
|
|
132
|
+
* Optional `t(key, fallback)` injection for shell chrome strings.
|
|
133
|
+
* Missing `t` keeps English defaults from `resolveWorkbenchShellChromeLabels`.
|
|
134
|
+
*/
|
|
135
|
+
t?: WorkbenchTranslate | undefined;
|
|
119
136
|
theme?: string;
|
|
120
137
|
themeOptions?: readonly WorkbenchThemeOption[] | undefined;
|
|
121
138
|
title?: ReactNode;
|
|
@@ -148,6 +165,7 @@ export function WorkbenchShell({
|
|
|
148
165
|
editorArea,
|
|
149
166
|
helpContent,
|
|
150
167
|
helpTitle = 'Workbench Help',
|
|
168
|
+
labels: labelOverrides,
|
|
151
169
|
lightPreset,
|
|
152
170
|
locale = 'en',
|
|
153
171
|
onDarkPresetChange,
|
|
@@ -162,6 +180,7 @@ export function WorkbenchShell({
|
|
|
162
180
|
rootClassName,
|
|
163
181
|
shellPreset = DEFAULT_SHELL_PRESET,
|
|
164
182
|
statusSections,
|
|
183
|
+
t,
|
|
165
184
|
theme,
|
|
166
185
|
themeOptions,
|
|
167
186
|
title = 'Workbench',
|
|
@@ -210,15 +229,20 @@ export function WorkbenchShell({
|
|
|
210
229
|
const resolvedStatusSections = useMemo(
|
|
211
230
|
() =>
|
|
212
231
|
statusSections ??
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
232
|
+
mergeWorkbenchStatusSections(
|
|
233
|
+
createDefaultWorkbenchStatusSections({
|
|
234
|
+
dependencyDiagnostics: extensionRegistry.getDependencyDiagnostics(),
|
|
235
|
+
extensionCount: extensionRegistry.getExtensions().length,
|
|
236
|
+
missingExtensionIds,
|
|
237
|
+
profile,
|
|
238
|
+
}),
|
|
239
|
+
createContributedWorkbenchStatusSections(extensionRegistry.statusBar.getStatusBarItems()),
|
|
240
|
+
),
|
|
219
241
|
[extensionRegistry, missingExtensionIds, profile, statusSections],
|
|
220
242
|
);
|
|
221
243
|
const activeViewContainerId = layout.sideBar.activeViewContainer;
|
|
244
|
+
const activePanelViewContainerId = layout.panel.activeViewContainer;
|
|
245
|
+
const panelViewContainers = extensionRegistry.views.getViewContainers('panel');
|
|
222
246
|
const visibleActivities = useMemo(
|
|
223
247
|
() =>
|
|
224
248
|
filterActivitiesByWhenClause(
|
|
@@ -231,7 +255,7 @@ export function WorkbenchShell({
|
|
|
231
255
|
createWorkbenchShellActivityItems({
|
|
232
256
|
activeViewContainerId,
|
|
233
257
|
activities: visibleActivities,
|
|
234
|
-
viewContainers: extensionRegistry.views.getViewContainers(),
|
|
258
|
+
viewContainers: extensionRegistry.views.getViewContainers('activitybar'),
|
|
235
259
|
views: extensionRegistry.views.getViews(),
|
|
236
260
|
}),
|
|
237
261
|
layout.activityBar.itemOrder,
|
|
@@ -243,11 +267,18 @@ export function WorkbenchShell({
|
|
|
243
267
|
const canOpenSettingsValue = contextKeyService.get(
|
|
244
268
|
WORKBENCH_PERMISSION_CONTEXT_KEY_CAN_OPEN_SETTINGS,
|
|
245
269
|
);
|
|
270
|
+
const chromeLabels = useMemo(
|
|
271
|
+
() => resolveWorkbenchShellChromeLabels(labelOverrides, t),
|
|
272
|
+
[labelOverrides, t],
|
|
273
|
+
);
|
|
246
274
|
const secondaryActivityItems = createWorkbenchSecondaryActivityItems({
|
|
247
275
|
hasProfile: profile !== undefined,
|
|
248
276
|
isProfileOpen,
|
|
249
277
|
isSettingsOpen,
|
|
250
278
|
showSettings: canOpenSettingsValue !== false,
|
|
279
|
+
profileLabel: chromeLabels.profileLabel,
|
|
280
|
+
profileTitle: chromeLabels.profileTitle,
|
|
281
|
+
settingsLabel: chromeLabels.settingsLabel,
|
|
251
282
|
});
|
|
252
283
|
|
|
253
284
|
useEffect(() => {
|
|
@@ -260,6 +291,20 @@ export function WorkbenchShell({
|
|
|
260
291
|
layoutService.setActiveViewContainer(visibleActivityItems[0]?.id);
|
|
261
292
|
}
|
|
262
293
|
}, [activeViewContainerId, layoutService, visibleActivityItems]);
|
|
294
|
+
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (panelViewContainers.length === 0) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const panelContainerIds = new Set(panelViewContainers.map((container) => container.id));
|
|
301
|
+
if (
|
|
302
|
+
activePanelViewContainerId === undefined ||
|
|
303
|
+
!panelContainerIds.has(activePanelViewContainerId)
|
|
304
|
+
) {
|
|
305
|
+
layoutService.setActivePanelViewContainer(panelViewContainers[0]?.id);
|
|
306
|
+
}
|
|
307
|
+
}, [activePanelViewContainerId, layoutService, panelViewContainers]);
|
|
263
308
|
const settingsCategories = useMemo(() => {
|
|
264
309
|
const managementCategories: WorkbenchSettingsCategory[] = [];
|
|
265
310
|
|
|
@@ -389,6 +434,16 @@ export function WorkbenchShell({
|
|
|
389
434
|
}
|
|
390
435
|
}, [activeViewContainerId, extensionRegistry, forceRender]);
|
|
391
436
|
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
if (!activePanelViewContainerId) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (const view of extensionRegistry.views.getViews(activePanelViewContainerId)) {
|
|
443
|
+
void extensionRegistry.activateView(view.id).then(forceRender);
|
|
444
|
+
}
|
|
445
|
+
}, [activePanelViewContainerId, extensionRegistry, forceRender]);
|
|
446
|
+
|
|
392
447
|
useEffect(() => {
|
|
393
448
|
if (extensionRegistry.capabilityRegistry.has(WORKBENCH_SETTINGS_CAPABILITY_ID)) {
|
|
394
449
|
return undefined;
|
|
@@ -429,6 +484,17 @@ export function WorkbenchShell({
|
|
|
429
484
|
return {
|
|
430
485
|
...hostProps,
|
|
431
486
|
additionalCommands,
|
|
487
|
+
commandPaletteCloseLabel:
|
|
488
|
+
hostProps.commandPaletteCloseLabel ?? chromeLabels.commandPaletteCloseLabel,
|
|
489
|
+
commandPaletteEmptyLabel:
|
|
490
|
+
hostProps.commandPaletteEmptyLabel ?? chromeLabels.commandPaletteEmptyLabel,
|
|
491
|
+
commandPalettePlaceholder:
|
|
492
|
+
hostProps.commandPalettePlaceholder ?? chromeLabels.commandPalettePlaceholder,
|
|
493
|
+
commandPaletteTitle: hostProps.commandPaletteTitle ?? chromeLabels.commandPaletteTitle,
|
|
494
|
+
quickOpenCloseLabel: hostProps.quickOpenCloseLabel ?? chromeLabels.quickOpenCloseLabel,
|
|
495
|
+
quickOpenEmptyLabel: hostProps.quickOpenEmptyLabel ?? chromeLabels.quickOpenEmptyLabel,
|
|
496
|
+
quickOpenPlaceholder: hostProps.quickOpenPlaceholder ?? chromeLabels.quickOpenPlaceholder,
|
|
497
|
+
quickOpenTitle: hostProps.quickOpenTitle ?? chromeLabels.quickOpenTitle,
|
|
432
498
|
onRunCommand: (command, context) => {
|
|
433
499
|
if (command.id === MANAGE_COMMANDS_COMMAND_ID) {
|
|
434
500
|
layoutService.setActiveViewContainer(BUILTIN_COMMANDS_VIEW_CONTAINER_ID);
|
|
@@ -455,7 +521,7 @@ export function WorkbenchShell({
|
|
|
455
521
|
return hostProps.onRunCommand?.(command, context) ?? false;
|
|
456
522
|
},
|
|
457
523
|
};
|
|
458
|
-
}, [commandHost, layoutService]);
|
|
524
|
+
}, [chromeLabels, commandHost, layoutService]);
|
|
459
525
|
|
|
460
526
|
const handleStatusItemActivate = useCallback(
|
|
461
527
|
(item: StatusBarItemModel) => {
|
|
@@ -473,14 +539,28 @@ export function WorkbenchShell({
|
|
|
473
539
|
return;
|
|
474
540
|
}
|
|
475
541
|
|
|
542
|
+
const contributed = extensionRegistry.statusBar.getStatusBarItem(item.id);
|
|
543
|
+
if (contributed?.command) {
|
|
544
|
+
void executeCommand(contributed.command).catch(() => undefined);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
476
548
|
onStatusItemActivate?.(item);
|
|
477
549
|
},
|
|
478
|
-
[
|
|
550
|
+
[
|
|
551
|
+
accountManagement,
|
|
552
|
+
executeCommand,
|
|
553
|
+
extensionRegistry,
|
|
554
|
+
onStatusItemActivate,
|
|
555
|
+
profile,
|
|
556
|
+
showProfileModal,
|
|
557
|
+
],
|
|
479
558
|
);
|
|
480
559
|
|
|
481
560
|
return (
|
|
482
561
|
<ReactWorkbenchShell
|
|
483
562
|
activityBar={{
|
|
563
|
+
'aria-label': chromeLabels.activityBarAriaLabel,
|
|
484
564
|
visible: layout.activityBar.visible,
|
|
485
565
|
items: visibleActivityItems,
|
|
486
566
|
reorderable: true,
|
|
@@ -506,6 +586,7 @@ export function WorkbenchShell({
|
|
|
506
586
|
},
|
|
507
587
|
}}
|
|
508
588
|
compactStatus={compactStatus}
|
|
589
|
+
statusBarAriaLabel={chromeLabels.statusBarAriaLabel}
|
|
509
590
|
onStatusItemActivate={handleStatusItemActivate}
|
|
510
591
|
primarySidebar={{
|
|
511
592
|
isVisible: layout.sideBar.visible,
|
|
@@ -536,7 +617,20 @@ export function WorkbenchShell({
|
|
|
536
617
|
}}
|
|
537
618
|
bottomPanel={{
|
|
538
619
|
isVisible: layout.panel.visible,
|
|
539
|
-
node:
|
|
620
|
+
node: renderDefaultBottomPanel(extensionRegistry, activePanelViewContainerId, {
|
|
621
|
+
catalogTrustPolicy,
|
|
622
|
+
catalogUrl,
|
|
623
|
+
onActiveViewContainerChange: (viewContainerId) => {
|
|
624
|
+
layoutService.setActivePanelViewContainer(viewContainerId);
|
|
625
|
+
if (!layout.panel.visible) {
|
|
626
|
+
layoutService.setPanelVisible(true);
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
}),
|
|
630
|
+
onSizePercentChange: (sizePercent) => {
|
|
631
|
+
layoutService.setPanelSizePercent(sizePercent);
|
|
632
|
+
},
|
|
633
|
+
sizePercent: layout.panel.sizePercent,
|
|
540
634
|
}}
|
|
541
635
|
rootClassName={rootClassName}
|
|
542
636
|
secondaryArea={resolvedEditorArea}
|
|
@@ -563,7 +657,7 @@ export function WorkbenchShell({
|
|
|
563
657
|
footer={<Button onClick={() => setSettingsOpen(false)}>Close</Button>}
|
|
564
658
|
scopes={[...WORKBENCH_PREFERENCE_SCOPES]}
|
|
565
659
|
searchValue={settingsSearchValue}
|
|
566
|
-
title=
|
|
660
|
+
title={chromeLabels.settingsLabel}
|
|
567
661
|
titleSuffix={
|
|
568
662
|
<Badge variant="muted">
|
|
569
663
|
{settingsContributionCount === 1
|
package/src/shell/view-host.tsx
CHANGED
|
@@ -56,6 +56,79 @@ export function renderDefaultPrimarySidebar(
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export function renderDefaultBottomPanel(
|
|
60
|
+
extensionRegistry: Pick<ExtensionRegistry, 'viewHostFactories' | 'views'>,
|
|
61
|
+
activeViewContainerId: string | undefined,
|
|
62
|
+
options: {
|
|
63
|
+
catalogTrustPolicy?: ExtensionCatalogTrustPolicy | undefined;
|
|
64
|
+
catalogUrl?: string | undefined;
|
|
65
|
+
onActiveViewContainerChange?: ((viewContainerId: string) => void) | undefined;
|
|
66
|
+
} = {},
|
|
67
|
+
) {
|
|
68
|
+
const containers = extensionRegistry.views.getViewContainers('panel');
|
|
69
|
+
if (containers.length === 0) {
|
|
70
|
+
return (
|
|
71
|
+
<section aria-label="Panel" className="workbench-bottom-panel">
|
|
72
|
+
<div className="workbench-bottom-panel__empty">No panel views contributed.</div>
|
|
73
|
+
</section>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const resolvedActiveViewContainerId =
|
|
78
|
+
activeViewContainerId !== undefined &&
|
|
79
|
+
containers.some((container) => container.id === activeViewContainerId)
|
|
80
|
+
? activeViewContainerId
|
|
81
|
+
: containers[0]?.id;
|
|
82
|
+
const views = resolvedActiveViewContainerId
|
|
83
|
+
? extensionRegistry.views.getViews(resolvedActiveViewContainerId)
|
|
84
|
+
: [];
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<section aria-label="Panel" className="workbench-bottom-panel">
|
|
88
|
+
<div className="workbench-bottom-panel__header" role="tablist" aria-label="Panel views">
|
|
89
|
+
{containers.map((container) => {
|
|
90
|
+
const isActive = container.id === resolvedActiveViewContainerId;
|
|
91
|
+
return (
|
|
92
|
+
<button
|
|
93
|
+
key={container.id}
|
|
94
|
+
type="button"
|
|
95
|
+
role="tab"
|
|
96
|
+
aria-selected={isActive}
|
|
97
|
+
className={
|
|
98
|
+
isActive
|
|
99
|
+
? 'workbench-bottom-panel__tab workbench-bottom-panel__tab--active'
|
|
100
|
+
: 'workbench-bottom-panel__tab'
|
|
101
|
+
}
|
|
102
|
+
data-panel-view-container-id={container.id}
|
|
103
|
+
onClick={() => options.onActiveViewContainerChange?.(container.id)}
|
|
104
|
+
>
|
|
105
|
+
{container.title}
|
|
106
|
+
</button>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
109
|
+
</div>
|
|
110
|
+
<div className="workbench-bottom-panel__body">
|
|
111
|
+
{views.length === 0 ? (
|
|
112
|
+
<div className="workbench-bottom-panel__empty">No views in this panel container.</div>
|
|
113
|
+
) : (
|
|
114
|
+
views.map((view) => (
|
|
115
|
+
<section key={view.id} data-view-id={view.id} className="workbench-bottom-panel__view">
|
|
116
|
+
<WorkbenchViewHost
|
|
117
|
+
catalogTrustPolicy={options.catalogTrustPolicy}
|
|
118
|
+
catalogUrl={options.catalogUrl}
|
|
119
|
+
fallback={view.name}
|
|
120
|
+
provider={extensionRegistry.views.getViewProvider(view.id)}
|
|
121
|
+
viewHostFactories={extensionRegistry.viewHostFactories}
|
|
122
|
+
viewId={view.id}
|
|
123
|
+
/>
|
|
124
|
+
</section>
|
|
125
|
+
))
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
59
132
|
export function WorkbenchViewHost({
|
|
60
133
|
catalogTrustPolicy,
|
|
61
134
|
catalogUrl,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBrowserWorkbenchStorage,
|
|
3
|
+
type WorkbenchStorageAdapter,
|
|
4
|
+
type WorkbenchStorageReader,
|
|
5
|
+
type WorkbenchStorageWriter,
|
|
6
|
+
} from '@workbench-kit/workbench-core';
|
|
7
|
+
|
|
8
|
+
export function resolveLocalWorkbenchStorage<
|
|
9
|
+
TStorage extends WorkbenchStorageReader | WorkbenchStorageWriter = WorkbenchStorageAdapter,
|
|
10
|
+
>(storage?: TStorage): TStorage | WorkbenchStorageAdapter | undefined {
|
|
11
|
+
return storage ?? createBrowserWorkbenchStorage({ kind: 'local' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readLocalJsonStorage<T>(
|
|
15
|
+
storageKey: string,
|
|
16
|
+
parse: (value: unknown) => T,
|
|
17
|
+
fallback: () => T,
|
|
18
|
+
storage?: WorkbenchStorageReader,
|
|
19
|
+
): T {
|
|
20
|
+
const resolvedStorage = resolveLocalWorkbenchStorage(storage);
|
|
21
|
+
if (!resolvedStorage) {
|
|
22
|
+
return fallback();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const raw = resolvedStorage.getItem(storageKey);
|
|
27
|
+
if (!raw) {
|
|
28
|
+
return fallback();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return parse(JSON.parse(raw) as unknown);
|
|
32
|
+
} catch {
|
|
33
|
+
return fallback();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function writeLocalJsonStorage<T>(
|
|
38
|
+
storageKey: string,
|
|
39
|
+
value: T,
|
|
40
|
+
storage?: WorkbenchStorageWriter,
|
|
41
|
+
options: {
|
|
42
|
+
readonly errorMode?: 'ignore' | 'throw';
|
|
43
|
+
readonly toStorageValue?: (value: T) => unknown;
|
|
44
|
+
} = {},
|
|
45
|
+
): void {
|
|
46
|
+
const resolvedStorage = resolveLocalWorkbenchStorage(storage);
|
|
47
|
+
if (!resolvedStorage) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
resolvedStorage.setItem(
|
|
53
|
+
storageKey,
|
|
54
|
+
JSON.stringify((options.toStorageValue ?? identity)(value), null, 2),
|
|
55
|
+
);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (options.errorMode === 'throw') {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
// Local storage is best-effort; quota, security, and serialization errors stay non-fatal.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function identity<T>(value: T): T {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
@@ -7,11 +7,16 @@ import {
|
|
|
7
7
|
type WorkbenchColorSchemePreference,
|
|
8
8
|
} from '@workbench-kit/react/workbench';
|
|
9
9
|
import {
|
|
10
|
-
createBrowserWorkbenchStorage,
|
|
11
10
|
type WorkbenchStorageReader,
|
|
12
11
|
type WorkbenchStorageWriter,
|
|
13
12
|
} from '@workbench-kit/workbench-core';
|
|
14
13
|
|
|
14
|
+
import {
|
|
15
|
+
readLocalJsonStorage,
|
|
16
|
+
resolveLocalWorkbenchStorage,
|
|
17
|
+
writeLocalJsonStorage,
|
|
18
|
+
} from '../storage/local-json-storage.js';
|
|
19
|
+
|
|
15
20
|
export type { WorkbenchAppearanceSettings } from '@workbench-kit/react/workbench/themePresets';
|
|
16
21
|
|
|
17
22
|
export const DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY = 'workbench-kit/.workbench/appearance';
|
|
@@ -24,28 +29,19 @@ export const DEFAULT_WORKBENCH_APPEARANCE: WorkbenchAppearanceSettings = {
|
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
export function isWorkbenchAppearancePersistenceAvailable(): boolean {
|
|
27
|
-
return
|
|
32
|
+
return resolveLocalWorkbenchStorage() !== undefined;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export function readPersistedWorkbenchAppearance(
|
|
31
36
|
storageKey = DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY,
|
|
32
37
|
storage?: WorkbenchStorageReader,
|
|
33
38
|
): WorkbenchAppearanceSettings {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const raw = resolvedStorage.getItem(storageKey);
|
|
41
|
-
if (!raw) {
|
|
42
|
-
return DEFAULT_WORKBENCH_APPEARANCE;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return normalizeWorkbenchAppearance(JSON.parse(raw) as unknown);
|
|
46
|
-
} catch {
|
|
47
|
-
return DEFAULT_WORKBENCH_APPEARANCE;
|
|
48
|
-
}
|
|
39
|
+
return readLocalJsonStorage(
|
|
40
|
+
storageKey,
|
|
41
|
+
normalizeWorkbenchAppearance,
|
|
42
|
+
() => DEFAULT_WORKBENCH_APPEARANCE,
|
|
43
|
+
storage,
|
|
44
|
+
);
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
export function writePersistedWorkbenchAppearance(
|
|
@@ -53,16 +49,7 @@ export function writePersistedWorkbenchAppearance(
|
|
|
53
49
|
storageKey = DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY,
|
|
54
50
|
storage?: WorkbenchStorageWriter,
|
|
55
51
|
): void {
|
|
56
|
-
|
|
57
|
-
if (!resolvedStorage) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
resolvedStorage.setItem(storageKey, JSON.stringify(settings, null, 2));
|
|
63
|
-
} catch {
|
|
64
|
-
// Ignore quota and security errors so the shell keeps working offline.
|
|
65
|
-
}
|
|
52
|
+
writeLocalJsonStorage(storageKey, settings, storage);
|
|
66
53
|
}
|
|
67
54
|
|
|
68
55
|
function normalizeWorkbenchAppearance(value: unknown): WorkbenchAppearanceSettings {
|