@workbench-kit/shell-react 0.0.2-prototype.0.2.10

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 (105) hide show
  1. package/README.md +98 -0
  2. package/package.json +51 -0
  3. package/src/chat/ai-mock-proposals.ts +39 -0
  4. package/src/chat/command-input.ts +52 -0
  5. package/src/chat/command-policy.ts +54 -0
  6. package/src/chat/command-surface.tsx +155 -0
  7. package/src/chat/use-chat-command-proposals.ts +194 -0
  8. package/src/chat/view-data.ts +20 -0
  9. package/src/chat/view.tsx +220 -0
  10. package/src/commands/inspector-surface.tsx +46 -0
  11. package/src/commands/use-command-descriptors.ts +41 -0
  12. package/src/commands/use-context-key-revision.ts +18 -0
  13. package/src/commands/view-data.ts +17 -0
  14. package/src/commands/view.tsx +43 -0
  15. package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
  16. package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
  17. package/src/devtools/index.ts +11 -0
  18. package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
  19. package/src/devtools/workbench-devtools-snapshot.ts +140 -0
  20. package/src/devtools/workbench-devtools.css +82 -0
  21. package/src/editor/area-dnd.ts +69 -0
  22. package/src/editor/area-model.ts +71 -0
  23. package/src/editor/area.css +454 -0
  24. package/src/editor/area.tsx +676 -0
  25. package/src/editor/host-surface.tsx +691 -0
  26. package/src/editor/markdown-document-view-data.ts +16 -0
  27. package/src/editor/pane-visibility.ts +130 -0
  28. package/src/editor/resource.ts +36 -0
  29. package/src/editor/state-storage.ts +182 -0
  30. package/src/editor/tab-context-menu.ts +118 -0
  31. package/src/editor/use-editor.ts +138 -0
  32. package/src/editor/view-providers.tsx +504 -0
  33. package/src/editor/workspace-file-availability.ts +15 -0
  34. package/src/editor/workspace-reconcile.tsx +31 -0
  35. package/src/explorer/context-menu.ts +114 -0
  36. package/src/explorer/create-command-workspace-explorer-port.ts +86 -0
  37. package/src/explorer/reveal.ts +112 -0
  38. package/src/explorer/search-view-data.ts +15 -0
  39. package/src/explorer/search-view.tsx +65 -0
  40. package/src/explorer/use-active-workspace-path.ts +15 -0
  41. package/src/explorer/view-data.ts +58 -0
  42. package/src/explorer/view.tsx +227 -0
  43. package/src/extensions/context-menu.ts +52 -0
  44. package/src/extensions/management-model.ts +313 -0
  45. package/src/extensions/use-extension-management.ts +241 -0
  46. package/src/extensions/view-data.ts +19 -0
  47. package/src/extensions/view.tsx +63 -0
  48. package/src/field-remap/chrome-labels.ts +74 -0
  49. package/src/field-remap/convert-note-editor.tsx +206 -0
  50. package/src/field-remap/convert-palette.tsx +126 -0
  51. package/src/field-remap/demo.tsx +105 -0
  52. package/src/field-remap/detail-panel.tsx +659 -0
  53. package/src/field-remap/editor-surface.tsx +36 -0
  54. package/src/field-remap/flow-adapter.ts +838 -0
  55. package/src/field-remap/flow-ops.ts +442 -0
  56. package/src/field-remap/flow.tsx +948 -0
  57. package/src/field-remap/graph.tsx +559 -0
  58. package/src/field-remap/index.ts +88 -0
  59. package/src/field-remap/io-class-browse.tsx +145 -0
  60. package/src/field-remap/jsonata-transform.ts +166 -0
  61. package/src/field-remap/panel.tsx +466 -0
  62. package/src/field-remap/samples.ts +507 -0
  63. package/src/field-remap/shape-io-editor.tsx +170 -0
  64. package/src/field-remap/table-mapping-adapter.ts +59 -0
  65. package/src/field-remap/transform-options-editor.tsx +312 -0
  66. package/src/field-remap/tree.tsx +404 -0
  67. package/src/field-remap/view-data.ts +16 -0
  68. package/src/field-remap/view.css +1367 -0
  69. package/src/field-remap/view.tsx +86 -0
  70. package/src/index.ts +294 -0
  71. package/src/jdw/document-view-data.ts +31 -0
  72. package/src/jdw/json-form-source-patch.ts +323 -0
  73. package/src/jdw/lab-view-data.ts +25 -0
  74. package/src/jdw/lab-view.css +21 -0
  75. package/src/jdw/lab-view.tsx +79 -0
  76. package/src/jdw/widget-form-view.tsx +68 -0
  77. package/src/jdw/widget-preview-view.tsx +54 -0
  78. package/src/management/keybinding-overrides-storage.ts +49 -0
  79. package/src/management/keybinding-settings.tsx +16 -0
  80. package/src/management/palette-commands.ts +38 -0
  81. package/src/management/preference-settings-storage.ts +50 -0
  82. package/src/management/settings-ids.ts +12 -0
  83. package/src/management/settings.tsx +96 -0
  84. package/src/management/use-command-management.ts +191 -0
  85. package/src/management/use-keybinding-management.ts +130 -0
  86. package/src/shell/model.tsx +149 -0
  87. package/src/shell/provider.tsx +606 -0
  88. package/src/shell/secondary-actions.tsx +59 -0
  89. package/src/shell/settings-constants.ts +9 -0
  90. package/src/shell/settings.tsx +599 -0
  91. package/src/shell/shell.tsx +680 -0
  92. package/src/shell/titlebar-layout-controls.tsx +4 -0
  93. package/src/shell/view-host.tsx +212 -0
  94. package/src/workbench/appearance-storage.ts +107 -0
  95. package/src/workbench/command-host.tsx +243 -0
  96. package/src/workbench/command-palette.ts +208 -0
  97. package/src/workbench/keybinding-bridge.ts +54 -0
  98. package/src/workbench/layout-storage.ts +134 -0
  99. package/src/workbench/profile-modal.tsx +118 -0
  100. package/src/workbench/shell-command-registration.ts +58 -0
  101. package/src/workbench/startup-gate.tsx +136 -0
  102. package/src/workbench/status-sections.ts +43 -0
  103. package/src/workbench/use-persisted-appearance.ts +33 -0
  104. package/src/workbench/user-commands.ts +46 -0
  105. package/src/workbench/workspace-view-state.ts +35 -0
