@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.
Files changed (93) hide show
  1. package/package.json +49 -0
  2. package/src/chat-ai-mock-proposals.ts +39 -0
  3. package/src/chat-command-input.ts +52 -0
  4. package/src/chat-command-policy.ts +54 -0
  5. package/src/chat-command-surface.tsx +155 -0
  6. package/src/chat-view-data.ts +20 -0
  7. package/src/chat-view.tsx +220 -0
  8. package/src/command-inspector-surface.tsx +46 -0
  9. package/src/commands-view-data.ts +17 -0
  10. package/src/commands-view.tsx +43 -0
  11. package/src/createCommandWorkspaceExplorerPort.ts +86 -0
  12. package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
  13. package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
  14. package/src/devtools/index.ts +11 -0
  15. package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
  16. package/src/devtools/workbench-devtools-snapshot.ts +140 -0
  17. package/src/devtools/workbench-devtools.css +82 -0
  18. package/src/editor-area-dnd.ts +69 -0
  19. package/src/editor-area-model.ts +71 -0
  20. package/src/editor-area.css +454 -0
  21. package/src/editor-area.tsx +676 -0
  22. package/src/editor-host-surface.tsx +634 -0
  23. package/src/editor-pane-visibility.ts +130 -0
  24. package/src/editor-resource.ts +36 -0
  25. package/src/editor-state-storage.ts +193 -0
  26. package/src/editor-tab-context-menu.ts +118 -0
  27. package/src/editor-view-providers.tsx +552 -0
  28. package/src/editor-workspace-file-availability.ts +15 -0
  29. package/src/editor-workspace-reconcile.tsx +28 -0
  30. package/src/explorer-context-menu.ts +114 -0
  31. package/src/explorer-reveal.ts +112 -0
  32. package/src/explorer-view-data.ts +58 -0
  33. package/src/explorer-view.tsx +224 -0
  34. package/src/extension-context-menu.ts +52 -0
  35. package/src/extension-management-model.ts +313 -0
  36. package/src/extensions-view-data.ts +19 -0
  37. package/src/extensions-view.tsx +38 -0
  38. package/src/field-remap-demo.tsx +18 -0
  39. package/src/field-remap-editor-surface.tsx +36 -0
  40. package/src/field-remap-flow-adapter.ts +240 -0
  41. package/src/field-remap-flow.tsx +369 -0
  42. package/src/field-remap-graph.tsx +559 -0
  43. package/src/field-remap-panel.tsx +175 -0
  44. package/src/field-remap-samples.ts +352 -0
  45. package/src/field-remap-tree.tsx +404 -0
  46. package/src/field-remap-view-data.ts +16 -0
  47. package/src/field-remap-view.css +676 -0
  48. package/src/field-remap-view.tsx +86 -0
  49. package/src/index.ts +199 -0
  50. package/src/jdw-lab-view-data.ts +25 -0
  51. package/src/jdw-lab-view.css +21 -0
  52. package/src/jdw-lab-view.tsx +79 -0
  53. package/src/jdw-widget-form-view.tsx +65 -0
  54. package/src/jdw-widget-preview-view.tsx +51 -0
  55. package/src/json-form-source-patch.ts +323 -0
  56. package/src/jsonata-transform.ts +36 -0
  57. package/src/keybinding-management-settings.tsx +16 -0
  58. package/src/keybinding-overrides-storage.ts +57 -0
  59. package/src/management-palette-commands.ts +38 -0
  60. package/src/management-settings-ids.ts +12 -0
  61. package/src/management-settings.tsx +75 -0
  62. package/src/preference-settings-storage.ts +58 -0
  63. package/src/provider.tsx +573 -0
  64. package/src/search-view-data.ts +15 -0
  65. package/src/search-view.tsx +62 -0
  66. package/src/shell-model.tsx +149 -0
  67. package/src/shell-secondary-actions.tsx +59 -0
  68. package/src/shell-settings-constants.ts +9 -0
  69. package/src/shell-settings.tsx +599 -0
  70. package/src/shell-titlebar-layout-controls.tsx +4 -0
  71. package/src/shell-view-host.tsx +199 -0
  72. package/src/shell.tsx +649 -0
  73. package/src/table-mapping-adapter.ts +59 -0
  74. package/src/use-active-workspace-path.ts +15 -0
  75. package/src/use-command-management.ts +191 -0
  76. package/src/use-context-key-revision.ts +18 -0
  77. package/src/use-editor.ts +107 -0
  78. package/src/use-extension-management.ts +193 -0
  79. package/src/use-keybinding-management.ts +130 -0
  80. package/src/use-persisted-workbench-appearance.ts +33 -0
  81. package/src/use-workbench-chat-command-proposals.ts +194 -0
  82. package/src/use-workbench-command-descriptors.ts +41 -0
  83. package/src/workbench-appearance-storage.ts +115 -0
  84. package/src/workbench-command-host.tsx +243 -0
  85. package/src/workbench-command-palette.ts +208 -0
  86. package/src/workbench-keybinding-bridge.ts +54 -0
  87. package/src/workbench-layout-storage.ts +145 -0
  88. package/src/workbench-profile-modal.tsx +118 -0
  89. package/src/workbench-shell-command-registration.ts +58 -0
  90. package/src/workbench-startup-gate.tsx +136 -0
  91. package/src/workbench-status-sections.ts +43 -0
  92. package/src/workbench-user-commands.ts +46 -0
  93. package/src/workspace-view-state.ts +35 -0
