@workbench-kit/react 0.0.2-prototype.0.2.33 → 0.0.2-prototype.0.2.35
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 +10 -10
- package/src/index.ts +12 -1
- package/src/overlay/ContextMenu.tsx +19 -2
- package/src/overlay/index.ts +6 -1
- package/src/overlay/pointerPassthroughRegion.ts +16 -0
- package/src/overlay/usePointerPassthroughRegion.ts +35 -6
- package/src/workbench/commands/commands.ts +16 -0
- package/src/workbench/editor/WorkbenchEditorTabs.tsx +12 -2
- package/src/workbench/editor/editorTabContextMenu.ts +41 -2
- package/src/workbench/editor/useWorkbenchEditorTabContextMenu.tsx +16 -2
- package/src/workbench/editor-tabs/index.ts +1 -0
- package/src/workbench/index.ts +3 -0
- package/src/workbench/management/ExtensionManagementPanel.tsx +52 -10
- package/src/workbench/management/ExtensionManagementSidebar.tsx +60 -10
- package/src/workbench/management/index.ts +1 -0
- package/src/workbench/management/types.ts +9 -0
- package/src/workbench/settings/StructuredDataSchemaPanel.tsx +18 -4
- package/src/workbench/settings/structuredDataSchema.ts +24 -13
- package/src/workbench/settings/structuredDataSchemaSection.ts +4 -0
- package/src/workbench/settings/structuredDataSchemaTable.ts +5 -2
- package/src/workbench/shell/shell.ts +1 -0
- package/src/workbench/views/extensions-sidebar.css +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workbench-kit/react",
|
|
3
|
-
"version": "0.0.2-prototype.0.2.
|
|
3
|
+
"version": "0.0.2-prototype.0.2.35",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -73,15 +73,15 @@
|
|
|
73
73
|
"@vscode/codicons": "^0.0.45",
|
|
74
74
|
"remark-gfm": "^4.0.1",
|
|
75
75
|
"rehype-sanitize": "^6.0.0",
|
|
76
|
-
"@workbench-kit/adapters": "0.0.2-prototype.0.2.
|
|
77
|
-
"@workbench-kit/contracts": "0.0.2-prototype.0.2.
|
|
78
|
-
"@workbench-kit/monaco": "0.0.2-prototype.0.2.
|
|
79
|
-
"@workbench-kit/
|
|
80
|
-
"@workbench-kit/runtime": "0.0.2-prototype.0.2.
|
|
81
|
-
"@workbench-kit/services": "0.0.2-prototype.0.2.
|
|
82
|
-
"@workbench-kit/
|
|
83
|
-
"@workbench-kit/
|
|
84
|
-
"@workbench-kit/tokens": "0.0.2-prototype.0.2.
|
|
76
|
+
"@workbench-kit/adapters": "0.0.2-prototype.0.2.35",
|
|
77
|
+
"@workbench-kit/contracts": "0.0.2-prototype.0.2.35",
|
|
78
|
+
"@workbench-kit/monaco": "0.0.2-prototype.0.2.35",
|
|
79
|
+
"@workbench-kit/jdw": "0.0.2-prototype.0.2.35",
|
|
80
|
+
"@workbench-kit/runtime": "0.0.2-prototype.0.2.35",
|
|
81
|
+
"@workbench-kit/services": "0.0.2-prototype.0.2.35",
|
|
82
|
+
"@workbench-kit/workspace": "0.0.2-prototype.0.2.35",
|
|
83
|
+
"@workbench-kit/platform": "0.0.2-prototype.0.2.35",
|
|
84
|
+
"@workbench-kit/tokens": "0.0.2-prototype.0.2.35"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"react": "^19.0.0",
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,11 @@ export type {
|
|
|
19
19
|
WorkbenchIconResolver,
|
|
20
20
|
} from './icons';
|
|
21
21
|
export { commandMenuItemsToContextMenuItems } from './workbench/commands/commands';
|
|
22
|
+
export type {
|
|
23
|
+
ExtensionManagementEntry,
|
|
24
|
+
ExtensionManagementPanelProps,
|
|
25
|
+
ExtensionManagementTransition,
|
|
26
|
+
} from './workbench/management';
|
|
22
27
|
export {
|
|
23
28
|
WorkbenchArtifactModeControls,
|
|
24
29
|
WorkbenchArtifactPreview,
|
|
@@ -213,15 +218,20 @@ export type {
|
|
|
213
218
|
export {
|
|
214
219
|
createPointerPassthroughController,
|
|
215
220
|
isPointerOverHitRegion,
|
|
221
|
+
resolvePointerHitTarget,
|
|
216
222
|
} from './overlay/pointerPassthroughRegion';
|
|
217
223
|
export type {
|
|
224
|
+
PointerHitTestDocument,
|
|
218
225
|
PointerOverHitRegionOptions,
|
|
219
226
|
PointerPassthroughController,
|
|
220
227
|
PointerPassthroughControllerOptions,
|
|
221
228
|
PointerPassthroughPort,
|
|
222
229
|
} from './overlay/pointerPassthroughRegion';
|
|
223
230
|
export { usePointerPassthroughRegion } from './overlay/usePointerPassthroughRegion';
|
|
224
|
-
export type {
|
|
231
|
+
export type {
|
|
232
|
+
PointerHitTargetResolver,
|
|
233
|
+
UsePointerPassthroughRegionOptions,
|
|
234
|
+
} from './overlay/usePointerPassthroughRegion';
|
|
225
235
|
export {
|
|
226
236
|
getWorkbenchStatusDescriptor,
|
|
227
237
|
getWorkbenchStatusLabel,
|
|
@@ -743,6 +753,7 @@ export {
|
|
|
743
753
|
} from './workbench/editor/editorTabContextMenu';
|
|
744
754
|
export type {
|
|
745
755
|
CreateWorkbenchStandaloneEditorTabCommandContextInput,
|
|
756
|
+
CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
|
|
746
757
|
WorkbenchStandaloneEditorTabLike,
|
|
747
758
|
} from './workbench/editor/editorTabContextMenu';
|
|
748
759
|
export { useWorkbenchEditorTabContextMenu } from './workbench/editor/useWorkbenchEditorTabContextMenu';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './context-menu.css';
|
|
2
|
-
import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react';
|
|
3
3
|
import { Button } from '../primitives/button';
|
|
4
4
|
import { cxCodicon } from '../utils/codicon';
|
|
5
5
|
import { cx } from '../utils/cx';
|
|
@@ -13,6 +13,8 @@ export interface ContextMenuProps {
|
|
|
13
13
|
ariaLabel?: string | undefined;
|
|
14
14
|
className?: string | undefined;
|
|
15
15
|
items: ContextMenuItem[];
|
|
16
|
+
/** Explicit focus target restored only when Escape dismisses the menu. */
|
|
17
|
+
returnFocusTarget?: HTMLElement | null | undefined;
|
|
16
18
|
x: number;
|
|
17
19
|
y: number;
|
|
18
20
|
onClose: () => void;
|
|
@@ -77,18 +79,33 @@ export function ContextMenu({
|
|
|
77
79
|
ariaLabel = 'Context menu',
|
|
78
80
|
className,
|
|
79
81
|
items,
|
|
82
|
+
returnFocusTarget,
|
|
80
83
|
x,
|
|
81
84
|
y,
|
|
82
85
|
onClose,
|
|
83
86
|
}: ContextMenuProps) {
|
|
84
87
|
const ref = useRef<HTMLDivElement>(null);
|
|
88
|
+
const [fallbackReturnFocusTarget] = useState<HTMLElement | null>(() => {
|
|
89
|
+
if (typeof document === 'undefined') {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
93
|
+
});
|
|
85
94
|
const position = useClampedFixedOverlayPosition(ref, { x, y }, items.length);
|
|
86
95
|
const hasIcons = menuHasIcons(items);
|
|
87
96
|
const hasShortcuts = menuHasShortcuts(items);
|
|
88
97
|
const enabledIndexes = useMemo(() => getEnabledItemIndexes(items), [items]);
|
|
89
98
|
const [highlightedIndex, setHighlightedIndex] = useState(() => enabledIndexes[0] ?? -1);
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
const restoreFocusOnEscape = useCallback(() => {
|
|
101
|
+
const target = returnFocusTarget ?? fallbackReturnFocusTarget;
|
|
102
|
+
if (target?.isConnected) {
|
|
103
|
+
target.focus();
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}, [fallbackReturnFocusTarget, returnFocusTarget]);
|
|
107
|
+
|
|
108
|
+
useFixedOverlayDismiss({ containerRef: ref, onClose, onEscape: restoreFocusOnEscape });
|
|
92
109
|
|
|
93
110
|
useEffect(() => {
|
|
94
111
|
const handleContextMenu = (event: MouseEvent) => {
|
package/src/overlay/index.ts
CHANGED
|
@@ -20,12 +20,17 @@ export type {
|
|
|
20
20
|
export {
|
|
21
21
|
createPointerPassthroughController,
|
|
22
22
|
isPointerOverHitRegion,
|
|
23
|
+
resolvePointerHitTarget,
|
|
23
24
|
} from './pointerPassthroughRegion';
|
|
24
25
|
export type {
|
|
26
|
+
PointerHitTestDocument,
|
|
25
27
|
PointerOverHitRegionOptions,
|
|
26
28
|
PointerPassthroughController,
|
|
27
29
|
PointerPassthroughControllerOptions,
|
|
28
30
|
PointerPassthroughPort,
|
|
29
31
|
} from './pointerPassthroughRegion';
|
|
30
32
|
export { usePointerPassthroughRegion } from './usePointerPassthroughRegion';
|
|
31
|
-
export type {
|
|
33
|
+
export type {
|
|
34
|
+
PointerHitTargetResolver,
|
|
35
|
+
UsePointerPassthroughRegionOptions,
|
|
36
|
+
} from './usePointerPassthroughRegion';
|
|
@@ -2,6 +2,22 @@ export interface PointerPassthroughPort {
|
|
|
2
2
|
setPointerPassthrough(enabled: boolean): void | Promise<void>;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export interface PointerHitTestDocument {
|
|
6
|
+
elementFromPoint(x: number, y: number): Element | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Resolve the painted element at finite viewport coordinates. */
|
|
10
|
+
export function resolvePointerHitTarget(
|
|
11
|
+
clientX: number,
|
|
12
|
+
clientY: number,
|
|
13
|
+
documentSurface: PointerHitTestDocument = document,
|
|
14
|
+
): Element | null {
|
|
15
|
+
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return documentSurface.elementFromPoint(clientX, clientY);
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
export interface PointerOverHitRegionOptions {
|
|
6
22
|
readonly hitSelectors: readonly string[];
|
|
7
23
|
readonly controlSelectors?: readonly string[];
|
|
@@ -2,9 +2,12 @@ import { useEffect, useRef, type RefObject } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
createPointerPassthroughController,
|
|
5
|
+
resolvePointerHitTarget,
|
|
5
6
|
type PointerPassthroughPort,
|
|
6
7
|
} from './pointerPassthroughRegion';
|
|
7
8
|
|
|
9
|
+
export type PointerHitTargetResolver = (clientX: number, clientY: number) => EventTarget | null;
|
|
10
|
+
|
|
8
11
|
export interface UsePointerPassthroughRegionOptions {
|
|
9
12
|
readonly enabled: boolean;
|
|
10
13
|
readonly port: PointerPassthroughPort;
|
|
@@ -14,11 +17,15 @@ export interface UsePointerPassthroughRegionOptions {
|
|
|
14
17
|
readonly controlSelectors?: readonly string[];
|
|
15
18
|
/** Limit hit-testing to a subtree; defaults to the document. */
|
|
16
19
|
readonly rootRef?: RefObject<HTMLElement | null>;
|
|
20
|
+
/** Override painted-element resolution; defaults to `document.elementFromPoint`. */
|
|
21
|
+
readonly resolveHitTarget?: PointerHitTargetResolver;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
/**
|
|
20
25
|
* Toggles host pointer passthrough from renderer hit-testing.
|
|
21
|
-
* Uses `requestAnimationFrame` coalescing
|
|
26
|
+
* Uses `requestAnimationFrame` coalescing and painted-element hit-testing; no
|
|
27
|
+
* product selectors in kit. Electron can forward move events while ignoring
|
|
28
|
+
* mouse input, in which case `event.target` is not necessarily under the cursor.
|
|
22
29
|
*
|
|
23
30
|
* Host responsibilities:
|
|
24
31
|
* - Provide selector lists
|
|
@@ -26,7 +33,7 @@ export interface UsePointerPassthroughRegionOptions {
|
|
|
26
33
|
* - Pair with platform residency applicator for the main-process half
|
|
27
34
|
*/
|
|
28
35
|
export function usePointerPassthroughRegion(options: UsePointerPassthroughRegionOptions): void {
|
|
29
|
-
const { enabled, port, hitSelectors, controlSelectors, rootRef } = options;
|
|
36
|
+
const { enabled, port, hitSelectors, controlSelectors, rootRef, resolveHitTarget } = options;
|
|
30
37
|
|
|
31
38
|
const portRef = useRef(port);
|
|
32
39
|
portRef.current = port;
|
|
@@ -34,6 +41,8 @@ export function usePointerPassthroughRegion(options: UsePointerPassthroughRegion
|
|
|
34
41
|
hitSelectorsRef.current = hitSelectors;
|
|
35
42
|
const controlSelectorsRef = useRef(controlSelectors);
|
|
36
43
|
controlSelectorsRef.current = controlSelectors;
|
|
44
|
+
const resolveHitTargetRef = useRef(resolveHitTarget ?? resolvePointerHitTarget);
|
|
45
|
+
resolveHitTargetRef.current = resolveHitTarget ?? resolvePointerHitTarget;
|
|
37
46
|
|
|
38
47
|
useEffect(() => {
|
|
39
48
|
const controller = createPointerPassthroughController({
|
|
@@ -58,25 +67,45 @@ export function usePointerPassthroughRegion(options: UsePointerPassthroughRegion
|
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
let frame = 0;
|
|
61
|
-
let
|
|
70
|
+
let pendingX = 0;
|
|
71
|
+
let pendingY = 0;
|
|
62
72
|
|
|
63
73
|
const flush = () => {
|
|
64
74
|
frame = 0;
|
|
65
|
-
|
|
75
|
+
// Re-assert on every frame. Host state may also change outside this hook;
|
|
76
|
+
// the main-process port should make same-value updates a no-op.
|
|
77
|
+
controller.reset();
|
|
78
|
+
controller.handlePointerTarget(resolveHitTargetRef.current(pendingX, pendingY));
|
|
66
79
|
};
|
|
67
80
|
|
|
68
|
-
const onPointerMove = (event: PointerEvent) => {
|
|
69
|
-
|
|
81
|
+
const onPointerMove = (event: PointerEvent | MouseEvent) => {
|
|
82
|
+
pendingX = event.clientX;
|
|
83
|
+
pendingY = event.clientY;
|
|
70
84
|
if (frame !== 0) {
|
|
71
85
|
return;
|
|
72
86
|
}
|
|
73
87
|
frame = window.requestAnimationFrame(flush);
|
|
74
88
|
};
|
|
75
89
|
|
|
90
|
+
const onMouseLeave = () => {
|
|
91
|
+
if (frame !== 0) {
|
|
92
|
+
window.cancelAnimationFrame(frame);
|
|
93
|
+
frame = 0;
|
|
94
|
+
}
|
|
95
|
+
controller.reset();
|
|
96
|
+
controller.handlePointerTarget(null);
|
|
97
|
+
};
|
|
98
|
+
|
|
76
99
|
window.addEventListener('pointermove', onPointerMove, { passive: true });
|
|
100
|
+
// Some Electron builds forward mousemove rather than pointermove while the
|
|
101
|
+
// native window ignores input.
|
|
102
|
+
window.addEventListener('mousemove', onPointerMove, { passive: true });
|
|
103
|
+
document.documentElement.addEventListener('mouseleave', onMouseLeave);
|
|
77
104
|
|
|
78
105
|
return () => {
|
|
79
106
|
window.removeEventListener('pointermove', onPointerMove);
|
|
107
|
+
window.removeEventListener('mousemove', onPointerMove);
|
|
108
|
+
document.documentElement.removeEventListener('mouseleave', onMouseLeave);
|
|
80
109
|
if (frame !== 0) {
|
|
81
110
|
window.cancelAnimationFrame(frame);
|
|
82
111
|
}
|
|
@@ -23,6 +23,7 @@ export const WORKBENCH_EDITOR_SPLIT_RIGHT_COMMAND_ID = 'editor.splitRight';
|
|
|
23
23
|
export const WORKBENCH_EDITOR_SPLIT_DOWN_COMMAND_ID = 'editor.splitDown';
|
|
24
24
|
export const WORKBENCH_EDITOR_CLOSE_COMMAND_ID = 'editor.close';
|
|
25
25
|
export const WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID = 'editor.closeOthers';
|
|
26
|
+
export const WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID = 'editor.closeToRight';
|
|
26
27
|
export const WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID = 'editor.closeAll';
|
|
27
28
|
export const WORKBENCH_EDITOR_DELETE_COMMAND_ID = 'editor.delete';
|
|
28
29
|
export const WORKBENCH_WORKSPACE_NEW_FILE_COMMAND_ID = 'workspace.newFile';
|
|
@@ -113,6 +114,8 @@ export interface WorkbenchShellCommandPresetOptions<TActivityId extends string =
|
|
|
113
114
|
export interface WorkbenchEditorCommandContext {
|
|
114
115
|
canCloseAll: boolean;
|
|
115
116
|
canCloseOthers: boolean;
|
|
117
|
+
/** Optional so existing public command-context object literals remain source-compatible. */
|
|
118
|
+
canCloseToRight?: boolean | undefined;
|
|
116
119
|
canClosePath: boolean;
|
|
117
120
|
canCopyPath: boolean;
|
|
118
121
|
canDeletePath: boolean;
|
|
@@ -123,6 +126,8 @@ export interface WorkbenchEditorCommandContext {
|
|
|
123
126
|
canTogglePinned: boolean;
|
|
124
127
|
closeAll: () => void;
|
|
125
128
|
closeOthers: () => void;
|
|
129
|
+
/** Presence advertises support and makes Close to the right visible. */
|
|
130
|
+
closeToRight?: (() => void) | undefined;
|
|
126
131
|
closePath: () => void;
|
|
127
132
|
copyPath: () => void;
|
|
128
133
|
deletePath: () => void;
|
|
@@ -342,6 +347,14 @@ export function createWorkbenchEditorCommands({
|
|
|
342
347
|
label: 'Close others',
|
|
343
348
|
run: ({ closeOthers }) => closeOthers(),
|
|
344
349
|
},
|
|
350
|
+
{
|
|
351
|
+
id: WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID,
|
|
352
|
+
icon: 'codicon-close-all',
|
|
353
|
+
isEnabled: ({ canCloseToRight, filePath }) => Boolean(filePath && canCloseToRight),
|
|
354
|
+
isVisible: ({ closeToRight }) => Boolean(closeToRight),
|
|
355
|
+
label: 'Close to the right',
|
|
356
|
+
run: ({ closeToRight }) => closeToRight?.(),
|
|
357
|
+
},
|
|
345
358
|
{
|
|
346
359
|
id: WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID,
|
|
347
360
|
icon: 'codicon-close-all',
|
|
@@ -407,6 +420,9 @@ export function createWorkbenchStandaloneEditorTabMenuEntries(): CommandMenuEntr
|
|
|
407
420
|
commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID, {
|
|
408
421
|
surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
|
|
409
422
|
}),
|
|
423
|
+
commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID, {
|
|
424
|
+
surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
|
|
425
|
+
}),
|
|
410
426
|
commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID, {
|
|
411
427
|
surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
|
|
412
428
|
}),
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { EditorTabs, type EditorTabsProps } from '../../primitives/workbench-editor';
|
|
2
|
+
import type { ContextMenuItem } from '../../overlay/ContextMenu';
|
|
2
3
|
import { useWorkbenchEditorTabContextMenu } from './useWorkbenchEditorTabContextMenu';
|
|
3
4
|
|
|
4
5
|
export interface WorkbenchEditorTabsProps extends EditorTabsProps {
|
|
6
|
+
/** Additional host actions appended after the built-in close group. */
|
|
7
|
+
readonly getExtraTabContextMenuItems?:
|
|
8
|
+
((tabId: string) => readonly ContextMenuItem[] | undefined) | undefined;
|
|
5
9
|
/**
|
|
6
10
|
* Optional bulk close. Defaults to calling `onClose` for every closable tab.
|
|
7
11
|
*/
|
|
@@ -10,26 +14,32 @@ export interface WorkbenchEditorTabsProps extends EditorTabsProps {
|
|
|
10
14
|
* Optional close-others. Defaults to calling `onClose` for every other closable tab.
|
|
11
15
|
*/
|
|
12
16
|
readonly onCloseOthers?: ((tabId: string) => void) | undefined;
|
|
17
|
+
/** Optional close-to-right override. Defaults to closing each later closable tab. */
|
|
18
|
+
readonly onCloseToRight?: ((tabId: string) => void) | undefined;
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
|
-
* Editor tab strip with a built-in Close / Close others / Close all
|
|
22
|
+
* Editor tab strip with a built-in Close / Close others / Close to the right / Close all menu.
|
|
17
23
|
* Intended for `WorkbenchStandaloneShell` secondary areas that own tab state in the host.
|
|
18
|
-
* Tabs with `closable: false` keep Close disabled and are skipped by
|
|
24
|
+
* Tabs with `closable: false` keep Close disabled and are skipped by every bulk close action.
|
|
19
25
|
*/
|
|
20
26
|
export function WorkbenchEditorTabs({
|
|
27
|
+
getExtraTabContextMenuItems,
|
|
21
28
|
onClose,
|
|
22
29
|
onCloseAll,
|
|
23
30
|
onCloseOthers,
|
|
31
|
+
onCloseToRight,
|
|
24
32
|
onSelect,
|
|
25
33
|
onTabContextMenu,
|
|
26
34
|
tabs,
|
|
27
35
|
...props
|
|
28
36
|
}: WorkbenchEditorTabsProps) {
|
|
29
37
|
const tabContextMenu = useWorkbenchEditorTabContextMenu({
|
|
38
|
+
getExtraTabContextMenuItems,
|
|
30
39
|
onClose: onClose ?? (() => undefined),
|
|
31
40
|
onCloseAll,
|
|
32
41
|
onCloseOthers,
|
|
42
|
+
onCloseToRight,
|
|
33
43
|
onSelectTab: onSelect,
|
|
34
44
|
tabs,
|
|
35
45
|
});
|
|
@@ -29,25 +29,41 @@ export interface CreateWorkbenchStandaloneEditorTabCommandContextInput {
|
|
|
29
29
|
readonly onClose: (tabId: string) => void;
|
|
30
30
|
readonly onCloseAll?: (() => void) | undefined;
|
|
31
31
|
readonly onCloseOthers?: ((tabId: string) => void) | undefined;
|
|
32
|
+
readonly onCloseToRight?: ((tabId: string) => void) | undefined;
|
|
32
33
|
readonly tabId: string;
|
|
33
34
|
readonly tabs: readonly WorkbenchStandaloneEditorTabLike[];
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export interface CreateWorkbenchStandaloneEditorTabContextMenuItemsInput extends CreateWorkbenchStandaloneEditorTabCommandContextInput {
|
|
38
|
+
readonly getExtraTabContextMenuItems?:
|
|
39
|
+
((tabId: string) => readonly ContextMenuItem[] | undefined) | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
export function createWorkbenchStandaloneEditorTabCommandContext({
|
|
37
43
|
onClose,
|
|
38
44
|
onCloseAll,
|
|
39
45
|
onCloseOthers,
|
|
46
|
+
onCloseToRight,
|
|
40
47
|
tabId,
|
|
41
48
|
tabs,
|
|
42
49
|
}: CreateWorkbenchStandaloneEditorTabCommandContextInput): WorkbenchEditorCommandContext {
|
|
43
50
|
const targetTab = tabs.find((tab) => tab.id === tabId);
|
|
51
|
+
const targetIndex = tabs.findIndex((tab) => tab.id === tabId);
|
|
44
52
|
const closableTabIds = tabs.filter(isWorkbenchEditorTabClosable).map((tab) => tab.id);
|
|
45
53
|
const otherClosableTabIds = closableTabIds.filter((id) => id !== tabId);
|
|
54
|
+
const rightClosableTabIds =
|
|
55
|
+
targetIndex < 0
|
|
56
|
+
? []
|
|
57
|
+
: tabs
|
|
58
|
+
.slice(targetIndex + 1)
|
|
59
|
+
.filter(isWorkbenchEditorTabClosable)
|
|
60
|
+
.map((tab) => tab.id);
|
|
46
61
|
const canCloseTarget = Boolean(targetTab && isWorkbenchEditorTabClosable(targetTab));
|
|
47
62
|
|
|
48
63
|
return {
|
|
49
64
|
canCloseAll: closableTabIds.length > 0,
|
|
50
65
|
canCloseOthers: otherClosableTabIds.length > 0,
|
|
66
|
+
canCloseToRight: rightClosableTabIds.length > 0,
|
|
51
67
|
canClosePath: canCloseTarget,
|
|
52
68
|
canCopyPath: false,
|
|
53
69
|
canDeletePath: false,
|
|
@@ -74,6 +90,18 @@ export function createWorkbenchStandaloneEditorTabCommandContext({
|
|
|
74
90
|
onClose(id);
|
|
75
91
|
}
|
|
76
92
|
},
|
|
93
|
+
closeToRight: () => {
|
|
94
|
+
if (rightClosableTabIds.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (onCloseToRight) {
|
|
98
|
+
onCloseToRight(tabId);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const id of rightClosableTabIds) {
|
|
102
|
+
onClose(id);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
77
105
|
closePath: () => {
|
|
78
106
|
if (canCloseTarget) {
|
|
79
107
|
onClose(tabId);
|
|
@@ -95,11 +123,11 @@ export function createWorkbenchStandaloneEditorTabCommandContext({
|
|
|
95
123
|
}
|
|
96
124
|
|
|
97
125
|
export function createWorkbenchStandaloneEditorTabContextMenuItems(
|
|
98
|
-
input:
|
|
126
|
+
input: CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
|
|
99
127
|
): ContextMenuItem[] {
|
|
100
128
|
const context = createWorkbenchStandaloneEditorTabCommandContext(input);
|
|
101
129
|
|
|
102
|
-
|
|
130
|
+
const builtInItems = commandMenuItemsToContextMenuItems(
|
|
103
131
|
resolveCommandMenuItems({
|
|
104
132
|
context,
|
|
105
133
|
entries: standaloneEditorTabMenuEntries,
|
|
@@ -108,4 +136,15 @@ export function createWorkbenchStandaloneEditorTabContextMenuItems(
|
|
|
108
136
|
}),
|
|
109
137
|
(commandId) => executeCommand(editorCommandRegistry, commandId, context),
|
|
110
138
|
);
|
|
139
|
+
const extraItems = input.getExtraTabContextMenuItems?.(input.tabId) ?? [];
|
|
140
|
+
|
|
141
|
+
if (extraItems.length === 0) {
|
|
142
|
+
return builtInItems;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return [
|
|
146
|
+
...builtInItems,
|
|
147
|
+
{ id: 'standalone-tab-extra-separator', type: 'separator' },
|
|
148
|
+
...extraItems,
|
|
149
|
+
];
|
|
111
150
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { useCallback, useState, type MouseEvent as ReactMouseEvent, type ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import { ContextMenu } from '../../overlay/ContextMenu';
|
|
3
|
+
import { ContextMenu, type ContextMenuItem } from '../../overlay/ContextMenu';
|
|
4
4
|
import {
|
|
5
5
|
createWorkbenchStandaloneEditorTabContextMenuItems,
|
|
6
6
|
type WorkbenchStandaloneEditorTabLike,
|
|
7
7
|
} from './editorTabContextMenu';
|
|
8
8
|
|
|
9
9
|
export interface UseWorkbenchEditorTabContextMenuOptions {
|
|
10
|
+
readonly getExtraTabContextMenuItems?:
|
|
11
|
+
((tabId: string) => readonly ContextMenuItem[] | undefined) | undefined;
|
|
10
12
|
readonly onClose: (tabId: string) => void;
|
|
11
13
|
readonly onCloseAll?: (() => void) | undefined;
|
|
12
14
|
readonly onCloseOthers?: ((tabId: string) => void) | undefined;
|
|
15
|
+
readonly onCloseToRight?: ((tabId: string) => void) | undefined;
|
|
13
16
|
readonly onSelectTab?: ((tabId: string) => void) | undefined;
|
|
14
17
|
readonly tabs: readonly WorkbenchStandaloneEditorTabLike[];
|
|
15
18
|
}
|
|
@@ -20,13 +23,16 @@ export interface UseWorkbenchEditorTabContextMenuResult {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export function useWorkbenchEditorTabContextMenu({
|
|
26
|
+
getExtraTabContextMenuItems,
|
|
23
27
|
onClose,
|
|
24
28
|
onCloseAll,
|
|
25
29
|
onCloseOthers,
|
|
30
|
+
onCloseToRight,
|
|
26
31
|
onSelectTab,
|
|
27
32
|
tabs,
|
|
28
33
|
}: UseWorkbenchEditorTabContextMenuOptions): UseWorkbenchEditorTabContextMenuResult {
|
|
29
34
|
const [menuState, setMenuState] = useState<{
|
|
35
|
+
returnFocusTarget: HTMLElement;
|
|
30
36
|
tabId: string;
|
|
31
37
|
x: number;
|
|
32
38
|
y: number;
|
|
@@ -37,7 +43,12 @@ export function useWorkbenchEditorTabContextMenu({
|
|
|
37
43
|
event.preventDefault();
|
|
38
44
|
event.stopPropagation();
|
|
39
45
|
onSelectTab?.(tabId);
|
|
40
|
-
setMenuState({
|
|
46
|
+
setMenuState({
|
|
47
|
+
returnFocusTarget: event.currentTarget,
|
|
48
|
+
tabId,
|
|
49
|
+
x: event.clientX,
|
|
50
|
+
y: event.clientY,
|
|
51
|
+
});
|
|
41
52
|
},
|
|
42
53
|
[onSelectTab],
|
|
43
54
|
);
|
|
@@ -47,12 +58,15 @@ export function useWorkbenchEditorTabContextMenu({
|
|
|
47
58
|
<ContextMenu
|
|
48
59
|
ariaLabel="Editor tab menu"
|
|
49
60
|
items={createWorkbenchStandaloneEditorTabContextMenuItems({
|
|
61
|
+
getExtraTabContextMenuItems,
|
|
50
62
|
onClose,
|
|
51
63
|
onCloseAll,
|
|
52
64
|
onCloseOthers,
|
|
65
|
+
onCloseToRight,
|
|
53
66
|
tabId: menuState.tabId,
|
|
54
67
|
tabs,
|
|
55
68
|
})}
|
|
69
|
+
returnFocusTarget={menuState.returnFocusTarget}
|
|
56
70
|
x={menuState.x}
|
|
57
71
|
y={menuState.y}
|
|
58
72
|
onClose={() => setMenuState(null)}
|
|
@@ -6,6 +6,7 @@ export {
|
|
|
6
6
|
} from '../editor/editorTabContextMenu';
|
|
7
7
|
export type {
|
|
8
8
|
CreateWorkbenchStandaloneEditorTabCommandContextInput,
|
|
9
|
+
CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
|
|
9
10
|
WorkbenchStandaloneEditorTabLike,
|
|
10
11
|
} from '../editor/editorTabContextMenu';
|
|
11
12
|
export { useEditorTabsStripDnd } from '../editor/useEditorTabsStripDnd';
|
package/src/workbench/index.ts
CHANGED
|
@@ -298,6 +298,7 @@ export {
|
|
|
298
298
|
WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID,
|
|
299
299
|
WORKBENCH_EDITOR_CLOSE_COMMAND_ID,
|
|
300
300
|
WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID,
|
|
301
|
+
WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID,
|
|
301
302
|
WORKBENCH_EDITOR_COPY_PATH_COMMAND_ID,
|
|
302
303
|
WORKBENCH_EDITOR_DELETE_COMMAND_ID,
|
|
303
304
|
WORKBENCH_EDITOR_DISCARD_CHANGES_COMMAND_ID,
|
|
@@ -449,6 +450,7 @@ export type {
|
|
|
449
450
|
ExtensionManagementEntry,
|
|
450
451
|
ExtensionManagementPanelProps,
|
|
451
452
|
ExtensionManagementSidebarProps,
|
|
453
|
+
ExtensionManagementTransition,
|
|
452
454
|
KeybindingManagementPanelProps,
|
|
453
455
|
ManagementPanelFrameProps,
|
|
454
456
|
ManagementPanelToolbarProps,
|
|
@@ -605,6 +607,7 @@ export {
|
|
|
605
607
|
} from './editor/editorTabContextMenu';
|
|
606
608
|
export type {
|
|
607
609
|
CreateWorkbenchStandaloneEditorTabCommandContextInput,
|
|
610
|
+
CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
|
|
608
611
|
WorkbenchStandaloneEditorTabLike,
|
|
609
612
|
} from './editor/editorTabContextMenu';
|
|
610
613
|
export { useWorkbenchEditorTabContextMenu } from './editor/useWorkbenchEditorTabContextMenu';
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
ExtensionManagementEntry,
|
|
25
25
|
ExtensionManagementFeatureSummary,
|
|
26
26
|
ExtensionManagementPanelProps,
|
|
27
|
+
ExtensionManagementTransition,
|
|
27
28
|
} from './types.js';
|
|
28
29
|
|
|
29
30
|
const BROWSE_CATEGORIES = ['all', 'feature', 'editor', 'theme', 'language'] as const;
|
|
@@ -46,6 +47,7 @@ export function ExtensionManagementPanel({
|
|
|
46
47
|
onInstall,
|
|
47
48
|
onRememberInstallTrust,
|
|
48
49
|
onToggleEnabled,
|
|
50
|
+
onUninstall,
|
|
49
51
|
}: ExtensionManagementPanelProps) {
|
|
50
52
|
const [activeTab, setActiveTab] = useState<'installed' | 'browse'>(
|
|
51
53
|
EXTENSION_MANAGEMENT_DEFAULT_TAB,
|
|
@@ -93,6 +95,7 @@ export function ExtensionManagementPanel({
|
|
|
93
95
|
summary={`${filteredInstalled.length} of ${installedEntries.length} visible`}
|
|
94
96
|
onQueryChange={setInstalledQuery}
|
|
95
97
|
onToggleEnabled={onToggleEnabled}
|
|
98
|
+
onUninstall={onUninstall}
|
|
96
99
|
/>
|
|
97
100
|
),
|
|
98
101
|
},
|
|
@@ -122,7 +125,8 @@ export function ExtensionManagementPanel({
|
|
|
122
125
|
onSelect={(tabId) => setActiveTab(tabId as 'installed' | 'browse')}
|
|
123
126
|
/>
|
|
124
127
|
<ManagementPanelNotice>
|
|
125
|
-
|
|
128
|
+
Eligible unselected theme packs apply immediately. Other extension changes reload the
|
|
129
|
+
workbench.
|
|
126
130
|
</ManagementPanelNotice>
|
|
127
131
|
</ManagementPanelFrame>
|
|
128
132
|
);
|
|
@@ -133,6 +137,7 @@ function InstalledExtensionsTab({
|
|
|
133
137
|
entries,
|
|
134
138
|
onQueryChange,
|
|
135
139
|
onToggleEnabled,
|
|
140
|
+
onUninstall,
|
|
136
141
|
query,
|
|
137
142
|
summary,
|
|
138
143
|
}: {
|
|
@@ -140,6 +145,7 @@ function InstalledExtensionsTab({
|
|
|
140
145
|
entries: readonly ExtensionManagementEntry[];
|
|
141
146
|
onQueryChange: (query: string) => void;
|
|
142
147
|
onToggleEnabled?: ExtensionManagementPanelProps['onToggleEnabled'];
|
|
148
|
+
onUninstall?: ExtensionManagementPanelProps['onUninstall'];
|
|
143
149
|
query: string;
|
|
144
150
|
summary: string;
|
|
145
151
|
}) {
|
|
@@ -160,15 +166,27 @@ function InstalledExtensionsTab({
|
|
|
160
166
|
<li key={entry.id}>
|
|
161
167
|
<ManagementCard
|
|
162
168
|
actions={
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
<>
|
|
170
|
+
<Button
|
|
171
|
+
compact
|
|
172
|
+
disabled={!onToggleEnabled || entry.source === 'bundled'}
|
|
173
|
+
type="button"
|
|
174
|
+
variant={entry.enabled ? 'default' : 'primary'}
|
|
175
|
+
onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
|
|
176
|
+
>
|
|
177
|
+
{entry.enabled ? 'Disable' : 'Enable'}
|
|
178
|
+
</Button>
|
|
179
|
+
{entry.canUninstall === true && onUninstall ? (
|
|
180
|
+
<Button
|
|
181
|
+
compact
|
|
182
|
+
type="button"
|
|
183
|
+
variant="danger"
|
|
184
|
+
onClick={() => onUninstall(entry)}
|
|
185
|
+
>
|
|
186
|
+
Uninstall
|
|
187
|
+
</Button>
|
|
188
|
+
) : null}
|
|
189
|
+
</>
|
|
172
190
|
}
|
|
173
191
|
badges={
|
|
174
192
|
<>
|
|
@@ -180,6 +198,14 @@ function InstalledExtensionsTab({
|
|
|
180
198
|
{entry.enabled ? 'Enabled' : 'Disabled'}
|
|
181
199
|
</Badge>
|
|
182
200
|
)}
|
|
201
|
+
{entry.transition ? (
|
|
202
|
+
<Badge
|
|
203
|
+
title={entry.transition.message}
|
|
204
|
+
variant={extensionTransitionBadgeVariant(entry.transition.kind)}
|
|
205
|
+
>
|
|
206
|
+
{extensionTransitionLabel(entry.transition.kind)}
|
|
207
|
+
</Badge>
|
|
208
|
+
) : null}
|
|
183
209
|
</>
|
|
184
210
|
}
|
|
185
211
|
description={entry.description}
|
|
@@ -198,6 +224,22 @@ function InstalledExtensionsTab({
|
|
|
198
224
|
);
|
|
199
225
|
}
|
|
200
226
|
|
|
227
|
+
function extensionTransitionLabel(kind: ExtensionManagementTransition['kind']): string {
|
|
228
|
+
if (kind === 'reloadRequired') {
|
|
229
|
+
return 'Reload required';
|
|
230
|
+
}
|
|
231
|
+
return kind === 'failed' ? 'Failed' : 'Applied';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function extensionTransitionBadgeVariant(
|
|
235
|
+
kind: ExtensionManagementTransition['kind'],
|
|
236
|
+
): 'accent' | 'danger' | 'muted' {
|
|
237
|
+
if (kind === 'failed') {
|
|
238
|
+
return 'danger';
|
|
239
|
+
}
|
|
240
|
+
return kind === 'applied' ? 'accent' : 'muted';
|
|
241
|
+
}
|
|
242
|
+
|
|
201
243
|
function ExtensionFeatureDetails({ entry }: { entry: ExtensionManagementEntry }) {
|
|
202
244
|
const rows = getExtensionFeatureDetailRows(entry.features);
|
|
203
245
|
const diagnostics = entry.diagnostics ?? [];
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
ExtensionManagementEntry,
|
|
27
27
|
ExtensionManagementFeatureSummary,
|
|
28
28
|
ExtensionManagementPanelProps,
|
|
29
|
+
ExtensionManagementTransition,
|
|
29
30
|
} from './types.js';
|
|
30
31
|
|
|
31
32
|
const BROWSE_CATEGORIES = ['all', 'feature', 'editor', 'theme', 'language'] as const;
|
|
@@ -43,6 +44,7 @@ export interface ExtensionManagementSidebarProps extends ExtensionManagementPane
|
|
|
43
44
|
emptyMarketplaceLabel?: string | undefined;
|
|
44
45
|
missingExtensionIds?: readonly string[] | undefined;
|
|
45
46
|
pendingAction?: ExtensionManagementPendingAction | undefined;
|
|
47
|
+
pendingUninstallEntryId?: string | undefined;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export interface ExtensionManagementPendingAction {
|
|
@@ -64,7 +66,9 @@ export function ExtensionManagementSidebar({
|
|
|
64
66
|
onInstall,
|
|
65
67
|
onRememberInstallTrust,
|
|
66
68
|
onToggleEnabled,
|
|
69
|
+
onUninstall,
|
|
67
70
|
pendingAction,
|
|
71
|
+
pendingUninstallEntryId,
|
|
68
72
|
}: ExtensionManagementSidebarProps) {
|
|
69
73
|
const [activeTab, setActiveTab] = useState<'installed' | 'marketplace'>(defaultTab);
|
|
70
74
|
const [query, setQuery] = useState('');
|
|
@@ -102,7 +106,8 @@ export function ExtensionManagementSidebar({
|
|
|
102
106
|
footer={
|
|
103
107
|
<p className="workbench-extensions-sidebar__notice" role="note">
|
|
104
108
|
<i aria-hidden className={cxCodicon('codicon-info')} />
|
|
105
|
-
|
|
109
|
+
Eligible unselected theme packs apply immediately. Other extension changes reload the
|
|
110
|
+
workbench.
|
|
106
111
|
</p>
|
|
107
112
|
}
|
|
108
113
|
footerPlacement="overlay"
|
|
@@ -167,7 +172,9 @@ export function ExtensionManagementSidebar({
|
|
|
167
172
|
emptyLabel={emptyInstalledLabel}
|
|
168
173
|
entries={filteredInstalled}
|
|
169
174
|
pendingAction={pendingAction}
|
|
175
|
+
pendingUninstallEntryId={pendingUninstallEntryId}
|
|
170
176
|
onToggleEnabled={onToggleEnabled}
|
|
177
|
+
onUninstall={onUninstall}
|
|
171
178
|
/>
|
|
172
179
|
)}
|
|
173
180
|
</SideBarViewFrame>
|
|
@@ -178,12 +185,16 @@ function InstalledExtensionList({
|
|
|
178
185
|
emptyLabel,
|
|
179
186
|
entries,
|
|
180
187
|
onToggleEnabled,
|
|
188
|
+
onUninstall,
|
|
181
189
|
pendingAction,
|
|
190
|
+
pendingUninstallEntryId,
|
|
182
191
|
}: {
|
|
183
192
|
emptyLabel: string;
|
|
184
193
|
entries: readonly ExtensionManagementEntry[];
|
|
185
194
|
onToggleEnabled?: ExtensionManagementPanelProps['onToggleEnabled'];
|
|
195
|
+
onUninstall?: ExtensionManagementPanelProps['onUninstall'];
|
|
186
196
|
pendingAction?: ExtensionManagementPendingAction | undefined;
|
|
197
|
+
pendingUninstallEntryId?: string | undefined;
|
|
187
198
|
}) {
|
|
188
199
|
if (entries.length === 0) {
|
|
189
200
|
return (
|
|
@@ -198,6 +209,8 @@ function InstalledExtensionList({
|
|
|
198
209
|
{entries.map((entry) => {
|
|
199
210
|
const isPendingToggle =
|
|
200
211
|
pendingAction?.kind === 'toggle' && pendingAction.entryId === entry.id;
|
|
212
|
+
const isPendingUninstall = pendingUninstallEntryId === entry.id;
|
|
213
|
+
const isPending = isPendingToggle || isPendingUninstall;
|
|
201
214
|
const errorDiagnostics = (entry.diagnostics ?? []).filter(
|
|
202
215
|
(diagnostic) => diagnostic.severity === 'error',
|
|
203
216
|
);
|
|
@@ -209,15 +222,28 @@ function InstalledExtensionList({
|
|
|
209
222
|
<ExtensionSidebarListItem
|
|
210
223
|
key={entry.id}
|
|
211
224
|
action={
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
<>
|
|
226
|
+
<Button
|
|
227
|
+
compact
|
|
228
|
+
disabled={!onToggleEnabled || entry.source === 'bundled' || isPending}
|
|
229
|
+
type="button"
|
|
230
|
+
variant={entry.enabled ? 'default' : 'primary'}
|
|
231
|
+
onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
|
|
232
|
+
>
|
|
233
|
+
{isPendingToggle ? 'Reloading…' : entry.enabled ? 'Disable' : 'Enable'}
|
|
234
|
+
</Button>
|
|
235
|
+
{entry.canUninstall === true && onUninstall ? (
|
|
236
|
+
<Button
|
|
237
|
+
compact
|
|
238
|
+
disabled={isPending}
|
|
239
|
+
type="button"
|
|
240
|
+
variant="danger"
|
|
241
|
+
onClick={() => onUninstall(entry)}
|
|
242
|
+
>
|
|
243
|
+
{isPendingUninstall ? 'Reloading…' : 'Uninstall'}
|
|
244
|
+
</Button>
|
|
245
|
+
) : null}
|
|
246
|
+
</>
|
|
221
247
|
}
|
|
222
248
|
category={entry.category}
|
|
223
249
|
description={entry.description}
|
|
@@ -248,6 +274,14 @@ function InstalledExtensionList({
|
|
|
248
274
|
{warningDiagnostics.length} warning{warningDiagnostics.length === 1 ? '' : 's'}
|
|
249
275
|
</Badge>
|
|
250
276
|
) : null}
|
|
277
|
+
{entry.transition ? (
|
|
278
|
+
<Badge
|
|
279
|
+
title={entry.transition.message}
|
|
280
|
+
variant={extensionTransitionBadgeVariant(entry.transition.kind)}
|
|
281
|
+
>
|
|
282
|
+
{extensionTransitionLabel(entry.transition.kind)}
|
|
283
|
+
</Badge>
|
|
284
|
+
) : null}
|
|
251
285
|
</>
|
|
252
286
|
}
|
|
253
287
|
title={entry.displayName}
|
|
@@ -258,6 +292,22 @@ function InstalledExtensionList({
|
|
|
258
292
|
);
|
|
259
293
|
}
|
|
260
294
|
|
|
295
|
+
function extensionTransitionLabel(kind: ExtensionManagementTransition['kind']): string {
|
|
296
|
+
if (kind === 'reloadRequired') {
|
|
297
|
+
return 'Reload required';
|
|
298
|
+
}
|
|
299
|
+
return kind === 'failed' ? 'Failed' : 'Applied';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function extensionTransitionBadgeVariant(
|
|
303
|
+
kind: ExtensionManagementTransition['kind'],
|
|
304
|
+
): 'accent' | 'danger' | 'muted' {
|
|
305
|
+
if (kind === 'failed') {
|
|
306
|
+
return 'danger';
|
|
307
|
+
}
|
|
308
|
+
return kind === 'applied' ? 'accent' : 'muted';
|
|
309
|
+
}
|
|
310
|
+
|
|
261
311
|
function MarketplaceExtensionList({
|
|
262
312
|
catalogError,
|
|
263
313
|
catalogLoading,
|
|
@@ -82,6 +82,11 @@ export interface ExtensionManagementDiagnosticSummary {
|
|
|
82
82
|
readonly severity: 'error' | 'warning';
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export interface ExtensionManagementTransition {
|
|
86
|
+
readonly kind: 'applied' | 'reloadRequired' | 'failed';
|
|
87
|
+
readonly message: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
85
90
|
export interface ExtensionInstallPlanSummary {
|
|
86
91
|
readonly blocked: boolean;
|
|
87
92
|
readonly diagnostics?: readonly ExtensionManagementDiagnosticSummary[] | undefined;
|
|
@@ -92,6 +97,8 @@ export interface ExtensionInstallPlanSummary {
|
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
export interface ExtensionManagementEntry {
|
|
100
|
+
/** True only when a persisted installed record currently owns this entry. */
|
|
101
|
+
readonly canUninstall?: boolean;
|
|
95
102
|
readonly category: string;
|
|
96
103
|
readonly description?: string | undefined;
|
|
97
104
|
readonly diagnostics?: readonly ExtensionManagementDiagnosticSummary[] | undefined;
|
|
@@ -102,6 +109,7 @@ export interface ExtensionManagementEntry {
|
|
|
102
109
|
readonly installedAt?: string | undefined;
|
|
103
110
|
readonly manifestUrl?: string | undefined;
|
|
104
111
|
readonly source: ExtensionManagementEntrySource;
|
|
112
|
+
readonly transition?: ExtensionManagementTransition;
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
export interface ExtensionCatalogBrowseEntry {
|
|
@@ -128,6 +136,7 @@ export interface ExtensionManagementPanelProps {
|
|
|
128
136
|
installedEntries: readonly ExtensionManagementEntry[];
|
|
129
137
|
onInstall?:
|
|
130
138
|
((entry: ExtensionCatalogBrowseEntry, options?: ExtensionInstallOptions) => void) | undefined;
|
|
139
|
+
onUninstall?: (entry: ExtensionManagementEntry) => void;
|
|
131
140
|
onToggleEnabled?: ((entry: ExtensionManagementEntry, enabled: boolean) => void) | undefined;
|
|
132
141
|
/**
|
|
133
142
|
* Host-owned durable trust check. When true for an entry, the install confirm
|
|
@@ -6,6 +6,7 @@ import { ScrollArea } from '../../primitives/scroll-area';
|
|
|
6
6
|
import { cx } from '../../utils/cx';
|
|
7
7
|
import { WorkbenchSectionTabPanel } from './SectionTabPanel';
|
|
8
8
|
import { WorkbenchSectionedPanel } from './SectionedPanel';
|
|
9
|
+
import { getWorkbenchStructuredDataSchemaPathSegments } from './structuredDataSchemaSection';
|
|
9
10
|
import {
|
|
10
11
|
WorkbenchStructuredDataSchemaFieldInput,
|
|
11
12
|
appendWorkbenchStructuredDataSchemaTableRow,
|
|
@@ -162,7 +163,10 @@ function getSchemaFieldValue({
|
|
|
162
163
|
sectionValue: unknown;
|
|
163
164
|
}) {
|
|
164
165
|
const fullPath = getWorkbenchStructuredDataSchemaFieldDataPath(section, fieldPath);
|
|
165
|
-
const rootValue = getWorkbenchStructuredDataValue(
|
|
166
|
+
const rootValue = getWorkbenchStructuredDataValue(
|
|
167
|
+
data,
|
|
168
|
+
getWorkbenchStructuredDataSchemaPathSegments(fullPath),
|
|
169
|
+
);
|
|
166
170
|
if (rootValue !== null && rootValue !== undefined) return rootValue;
|
|
167
171
|
|
|
168
172
|
const record = asWorkbenchStructuredDataRecord(sectionValue);
|
|
@@ -170,7 +174,8 @@ function getSchemaFieldValue({
|
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
function formatSchemaFieldErrorKey(path: string | readonly string[]) {
|
|
173
|
-
const segments =
|
|
177
|
+
const segments =
|
|
178
|
+
typeof path === 'string' ? getWorkbenchStructuredDataSchemaPathSegments(path) : path;
|
|
174
179
|
return segments.filter(Boolean).join('.');
|
|
175
180
|
}
|
|
176
181
|
|
|
@@ -286,7 +291,11 @@ function renderSchemaFormSection({
|
|
|
286
291
|
mergedFieldErrors: fieldErrors,
|
|
287
292
|
onValidateField,
|
|
288
293
|
value: fieldValue,
|
|
289
|
-
onValueChange: (nextValue) =>
|
|
294
|
+
onValueChange: (nextValue) =>
|
|
295
|
+
onDataValueChange(
|
|
296
|
+
getWorkbenchStructuredDataSchemaPathSegments(dataPath),
|
|
297
|
+
nextValue,
|
|
298
|
+
),
|
|
290
299
|
})
|
|
291
300
|
: null;
|
|
292
301
|
|
|
@@ -358,7 +367,12 @@ function renderSchemaFormSection({
|
|
|
358
367
|
readOnly={readOnly}
|
|
359
368
|
textareaClassName={classNames.settingControlTextarea}
|
|
360
369
|
value={entry}
|
|
361
|
-
onValueChange={(nextValue) =>
|
|
370
|
+
onValueChange={(nextValue) =>
|
|
371
|
+
onDataValueChange(
|
|
372
|
+
getWorkbenchStructuredDataSchemaPathSegments(dataPath),
|
|
373
|
+
nextValue,
|
|
374
|
+
)
|
|
375
|
+
}
|
|
362
376
|
/>
|
|
363
377
|
</div>
|
|
364
378
|
</Field>
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
getWorkbenchStructuredDataSchemaFieldDataPath,
|
|
21
21
|
getWorkbenchStructuredDataSchemaFieldDefinition,
|
|
22
|
+
getWorkbenchStructuredDataSchemaPathSegments,
|
|
22
23
|
getWorkbenchStructuredDataSchemaSectionFieldLabel,
|
|
23
24
|
getWorkbenchStructuredDataSchemaSectionPath,
|
|
24
25
|
} from './structuredDataSchemaSection';
|
|
@@ -206,14 +207,18 @@ export function getWorkbenchStructuredDataSchemaDocumentSectionValue({
|
|
|
206
207
|
section: WorkbenchStructuredDataSchemaSectionSummary;
|
|
207
208
|
}) {
|
|
208
209
|
const sectionKey = getWorkbenchStructuredDataSchemaSectionPath(section);
|
|
209
|
-
|
|
210
|
-
if (!sectionKey || !record) return sectionKey ? null : data;
|
|
211
|
-
if (sectionKey in record) return record[sectionKey];
|
|
210
|
+
if (!sectionKey) return data;
|
|
212
211
|
|
|
213
|
-
const directValue = getWorkbenchStructuredDataValue(
|
|
212
|
+
const directValue = getWorkbenchStructuredDataValue(
|
|
213
|
+
data,
|
|
214
|
+
getWorkbenchStructuredDataSchemaPathSegments(sectionKey),
|
|
215
|
+
);
|
|
214
216
|
if (directValue !== null && directValue !== undefined) return directValue;
|
|
215
217
|
|
|
216
|
-
|
|
218
|
+
const sectionAliases = Object.prototype.hasOwnProperty.call(aliases, sectionKey)
|
|
219
|
+
? (aliases[sectionKey] ?? [])
|
|
220
|
+
: [];
|
|
221
|
+
for (const path of sectionAliases) {
|
|
217
222
|
if (!path.length) return data;
|
|
218
223
|
const value = getWorkbenchStructuredDataValue(data, path);
|
|
219
224
|
if (value !== null && value !== undefined) return value;
|
|
@@ -279,13 +284,17 @@ export function createWorkbenchStructuredDataSchemaDocumentSampleData(
|
|
|
279
284
|
? section.columns
|
|
280
285
|
: Object.keys(schemaTable?.items ?? {});
|
|
281
286
|
|
|
282
|
-
return setWorkbenchStructuredDataValue(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
return setWorkbenchStructuredDataValue(
|
|
288
|
+
sample,
|
|
289
|
+
getWorkbenchStructuredDataSchemaPathSegments(path),
|
|
290
|
+
[
|
|
291
|
+
createWorkbenchStructuredDataSchemaDocumentEmptyRow({
|
|
292
|
+
columns,
|
|
293
|
+
schema,
|
|
294
|
+
section,
|
|
295
|
+
}),
|
|
296
|
+
],
|
|
297
|
+
);
|
|
289
298
|
}
|
|
290
299
|
|
|
291
300
|
return (section.fields ?? []).reduce<WorkbenchStructuredDataRecord>((nextSample, fieldPath) => {
|
|
@@ -296,7 +305,9 @@ export function createWorkbenchStructuredDataSchemaDocumentSampleData(
|
|
|
296
305
|
);
|
|
297
306
|
return setWorkbenchStructuredDataValue(
|
|
298
307
|
nextSample,
|
|
299
|
-
|
|
308
|
+
getWorkbenchStructuredDataSchemaPathSegments(
|
|
309
|
+
getWorkbenchStructuredDataSchemaFieldDataPath(section, fieldPath),
|
|
310
|
+
),
|
|
300
311
|
getWorkbenchStructuredDataSchemaFieldDefaultValue(definition),
|
|
301
312
|
);
|
|
302
313
|
}, sample);
|
|
@@ -21,6 +21,10 @@ export function getWorkbenchStructuredDataSchemaSectionPath(
|
|
|
21
21
|
return section.sectionKey || section.id || '';
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export function getWorkbenchStructuredDataSchemaPathSegments(path: string) {
|
|
25
|
+
return path ? path.split('.') : [];
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
export function getWorkbenchStructuredDataSchemaFieldDataPath(
|
|
25
29
|
section: WorkbenchStructuredDataSchemaSectionSummary,
|
|
26
30
|
fieldPath: string,
|
|
@@ -7,7 +7,10 @@ import type {
|
|
|
7
7
|
WorkbenchStructuredDataSchemaTableRowKeyInput,
|
|
8
8
|
} from './structuredDataSchemaTypes';
|
|
9
9
|
import { getWorkbenchStructuredDataSchemaFieldDefaultValue } from './structuredDataSchemaField';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getWorkbenchStructuredDataSchemaPathSegments,
|
|
12
|
+
getWorkbenchStructuredDataSchemaSectionPath,
|
|
13
|
+
} from './structuredDataSchemaSection';
|
|
11
14
|
|
|
12
15
|
export function getWorkbenchStructuredDataSchemaTableRows(
|
|
13
16
|
value: unknown,
|
|
@@ -34,7 +37,7 @@ export function getWorkbenchStructuredDataSchemaTablePath(
|
|
|
34
37
|
section: WorkbenchStructuredDataSchemaSectionSummary,
|
|
35
38
|
) {
|
|
36
39
|
const tablePath = getWorkbenchStructuredDataSchemaSectionPath(section);
|
|
37
|
-
return tablePath
|
|
40
|
+
return getWorkbenchStructuredDataSchemaPathSegments(tablePath);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export function getWorkbenchStructuredDataSchemaTableRowKey({
|
|
@@ -73,6 +73,7 @@ export {
|
|
|
73
73
|
} from '../editor/editorTabContextMenu';
|
|
74
74
|
export type {
|
|
75
75
|
CreateWorkbenchStandaloneEditorTabCommandContextInput,
|
|
76
|
+
CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
|
|
76
77
|
WorkbenchStandaloneEditorTabLike,
|
|
77
78
|
} from '../editor/editorTabContextMenu';
|
|
78
79
|
export { useWorkbenchEditorTabContextMenu } from '../editor/useWorkbenchEditorTabContextMenu';
|