@@ -0,0 +1,323 @@
1
+ export type JsonFormPath = readonly (string | number)[];
2
+
3
+ export interface JsonTextRange {
4
+ readonly end: number;
5
+ readonly start: number;
6
+ }
7
+
8
+ export function replaceJsonValueAtPath(
9
+ source: string,
10
+ path: JsonFormPath,
11
+ nextValue: unknown,
12
+ ): string {
13
+ if (path.length === 0) {
14
+ return JSON.stringify(nextValue);
15
+ }
16
+
17
+ const range = findJsonValueRangeAtPath(source, path);
18
+ if (!range) {
19
+ return fallbackSerializeJsonPathEdit(source, path, nextValue);
20
+ }
21
+
22
+ return `${source.slice(0, range.start)}${JSON.stringify(nextValue)}${source.slice(range.end)}`;
23
+ }
24
+
25
+ function fallbackSerializeJsonPathEdit(
26
+ source: string,
27
+ path: JsonFormPath,
28
+ nextValue: unknown,
29
+ ): string {
30
+ const parsed: unknown = JSON.parse(source);
31
+ const nextRecord = updateJsonPathValue(parsed, path, nextValue);
32
+ return JSON.stringify(nextRecord, null, detectJsonIndent(source));
33
+ }
34
+
35
+ function detectJsonIndent(source: string): number {
36
+ return /\n[ \t]+"/.test(source) ? 2 : 0;
37
+ }
38
+
39
+ function updateJsonPathValue(value: unknown, path: JsonFormPath, nextValue: unknown): unknown {
40
+ if (path.length === 0) {
41
+ return nextValue;
42
+ }
43
+
44
+ const [head, ...tail] = path;
45
+
46
+ if (Array.isArray(value) && typeof head === 'number') {
47
+ const nextArray = [...value];
48
+ nextArray[head] = updateJsonPathValue(nextArray[head], tail, nextValue);
49
+ return nextArray;
50
+ }
51
+
52
+ if (isJsonRecord(value) && typeof head === 'string') {
53
+ return {
54
+ ...value,
55
+ [head]: updateJsonPathValue(value[head], tail, nextValue),
56
+ };
57
+ }
58
+
59
+ return value;
60
+ }
61
+
62
+ function isJsonRecord(value: unknown): value is Record<string, unknown> {
63
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
64
+ }
65
+
66
+ export function findJsonValueRangeAtPath(source: string, path: JsonFormPath): JsonTextRange | null {
67
+ const start = skipWhitespace(source, 0);
68
+ if (start >= source.length) {
69
+ return null;
70
+ }
71
+
72
+ return locateJsonValueRange(source, start, path, 0);
73
+ }
74
+
75
+ function locateJsonValueRange(
76
+ source: string,
77
+ index: number,
78
+ path: JsonFormPath,
79
+ pathIndex: number,
80
+ ): JsonTextRange | null {
81
+ index = skipWhitespace(source, index);
82
+
83
+ if (pathIndex >= path.length) {
84
+ const end = scanJsonValueEnd(source, index);
85
+ return end < 0 ? null : { start: index, end };
86
+ }
87
+
88
+ const segment = path[pathIndex];
89
+ if (typeof segment === 'number') {
90
+ if (source[index] !== '[') {
91
+ return null;
92
+ }
93
+
94
+ return locateJsonValueInArray(source, index + 1, path, pathIndex, segment);
95
+ }
96
+
97
+ if (typeof segment === 'string') {
98
+ if (source[index] !== '{') {
99
+ return null;
100
+ }
101
+
102
+ return locateJsonValueInObject(source, index + 1, path, pathIndex, segment);
103
+ }
104
+
105
+ return null;
106
+ }
107
+
108
+ function locateJsonValueInObject(
109
+ source: string,
110
+ index: number,
111
+ path: JsonFormPath,
112
+ pathIndex: number,
113
+ key: string,
114
+ ): JsonTextRange | null {
115
+ let cursor = skipWhitespace(source, index);
116
+
117
+ while (cursor < source.length && source[cursor] !== '}') {
118
+ const keyRange = scanJsonStringToken(source, cursor);
119
+ if (!keyRange) {
120
+ return null;
121
+ }
122
+
123
+ const parsedKey = JSON.parse(source.slice(keyRange.start, keyRange.end)) as string;
124
+ cursor = skipWhitespace(source, keyRange.end);
125
+
126
+ if (source[cursor] !== ':') {
127
+ return null;
128
+ }
129
+
130
+ cursor = skipWhitespace(source, cursor + 1);
131
+
132
+ if (parsedKey === key) {
133
+ return locateJsonValueRange(source, cursor, path, pathIndex + 1);
134
+ }
135
+
136
+ const valueEnd = scanJsonValueEnd(source, cursor);
137
+ if (valueEnd < 0) {
138
+ return null;
139
+ }
140
+
141
+ cursor = skipWhitespace(source, valueEnd);
142
+ if (source[cursor] === ',') {
143
+ cursor = skipWhitespace(source, cursor + 1);
144
+ continue;
145
+ }
146
+
147
+ if (source[cursor] === '}') {
148
+ return null;
149
+ }
150
+
151
+ return null;
152
+ }
153
+
154
+ return null;
155
+ }
156
+
157
+ function locateJsonValueInArray(
158
+ source: string,
159
+ index: number,
160
+ path: JsonFormPath,
161
+ pathIndex: number,
162
+ targetIndex: number,
163
+ ): JsonTextRange | null {
164
+ let cursor = skipWhitespace(source, index);
165
+ let currentIndex = 0;
166
+
167
+ while (cursor < source.length && source[cursor] !== ']') {
168
+ if (currentIndex === targetIndex) {
169
+ return locateJsonValueRange(source, cursor, path, pathIndex + 1);
170
+ }
171
+
172
+ const valueEnd = scanJsonValueEnd(source, cursor);
173
+ if (valueEnd < 0) {
174
+ return null;
175
+ }
176
+
177
+ currentIndex += 1;
178
+ cursor = skipWhitespace(source, valueEnd);
179
+
180
+ if (source[cursor] === ',') {
181
+ cursor = skipWhitespace(source, cursor + 1);
182
+ continue;
183
+ }
184
+
185
+ if (source[cursor] === ']') {
186
+ return null;
187
+ }
188
+
189
+ return null;
190
+ }
191
+
192
+ return null;
193
+ }
194
+
195
+ function scanJsonValueEnd(source: string, index: number): number {
196
+ index = skipWhitespace(source, index);
197
+ if (index >= source.length) {
198
+ return -1;
199
+ }
200
+
201
+ const char = source[index];
202
+
203
+ if (char === '"') {
204
+ const stringToken = scanJsonStringToken(source, index);
205
+ return stringToken?.end ?? -1;
206
+ }
207
+
208
+ if (char === '{') {
209
+ return scanJsonContainerEnd(source, index, '{', '}');
210
+ }
211
+
212
+ if (char === '[') {
213
+ return scanJsonContainerEnd(source, index, '[', ']');
214
+ }
215
+
216
+ if (source.startsWith('true', index)) {
217
+ return index + 4;
218
+ }
219
+
220
+ if (source.startsWith('false', index)) {
221
+ return index + 5;
222
+ }
223
+
224
+ if (source.startsWith('null', index)) {
225
+ return index + 4;
226
+ }
227
+
228
+ let cursor = index;
229
+ while (cursor < source.length && !',]} \t\r\n'.includes(source[cursor] ?? '')) {
230
+ cursor += 1;
231
+ }
232
+
233
+ return cursor > index ? cursor : -1;
234
+ }
235
+
236
+ function scanJsonContainerEnd(
237
+ source: string,
238
+ index: number,
239
+ openChar: '{' | '[',
240
+ closeChar: '}' | ']',
241
+ ): number {
242
+ let depth = 0;
243
+ let cursor = index;
244
+ let inString = false;
245
+ let escaped = false;
246
+
247
+ while (cursor < source.length) {
248
+ const char = source[cursor];
249
+
250
+ if (inString) {
251
+ if (escaped) {
252
+ escaped = false;
253
+ } else if (char === '\\') {
254
+ escaped = true;
255
+ } else if (char === '"') {
256
+ inString = false;
257
+ }
258
+
259
+ cursor += 1;
260
+ continue;
261
+ }
262
+
263
+ if (char === '"') {
264
+ inString = true;
265
+ cursor += 1;
266
+ continue;
267
+ }
268
+
269
+ if (char === openChar) {
270
+ depth += 1;
271
+ } else if (char === closeChar) {
272
+ depth -= 1;
273
+ if (depth === 0) {
274
+ return cursor + 1;
275
+ }
276
+ }
277
+
278
+ cursor += 1;
279
+ }
280
+
281
+ return -1;
282
+ }
283
+
284
+ function scanJsonStringToken(source: string, index: number): JsonTextRange | null {
285
+ if (source[index] !== '"') {
286
+ return null;
287
+ }
288
+
289
+ let cursor = index + 1;
290
+ let escaped = false;
291
+
292
+ while (cursor < source.length) {
293
+ const char = source[cursor];
294
+
295
+ if (escaped) {
296
+ escaped = false;
297
+ cursor += 1;
298
+ continue;
299
+ }
300
+
301
+ if (char === '\\') {
302
+ escaped = true;
303
+ cursor += 1;
304
+ continue;
305
+ }
306
+
307
+ if (char === '"') {
308
+ return { start: index, end: cursor + 1 };
309
+ }
310
+
311
+ cursor += 1;
312
+ }
313
+
314
+ return null;
315
+ }
316
+
317
+ function skipWhitespace(source: string, index: number): number {
318
+ while (index < source.length && /\s/.test(source[index] ?? '')) {
319
+ index += 1;
320
+ }
321
+
322
+ return index;
323
+ }
@@ -0,0 +1,25 @@
1
+ export const SAMPLE_JDW_LAB_VIEW_RENDER_KIND = 'workbench-kit.samples.jdw.view' as const;
2
+
3
+ export interface SampleJdwLabViewRenderData {
4
+ readonly kind: typeof SAMPLE_JDW_LAB_VIEW_RENDER_KIND;
5
+ readonly templateJdwPath: string;
6
+ readonly widgetTreePath: string;
7
+ }
8
+
9
+ export function isSampleJdwLabViewRenderData(value: unknown): value is SampleJdwLabViewRenderData {
10
+ if (typeof value !== 'object' || value === null) {
11
+ return false;
12
+ }
13
+
14
+ const candidate = value as {
15
+ kind?: unknown;
16
+ templateJdwPath?: unknown;
17
+ widgetTreePath?: unknown;
18
+ };
19
+
20
+ return (
21
+ candidate.kind === SAMPLE_JDW_LAB_VIEW_RENDER_KIND &&
22
+ typeof candidate.templateJdwPath === 'string' &&
23
+ typeof candidate.widgetTreePath === 'string'
24
+ );
25
+ }
@@ -0,0 +1,21 @@
1
+ .workbench-jdw-lab-view {
2
+ height: 100%;
3
+ min-height: 0;
4
+ }
5
+
6
+ .workbench-jdw-lab-view__list {
7
+ padding-block: 4px;
8
+ }
9
+
10
+ .workbench-jdw-lab-view .ui-workbench-action-list-item__description {
11
+ font-family: var(
12
+ --monaco-monospace-font,
13
+ ui-monospace,
14
+ SFMono-Regular,
15
+ Menlo,
16
+ Consolas,
17
+ monospace
18
+ );
19
+ font-size: 11px;
20
+ opacity: 0.85;
21
+ }
@@ -0,0 +1,79 @@
1
+ import { WorkbenchActionSidebar } from '@workbench-kit/react/layout';
2
+
3
+ import './lab-view.css';
4
+
5
+ import { useWorkbench } from '../shell/provider.js';
6
+ import { useActiveEditorTab } from '../editor/use-editor.js';
7
+ import { useActiveWorkspacePath } from '../explorer/use-active-workspace-path.js';
8
+ import {
9
+ isSampleJdwLabViewRenderData,
10
+ SAMPLE_JDW_LAB_VIEW_RENDER_KIND,
11
+ type SampleJdwLabViewRenderData,
12
+ } from './lab-view-data.js';
13
+
14
+ export type { SampleJdwLabViewRenderData };
15
+ export { isSampleJdwLabViewRenderData, SAMPLE_JDW_LAB_VIEW_RENDER_KIND };
16
+
17
+ const WORKSPACE_OPEN_COMMAND_ID = 'workspace.open' as const;
18
+
19
+ const DEFAULT_WIDGET_TREE_PATH = 'jdw/showcase/example.jdw.json';
20
+ const DEFAULT_TEMPLATE_JDW_PATH = 'jdw/templates/analytics-dashboard.jdw.json';
21
+
22
+ export interface SampleJdwLabViewProps {
23
+ readonly templateJdwPath?: string | undefined;
24
+ readonly widgetTreePath?: string | undefined;
25
+ }
26
+
27
+ function pathMatches(activePath: string | undefined, candidate: string): boolean {
28
+ if (!activePath) {
29
+ return false;
30
+ }
31
+
32
+ return activePath === candidate || activePath.endsWith(`/${candidate}`);
33
+ }
34
+
35
+ export function SampleJdwLabView({
36
+ templateJdwPath = DEFAULT_TEMPLATE_JDW_PATH,
37
+ widgetTreePath = DEFAULT_WIDGET_TREE_PATH,
38
+ }: SampleJdwLabViewProps = {}) {
39
+ const { executeCommand } = useWorkbench();
40
+ const activeTab = useActiveEditorTab();
41
+ const activePath = useActiveWorkspacePath(activeTab?.resourceUri);
42
+
43
+ return (
44
+ <WorkbenchActionSidebar
45
+ className="workbench-jdw-lab-view"
46
+ data-testid="jdw-lab-view"
47
+ items={[
48
+ {
49
+ description: widgetTreePath,
50
+ icon: <i aria-hidden="true" className="codicon codicon-layout" />,
51
+ id: 'widget-tree',
52
+ label: 'Widget Tree',
53
+ selected: pathMatches(activePath, widgetTreePath),
54
+ testId: 'jdw-lab-open-widget-tree',
55
+ title: `Open ${widgetTreePath}`,
56
+ },
57
+ {
58
+ description: templateJdwPath,
59
+ icon: <i aria-hidden="true" className="codicon codicon-symbol-structure" />,
60
+ id: 'template-jdw',
61
+ label: 'Template JDW',
62
+ selected: pathMatches(activePath, templateJdwPath),
63
+ testId: 'jdw-lab-open-template-jdw',
64
+ title: `Open ${templateJdwPath}`,
65
+ },
66
+ ]}
67
+ listProps={{
68
+ 'aria-label': 'JDW lab surfaces',
69
+ className: 'workbench-jdw-lab-view__list',
70
+ }}
71
+ onSelect={(item) => {
72
+ if (typeof item.description !== 'string' || item.description.length === 0) {
73
+ return;
74
+ }
75
+ void executeCommand(WORKSPACE_OPEN_COMMAND_ID, { path: item.description });
76
+ }}
77
+ />
78
+ );
79
+ }
@@ -0,0 +1,68 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { resolveWidgetStudioAssetCatalog } from '@workbench-kit/react/widget-studio';
3
+ import { WidgetTreeLab } from '@workbench-kit/react/widget-tree';
4
+ import { BUILTIN_JDW_REGISTRY } from '@workbench-kit/react/jdw';
5
+
6
+ import { useWorkbench } from '../shell/provider.js';
7
+ import {
8
+ isWorkspaceResourceService,
9
+ useWorkspaceResourceState,
10
+ } from '../workbench/workspace-view-state.js';
11
+
12
+ export interface JdwWidgetFormViewProps {
13
+ readonly path: string;
14
+ readonly content: string;
15
+ readonly onContentChange: (content: string) => void;
16
+ }
17
+
18
+ const EMPTY_WORKSPACE_FILES: readonly { readonly path: string; readonly content: string }[] = [];
19
+
20
+ /**
21
+ * Form authoring surface for JDW widgets. Builds the Assets palette from built-in
22
+ * assets, workspace asset packages, and other workspace `*.jdw.json` documents.
23
+ * Preview expands `type: "ref"` via workspace documents without rewriting the
24
+ * authored JSON outline.
25
+ */
26
+ export function JdwWidgetFormView({ path, content, onContentChange }: JdwWidgetFormViewProps) {
27
+ const { workspaceHostPort } = useWorkbench();
28
+ const workspaceService = isWorkspaceResourceService(workspaceHostPort?.service)
29
+ ? workspaceHostPort.service
30
+ : undefined;
31
+ const workspaceState = useWorkspaceResourceState(workspaceService);
32
+ const files =
33
+ workspaceState?.files ?? workspaceService?.getState().files ?? EMPTY_WORKSPACE_FILES;
34
+
35
+ const filesByPath = useMemo(() => {
36
+ const map = new Map<string, string>();
37
+ for (const file of files) {
38
+ map.set(file.path.replace(/\\/g, '/'), file.content);
39
+ }
40
+ return map;
41
+ }, [files]);
42
+
43
+ const loadDocument = useCallback(
44
+ (documentPath: string) => filesByPath.get(documentPath.replace(/\\/g, '/')) ?? null,
45
+ [filesByPath],
46
+ );
47
+
48
+ const assetCatalog = useMemo(
49
+ () =>
50
+ resolveWidgetStudioAssetCatalog(files, {
51
+ excludeDocumentPaths: [path],
52
+ }),
53
+ [files, path],
54
+ );
55
+
56
+ return (
57
+ <WidgetTreeLab
58
+ assetCatalog={assetCatalog}
59
+ loadDocument={loadDocument}
60
+ path={path}
61
+ registry={BUILTIN_JDW_REGISTRY}
62
+ showDesignSource={false}
63
+ value={content}
64
+ viewMode="design"
65
+ onChange={onContentChange}
66
+ />
67
+ );
68
+ }
@@ -0,0 +1,54 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { JdwPreviewViewport } from '@workbench-kit/react/jdw/preview-viewport';
3
+ import { BUILTIN_JDW_REGISTRY } from '@workbench-kit/react/jdw';
4
+
5
+ import { useWorkbench } from '../shell/provider.js';
6
+ import {
7
+ isWorkspaceResourceService,
8
+ useWorkspaceResourceState,
9
+ } from '../workbench/workspace-view-state.js';
10
+
11
+ export interface JdwWidgetPreviewViewProps {
12
+ readonly path: string;
13
+ readonly content: string;
14
+ readonly className?: string | undefined;
15
+ }
16
+
17
+ const EMPTY_WORKSPACE_FILES: readonly { readonly path: string; readonly content: string }[] = [];
18
+
19
+ /**
20
+ * Preview viewport for JDW documents. Expands workspace `type: "ref"` imports
21
+ * before layout/draw without rewriting the source buffer.
22
+ */
23
+ export function JdwWidgetPreviewView({ path, content, className }: JdwWidgetPreviewViewProps) {
24
+ const { workspaceHostPort } = useWorkbench();
25
+ const workspaceService = isWorkspaceResourceService(workspaceHostPort?.service)
26
+ ? workspaceHostPort.service
27
+ : undefined;
28
+ const workspaceState = useWorkspaceResourceState(workspaceService);
29
+ const files =
30
+ workspaceState?.files ?? workspaceService?.getState().files ?? EMPTY_WORKSPACE_FILES;
31
+
32
+ const filesByPath = useMemo(() => {
33
+ const map = new Map<string, string>();
34
+ for (const file of files) {
35
+ map.set(file.path.replace(/\\/g, '/'), file.content);
36
+ }
37
+ return map;
38
+ }, [files]);
39
+
40
+ const loadDocument = useCallback(
41
+ (documentPath: string) => filesByPath.get(documentPath.replace(/\\/g, '/')) ?? null,
42
+ [filesByPath],
43
+ );
44
+
45
+ return (
46
+ <JdwPreviewViewport
47
+ className={className}
48
+ documentPath={path}
49
+ json={content}
50
+ loadDocument={loadDocument}
51
+ registry={BUILTIN_JDW_REGISTRY}
52
+ />
53
+ );
54
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ parseWorkbenchKeybindingsConfig,
3
+ type WorkbenchKeybindingDefinition,
4
+ } from '@workbench-kit/workbench-config';
5
+ import {
6
+ createBrowserWorkbenchStorage,
7
+ type WorkbenchStorageReader,
8
+ type WorkbenchStorageWriter,
9
+ } from '@workbench-kit/workbench-core';
10
+
11
+ export const DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY = 'workbench-kit/.workbench/keybindings';
12
+
13
+ export function isWorkbenchKeybindingPersistenceAvailable(): boolean {
14
+ return createBrowserWorkbenchStorage({ kind: 'local' }) !== undefined;
15
+ }
16
+
17
+ export function readPersistedKeybindingOverrides(
18
+ storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
19
+ storage?: WorkbenchStorageReader,
20
+ ): readonly WorkbenchKeybindingDefinition[] {
21
+ const resolvedStorage = storage ?? createBrowserWorkbenchStorage({ kind: 'local' });
22
+ if (!resolvedStorage) {
23
+ return [];
24
+ }
25
+
26
+ try {
27
+ const raw = resolvedStorage.getItem(storageKey);
28
+ if (!raw) {
29
+ return [];
30
+ }
31
+
32
+ return parseWorkbenchKeybindingsConfig(JSON.parse(raw) as unknown);
33
+ } catch {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ export function writePersistedKeybindingOverrides(
39
+ overrides: readonly WorkbenchKeybindingDefinition[],
40
+ storageKey = DEFAULT_WORKBENCH_KEYBINDING_STORAGE_KEY,
41
+ storage?: WorkbenchStorageWriter,
42
+ ): void {
43
+ const resolvedStorage = storage ?? createBrowserWorkbenchStorage({ kind: 'local' });
44
+ if (!resolvedStorage) {
45
+ return;
46
+ }
47
+
48
+ resolvedStorage.setItem(storageKey, JSON.stringify(overrides, null, 2));
49
+ }
@@ -0,0 +1,16 @@
1
+ import { KeybindingManagementPanel } from '@workbench-kit/react/workbench/management';
2
+
3
+ import { useKeybindingManagementModel } from './use-keybinding-management.js';
4
+
5
+ export function WorkbenchKeybindingManagementSettings() {
6
+ const { entries, overrideCount, resetKeybinding, setKeybinding } = useKeybindingManagementModel();
7
+
8
+ return (
9
+ <KeybindingManagementPanel
10
+ entries={entries}
11
+ summaryLabel={`${entries.length} command${entries.length === 1 ? '' : 's'} · ${overrideCount} user override${overrideCount === 1 ? '' : 's'}`}
12
+ onResetKeybinding={resetKeybinding}
13
+ onSetKeybinding={setKeybinding}
14
+ />
15
+ );
16
+ }
@@ -0,0 +1,38 @@
1
+ import type { WorkbenchCommandDescriptor } from '@workbench-kit/react/workbench';
2
+
3
+ import {
4
+ MANAGE_ACCOUNTS_COMMAND_ID,
5
+ MANAGE_COMMANDS_COMMAND_ID,
6
+ MANAGE_EXTENSIONS_COMMAND_ID,
7
+ MANAGE_KEYBINDINGS_COMMAND_ID,
8
+ } from './settings-ids.js';
9
+
10
+ /** Pure helper — kept out of React component modules for Fast Refresh. */
11
+ export function createWorkbenchManagementPaletteCommands(): readonly WorkbenchCommandDescriptor[] {
12
+ return [
13
+ {
14
+ category: 'Workbench',
15
+ icon: 'codicon-terminal',
16
+ id: MANAGE_COMMANDS_COMMAND_ID,
17
+ label: 'Manage Commands',
18
+ },
19
+ {
20
+ category: 'Workbench',
21
+ icon: 'codicon-keyboard',
22
+ id: MANAGE_KEYBINDINGS_COMMAND_ID,
23
+ label: 'Keyboard Shortcuts',
24
+ },
25
+ {
26
+ category: 'Workbench',
27
+ icon: 'codicon-extensions',
28
+ id: MANAGE_EXTENSIONS_COMMAND_ID,
29
+ label: 'Extensions',
30
+ },
31
+ {
32
+ category: 'Accounts',
33
+ icon: 'codicon-account',
34
+ id: MANAGE_ACCOUNTS_COMMAND_ID,
35
+ label: 'Manage Linked Accounts',
36
+ },
37
+ ];
38
+ }