@@ -0,0 +1,15 @@
1
+ import { useMemo } from 'react';
2
+ import { parseWorkspaceResourceUri } from '@workbench-kit/workspace';
3
+
4
+ export function resolveActiveWorkspacePath(resourceUri: string | undefined): string | undefined {
5
+ if (!resourceUri) {
6
+ return undefined;
7
+ }
8
+
9
+ const resource = parseWorkspaceResourceUri(resourceUri);
10
+ return resource?.kind === 'file' ? resource.path : undefined;
11
+ }
12
+
13
+ export function useActiveWorkspacePath(resourceUri: string | undefined): string | undefined {
14
+ return useMemo(() => resolveActiveWorkspacePath(resourceUri), [resourceUri]);
15
+ }
@@ -0,0 +1,191 @@
1
+ import { useEffect, useMemo, useReducer, useState } from 'react';
2
+ import type { ExtensionCommandFeatureSpec, ExtensionRegistry } from '@workbench-kit/workbench-core';
3
+ import {
4
+ buildCommandManagementGroups,
5
+ countCommandManagementEntries,
6
+ type CommandManagementRunState,
7
+ } from '@workbench-kit/react/workbench/management';
8
+ import { createWorkbenchShellCommands } from '@workbench-kit/react/workbench';
9
+
10
+ import { useWorkbench } from './provider.js';
11
+ import {
12
+ collectExtensionCommandFeaturesById,
13
+ resolveShellCommandActivities,
14
+ } from './workbench-command-palette.js';
15
+
16
+ export function useCommandManagementModel() {
17
+ const { executeCommand, extensionRegistry } = useWorkbench();
18
+ const [lastRun, setLastRun] = useState<CommandManagementRunState | undefined>();
19
+ const [refreshToken, refreshRegistry] = useReducer((count: number) => count + 1, 0);
20
+
21
+ useEffect(() => {
22
+ const commandDisposable = extensionRegistry.commands.onDidRegisterCommand(() => {
23
+ refreshRegistry();
24
+ });
25
+
26
+ return () => {
27
+ commandDisposable.dispose();
28
+ };
29
+ }, [extensionRegistry]);
30
+
31
+ const groups = useMemo(
32
+ () => buildCommandManagementModelGroups(extensionRegistry, refreshToken),
33
+ [extensionRegistry, refreshToken],
34
+ );
35
+
36
+ const totalCount = countCommandManagementEntries(groups);
37
+
38
+ const runCommand = async (commandId: string) => {
39
+ setLastRun({
40
+ commandId,
41
+ status: 'running',
42
+ timestamp: Date.now(),
43
+ });
44
+
45
+ try {
46
+ await executeCommand(commandId);
47
+ setLastRun({
48
+ commandId,
49
+ status: 'success',
50
+ timestamp: Date.now(),
51
+ });
52
+ } catch (error) {
53
+ setLastRun({
54
+ commandId,
55
+ message: error instanceof Error ? error.message : 'Command failed.',
56
+ status: 'error',
57
+ timestamp: Date.now(),
58
+ });
59
+ }
60
+ };
61
+
62
+ return {
63
+ groups,
64
+ lastRun,
65
+ refreshRegistry,
66
+ runCommand,
67
+ totalCount,
68
+ };
69
+ }
70
+
71
+ export function buildCommandManagementModelGroups(
72
+ extensionRegistry: ExtensionRegistry,
73
+ _refreshToken = 0,
74
+ ) {
75
+ const shellCommands = createWorkbenchShellCommands({
76
+ activities: resolveShellCommandActivities(extensionRegistry),
77
+ includeSettings: true,
78
+ includeSidebarToggle: true,
79
+ });
80
+ const shellCommandIds = new Set(shellCommands.map((command) => command.id));
81
+ const commandFeaturesById = collectExtensionCommandFeaturesById(extensionRegistry);
82
+
83
+ return buildCommandManagementGroups({
84
+ extensionCommands: collectExtensionCommandEntries(
85
+ extensionRegistry,
86
+ shellCommandIds,
87
+ commandFeaturesById,
88
+ ),
89
+ keybindingsByCommandId: collectKeybindingsByCommandId(extensionRegistry),
90
+ menuSurfacesByCommandId: collectMenuSurfacesByCommandId(extensionRegistry),
91
+ shellCommands: shellCommands.map((command) => ({
92
+ category: command.category ?? 'Workbench',
93
+ handler:
94
+ extensionRegistry.commands.getCommand(command.id)?.handler ??
95
+ command.handler ??
96
+ command.run,
97
+ id: command.id,
98
+ label: typeof command.label === 'string' ? command.label : (command.title ?? command.id),
99
+ })),
100
+ });
101
+ }
102
+
103
+ function collectExtensionCommandEntries(
104
+ extensionRegistry: ExtensionRegistry,
105
+ skippedCommandIds: ReadonlySet<string>,
106
+ commandFeaturesById: ReadonlyMap<string, ExtensionCommandFeatureSpec>,
107
+ ) {
108
+ const entries: Array<{
109
+ category?: string | undefined;
110
+ description?: string | undefined;
111
+ extensionId: string;
112
+ extensionLabel: string;
113
+ handler?: unknown;
114
+ id: string;
115
+ label: string;
116
+ }> = [];
117
+ const seen = new Set<string>();
118
+
119
+ for (const feature of extensionRegistry.getFeatureSpecs()) {
120
+ const extensionLabel = feature.displayName;
121
+
122
+ for (const contribution of feature.commands) {
123
+ const command = extensionRegistry.commands.getCommand(contribution.id);
124
+ if (!command || seen.has(contribution.id) || skippedCommandIds.has(contribution.id)) {
125
+ continue;
126
+ }
127
+
128
+ seen.add(contribution.id);
129
+ entries.push({
130
+ category: command.category ?? contribution.category,
131
+ description: contribution.description,
132
+ extensionId: feature.id,
133
+ extensionLabel,
134
+ handler: command.handler,
135
+ id: command.id,
136
+ label: command.title ?? contribution.title ?? command.id,
137
+ });
138
+ }
139
+ }
140
+
141
+ for (const command of extensionRegistry.commands.getCommands()) {
142
+ if (seen.has(command.id) || skippedCommandIds.has(command.id)) {
143
+ continue;
144
+ }
145
+
146
+ entries.push({
147
+ category: command.category,
148
+ description: commandFeaturesById.get(command.id)?.description,
149
+ extensionId: 'runtime',
150
+ extensionLabel: 'Runtime',
151
+ handler: command.handler,
152
+ id: command.id,
153
+ label: command.title ?? command.id,
154
+ });
155
+ }
156
+
157
+ return entries;
158
+ }
159
+
160
+ function collectKeybindingsByCommandId(extensionRegistry: ExtensionRegistry) {
161
+ const keybindingsByCommandId: Record<string, string> = {};
162
+
163
+ for (const keybinding of extensionRegistry.keybindings.getKeybindings()) {
164
+ if (!keybindingsByCommandId[keybinding.command]) {
165
+ keybindingsByCommandId[keybinding.command] = keybinding.key;
166
+ }
167
+ }
168
+
169
+ return keybindingsByCommandId;
170
+ }
171
+
172
+ function collectMenuSurfacesByCommandId(extensionRegistry: ExtensionRegistry) {
173
+ const menuSurfacesByCommandId = new Map<string, Set<string>>();
174
+
175
+ for (const menuItem of extensionRegistry.menus.getMenuItems()) {
176
+ if (!menuItem.menu || !menuItem.command) {
177
+ continue;
178
+ }
179
+
180
+ const surfaces = menuSurfacesByCommandId.get(menuItem.command) ?? new Set<string>();
181
+ surfaces.add(menuItem.menu);
182
+ menuSurfacesByCommandId.set(menuItem.command, surfaces);
183
+ }
184
+
185
+ return Object.fromEntries(
186
+ [...menuSurfacesByCommandId.entries()].map(([commandId, surfaces]) => [
187
+ commandId,
188
+ [...surfaces].sort(),
189
+ ]),
190
+ );
191
+ }
@@ -0,0 +1,18 @@
1
+ import { useEffect, useState } from 'react';
2
+ import type { ContextKeyService } from '@workbench-kit/platform';
3
+
4
+ export function useContextKeyRevision(contextKeyService: ContextKeyService): number {
5
+ const [revision, setRevision] = useState(0);
6
+
7
+ useEffect(() => {
8
+ const disposable = contextKeyService.onDidChangeContext(() => {
9
+ setRevision((current) => current + 1);
10
+ });
11
+
12
+ return () => {
13
+ disposable.dispose();
14
+ };
15
+ }, [contextKeyService]);
16
+
17
+ return revision;
18
+ }
@@ -0,0 +1,107 @@
1
+ import { useEffect, useMemo, useReducer } from 'react';
2
+ import type { EditorHost, EditorService, EditorTabState } from '@workbench-kit/workbench-core';
3
+
4
+ import { useWorkbench } from './provider.js';
5
+ import type { EditorDocumentViewProvider } from './editor-view-providers.js';
6
+
7
+ export function useEditorService(): EditorService {
8
+ return useWorkbench().editorService;
9
+ }
10
+
11
+ export function useEditorState(): ReturnType<EditorService['getState']> {
12
+ const editorService = useEditorService();
13
+ const forceRender = useForceRender();
14
+
15
+ useEffect(() => {
16
+ const disposable = editorService.onDidChangeEditors(forceRender);
17
+ return () => {
18
+ disposable.dispose();
19
+ };
20
+ }, [editorService, forceRender]);
21
+
22
+ return editorService.getState();
23
+ }
24
+
25
+ export function useActiveEditorTab(): EditorTabState | undefined {
26
+ const editorService = useEditorService();
27
+ const forceRender = useForceRender();
28
+
29
+ useEffect(() => {
30
+ const disposable = editorService.onDidChangeEditors(forceRender);
31
+ return () => {
32
+ disposable.dispose();
33
+ };
34
+ }, [editorService, forceRender]);
35
+
36
+ return editorService.getActiveTab();
37
+ }
38
+
39
+ export function useEditorHost(tabId?: string): EditorHost | undefined {
40
+ const editorService = useEditorService();
41
+ const { waitForExtensionStartup } = useWorkbench();
42
+ const editorState = useEditorState();
43
+ const forceRender = useForceRender();
44
+ const activeGroup = editorState.groups.find((group) => group.id === editorState.activeGroupId);
45
+ const resolvedTabId = tabId ?? activeGroup?.activeTabId;
46
+ const resolvedTab = resolvedTabId
47
+ ? editorState.groups.flatMap((group) => group.tabs).find((tab) => tab.id === resolvedTabId)
48
+ : undefined;
49
+
50
+ useEffect(() => {
51
+ const disposable = editorService.onDidChangeEditors(forceRender);
52
+ return () => {
53
+ disposable.dispose();
54
+ };
55
+ }, [editorService, forceRender]);
56
+
57
+ useEffect(() => {
58
+ let cancelled = false;
59
+
60
+ void waitForExtensionStartup().then(() => {
61
+ if (!cancelled) {
62
+ forceRender();
63
+ }
64
+ });
65
+
66
+ return () => {
67
+ cancelled = true;
68
+ };
69
+ }, [forceRender, waitForExtensionStartup]);
70
+
71
+ return useMemo(() => {
72
+ if (!resolvedTabId) {
73
+ return undefined;
74
+ }
75
+
76
+ return editorService.createEditorHost(resolvedTabId);
77
+ }, [editorService, resolvedTab?.resourceMissing, resolvedTab?.resourceUri, resolvedTabId]);
78
+ }
79
+
80
+ export function useEditorDocumentViewProviders(
81
+ localProviders?: readonly EditorDocumentViewProvider[] | undefined,
82
+ ): readonly EditorDocumentViewProvider[] {
83
+ const { editorDocumentViewProviders } = useWorkbench();
84
+ const forceRender = useForceRender();
85
+
86
+ useEffect(() => {
87
+ const disposable = editorDocumentViewProviders.onDidChangeProviders(forceRender);
88
+ return () => {
89
+ disposable.dispose();
90
+ };
91
+ }, [editorDocumentViewProviders, forceRender]);
92
+
93
+ const registryProviders = editorDocumentViewProviders.getProviders();
94
+
95
+ return useMemo(
96
+ () =>
97
+ localProviders && localProviders.length > 0
98
+ ? [...localProviders, ...registryProviders]
99
+ : registryProviders,
100
+ [localProviders, registryProviders],
101
+ );
102
+ }
103
+
104
+ function useForceRender() {
105
+ const [, forceRender] = useReducer((count: number) => count + 1, 0);
106
+ return forceRender;
107
+ }
@@ -0,0 +1,193 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ DEFAULT_INSTALLED_EXTENSIONS_STORAGE_KEY,
4
+ applyExtensionInstallPlanToRecords,
5
+ loadInstalledExtensions,
6
+ parseExtensionCatalog,
7
+ saveInstalledExtensions,
8
+ toggleInstalledExtensionEnabled,
9
+ type ExtensionCatalogEntry,
10
+ type InstalledExtensionRecord,
11
+ } from '@workbench-kit/workbench-core';
12
+ import type {
13
+ ExtensionCatalogBrowseEntry,
14
+ ExtensionManagementEntry,
15
+ ExtensionManagementPendingAction,
16
+ } from '@workbench-kit/react/workbench/management';
17
+
18
+ import {
19
+ createCatalogEntryInstallPlan,
20
+ createExtensionCatalogBrowseEntries,
21
+ createExtensionInstallPlanningContext,
22
+ createExtensionManagementEntries,
23
+ } from './extension-management-model.js';
24
+ import { useWorkbench, type WorkbenchStorageAdapter } from './provider.js';
25
+
26
+ export interface UseExtensionManagementModelOptions {
27
+ catalogUrl?: string | undefined;
28
+ installedExtensionsStorage?: WorkbenchStorageAdapter | undefined;
29
+ installedExtensionsStorageKey?: string | undefined;
30
+ }
31
+
32
+ export function useExtensionManagementModel({
33
+ catalogUrl = '/extension-catalog.json',
34
+ installedExtensionsStorage,
35
+ installedExtensionsStorageKey,
36
+ }: UseExtensionManagementModelOptions = {}) {
37
+ const workbench = useWorkbench();
38
+ const { extensionRegistry } = workbench;
39
+ const resolvedInstalledExtensionsStorage =
40
+ installedExtensionsStorage ?? workbench.installedExtensionsStorage;
41
+ const resolvedInstalledExtensionsStorageKey =
42
+ installedExtensionsStorageKey ??
43
+ workbench.installedExtensionsStorageKey ??
44
+ DEFAULT_INSTALLED_EXTENSIONS_STORAGE_KEY;
45
+ const [catalogEntries, setCatalogEntries] = useState<readonly ExtensionCatalogEntry[]>([]);
46
+ const [catalogLoading, setCatalogLoading] = useState(Boolean(catalogUrl));
47
+ const [catalogError, setCatalogError] = useState<string | undefined>();
48
+ const [pendingAction, setPendingAction] = useState<
49
+ ExtensionManagementPendingAction | undefined
50
+ >();
51
+ const [installedRecords, setInstalledRecords] = useState<readonly InstalledExtensionRecord[]>(
52
+ () =>
53
+ loadInstalledExtensions(
54
+ resolvedInstalledExtensionsStorageKey,
55
+ resolvedInstalledExtensionsStorage,
56
+ ),
57
+ );
58
+
59
+ useEffect(() => {
60
+ setInstalledRecords(
61
+ loadInstalledExtensions(
62
+ resolvedInstalledExtensionsStorageKey,
63
+ resolvedInstalledExtensionsStorage,
64
+ ),
65
+ );
66
+ }, [resolvedInstalledExtensionsStorage, resolvedInstalledExtensionsStorageKey]);
67
+
68
+ useEffect(() => {
69
+ if (!catalogUrl) {
70
+ setCatalogEntries([]);
71
+ setCatalogLoading(false);
72
+ setCatalogError(undefined);
73
+ return;
74
+ }
75
+
76
+ let cancelled = false;
77
+ setCatalogLoading(true);
78
+ setCatalogError(undefined);
79
+
80
+ void fetch(catalogUrl)
81
+ .then(async (response) => {
82
+ if (!response.ok) {
83
+ throw new Error(`Catalog request failed with status ${response.status}.`);
84
+ }
85
+
86
+ const catalog = parseExtensionCatalog(await response.json());
87
+ if (!cancelled) {
88
+ setCatalogEntries(catalog.entries);
89
+ }
90
+ })
91
+ .catch((error: unknown) => {
92
+ if (!cancelled) {
93
+ setCatalogEntries([]);
94
+ setCatalogError(
95
+ error instanceof Error ? error.message : 'Failed to load extension catalog.',
96
+ );
97
+ }
98
+ })
99
+ .finally(() => {
100
+ if (!cancelled) {
101
+ setCatalogLoading(false);
102
+ }
103
+ });
104
+
105
+ return () => {
106
+ cancelled = true;
107
+ };
108
+ }, [catalogUrl]);
109
+
110
+ const installedEntries = useMemo<readonly ExtensionManagementEntry[]>(() => {
111
+ return createExtensionManagementEntries({ extensionRegistry, installedRecords });
112
+ }, [extensionRegistry, installedRecords]);
113
+
114
+ const browseEntries = useMemo<readonly ExtensionCatalogBrowseEntry[]>(() => {
115
+ return createExtensionCatalogBrowseEntries({
116
+ catalogEntries,
117
+ extensionRegistry,
118
+ installedRecords,
119
+ });
120
+ }, [catalogEntries, extensionRegistry, installedRecords]);
121
+
122
+ const installCatalogEntry = useCallback(
123
+ (entry: ExtensionCatalogBrowseEntry) => {
124
+ const installContext = createExtensionInstallPlanningContext({
125
+ catalogEntries,
126
+ extensionRegistry,
127
+ installedRecords,
128
+ });
129
+ const plan = createCatalogEntryInstallPlan(entry, installContext);
130
+ if (!plan || plan.blocked) {
131
+ return;
132
+ }
133
+
134
+ const next = applyExtensionInstallPlanToRecords({
135
+ currentRecords: installedRecords,
136
+ installSources: installContext.installSources,
137
+ plan,
138
+ });
139
+ saveInstalledExtensions(
140
+ next,
141
+ resolvedInstalledExtensionsStorageKey,
142
+ resolvedInstalledExtensionsStorage,
143
+ );
144
+ setInstalledRecords(next);
145
+ setPendingAction({ entryId: entry.id, kind: 'install' });
146
+ if (typeof window !== 'undefined') {
147
+ window.requestAnimationFrame(() => {
148
+ window.location.reload();
149
+ });
150
+ }
151
+ },
152
+ [
153
+ catalogEntries,
154
+ extensionRegistry,
155
+ installedRecords,
156
+ resolvedInstalledExtensionsStorage,
157
+ resolvedInstalledExtensionsStorageKey,
158
+ ],
159
+ );
160
+
161
+ const toggleInstalledEntry = useCallback(
162
+ (entry: ExtensionManagementEntry, enabled: boolean) => {
163
+ if (entry.source === 'bundled' && entry.id.startsWith('workbench-kit.builtin.')) {
164
+ return;
165
+ }
166
+
167
+ const next = toggleInstalledExtensionEnabled(
168
+ entry.id,
169
+ enabled,
170
+ resolvedInstalledExtensionsStorageKey,
171
+ resolvedInstalledExtensionsStorage,
172
+ );
173
+ setInstalledRecords(next);
174
+ setPendingAction({ entryId: entry.id, kind: 'toggle' });
175
+ if (typeof window !== 'undefined') {
176
+ window.requestAnimationFrame(() => {
177
+ window.location.reload();
178
+ });
179
+ }
180
+ },
181
+ [resolvedInstalledExtensionsStorage, resolvedInstalledExtensionsStorageKey],
182
+ );
183
+
184
+ return {
185
+ browseEntries,
186
+ catalogError,
187
+ catalogLoading,
188
+ installCatalogEntry,
189
+ installedEntries,
190
+ pendingAction,
191
+ toggleInstalledEntry,
192
+ };
193
+ }
@@ -0,0 +1,130 @@
1
+ import { useMemo } from 'react';
2
+ import {
3
+ buildKeybindingManagementEntries,
4
+ type KeybindingDefinition,
5
+ } from '@workbench-kit/platform';
6
+ import { createWorkbenchShellCommands } from '@workbench-kit/react/workbench';
7
+ import type { ExtensionRegistry } from '@workbench-kit/workbench-core';
8
+
9
+ import { useWorkbench } from './provider.js';
10
+ import { resolveShellCommandActivities } from './workbench-command-palette.js';
11
+
12
+ export function useKeybindingManagementModel() {
13
+ const {
14
+ extensionRegistry,
15
+ keybindingOverrides,
16
+ resetCommandKeybindingOverride,
17
+ setCommandKeybindingOverride,
18
+ } = useWorkbench();
19
+
20
+ const defaults = useMemo(
21
+ () => extensionRegistry.keybindings.getKeybindings(),
22
+ [extensionRegistry, keybindingOverrides.length],
23
+ );
24
+
25
+ const entries = useMemo(
26
+ () =>
27
+ buildKeybindingManagementEntries({
28
+ commands: collectKeybindingManagementCommands(extensionRegistry),
29
+ defaults,
30
+ overrides: keybindingOverrides,
31
+ }),
32
+ [defaults, extensionRegistry, keybindingOverrides],
33
+ );
34
+
35
+ const overrideCount = keybindingOverrides.length;
36
+
37
+ const setKeybinding = (commandId: string, key: string | undefined) => {
38
+ const defaultKey = defaults.find((binding) => binding.command === commandId)?.key;
39
+ if (!key || key === defaultKey) {
40
+ resetCommandKeybindingOverride(commandId);
41
+ return;
42
+ }
43
+
44
+ setCommandKeybindingOverride(commandId, key);
45
+ };
46
+
47
+ return {
48
+ entries,
49
+ overrideCount,
50
+ resetKeybinding: resetCommandKeybindingOverride,
51
+ setKeybinding,
52
+ };
53
+ }
54
+
55
+ function collectKeybindingManagementCommands(extensionRegistry: ExtensionRegistry) {
56
+ const commands: Array<{
57
+ category?: string | undefined;
58
+ id: string;
59
+ label: string;
60
+ sourceLabel?: string | undefined;
61
+ }> = [];
62
+ const seen = new Set<string>();
63
+ const shellCommands = createWorkbenchShellCommands({
64
+ activities: resolveShellCommandActivities(extensionRegistry),
65
+ includeSettings: true,
66
+ includeSidebarToggle: true,
67
+ });
68
+ const shellCommandIds = new Set(shellCommands.map((command) => command.id));
69
+
70
+ for (const extension of extensionRegistry.getExtensions()) {
71
+ const extensionLabel = extension.manifest.displayName ?? extension.manifest.id;
72
+
73
+ for (const contribution of extension.manifest.contributes?.commands ?? []) {
74
+ const command = extensionRegistry.commands.getCommand(contribution.command);
75
+ if (!command || seen.has(contribution.command) || shellCommandIds.has(contribution.command)) {
76
+ continue;
77
+ }
78
+
79
+ seen.add(contribution.command);
80
+ commands.push({
81
+ category: command.category ?? contribution.category,
82
+ id: command.id,
83
+ label: command.title ?? contribution.title ?? command.id,
84
+ sourceLabel: extensionLabel,
85
+ });
86
+ }
87
+ }
88
+
89
+ for (const command of extensionRegistry.commands.getCommands()) {
90
+ if (seen.has(command.id) || shellCommandIds.has(command.id)) {
91
+ continue;
92
+ }
93
+
94
+ commands.push({
95
+ category: command.category,
96
+ id: command.id,
97
+ label: command.title ?? command.id,
98
+ sourceLabel: 'Runtime',
99
+ });
100
+ }
101
+
102
+ for (const command of shellCommands) {
103
+ if (seen.has(command.id)) {
104
+ continue;
105
+ }
106
+
107
+ const label =
108
+ typeof command.label === 'function'
109
+ ? command.id
110
+ : typeof command.label === 'string'
111
+ ? command.label
112
+ : command.id;
113
+
114
+ seen.add(command.id);
115
+ commands.push({
116
+ category: 'Workbench',
117
+ id: command.id,
118
+ label,
119
+ sourceLabel: 'Workbench Shell',
120
+ });
121
+ }
122
+
123
+ return commands;
124
+ }
125
+
126
+ export function toKeybindingOverrideDefinitions(
127
+ overrides: readonly KeybindingDefinition[],
128
+ ): KeybindingDefinition[] {
129
+ return overrides.map((binding) => ({ ...binding }));
130
+ }
@@ -0,0 +1,33 @@
1
+ import { useEffect, useState } from 'react';
2
+ import type { WorkbenchStorageAdapter } from '@workbench-kit/workbench-core';
3
+
4
+ import {
5
+ readPersistedWorkbenchAppearance,
6
+ writePersistedWorkbenchAppearance,
7
+ type WorkbenchAppearanceSettings,
8
+ } from './workbench-appearance-storage.js';
9
+
10
+ export interface UsePersistedWorkbenchAppearanceOptions {
11
+ persist?: boolean | undefined;
12
+ storage?: WorkbenchStorageAdapter | undefined;
13
+ storageKey?: string | undefined;
14
+ }
15
+
16
+ export function usePersistedWorkbenchAppearance(
17
+ options: UsePersistedWorkbenchAppearanceOptions = {},
18
+ ): [WorkbenchAppearanceSettings, (settings: WorkbenchAppearanceSettings) => void] {
19
+ const { persist = true, storage, storageKey } = options;
20
+ const [appearance, setAppearance] = useState(() =>
21
+ readPersistedWorkbenchAppearance(storageKey, storage),
22
+ );
23
+
24
+ useEffect(() => {
25
+ if (!persist) {
26
+ return;
27
+ }
28
+
29
+ writePersistedWorkbenchAppearance(appearance, storageKey, storage);
30
+ }, [appearance, persist, storage, storageKey]);
31
+
32
+ return [appearance, setAppearance];
33
+ }