Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/actions/browser/buttonbar.ts
5243 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { ButtonBar, IButton } from '../../../base/browser/ui/button/button.js';
7
import { createInstantHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegateFactory.js';
8
import { ActionRunner, IAction, IActionRunner, SubmenuAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../base/common/actions.js';
9
import { Codicon } from '../../../base/common/codicons.js';
10
import { Emitter, Event } from '../../../base/common/event.js';
11
import { DisposableStore } from '../../../base/common/lifecycle.js';
12
import { ThemeIcon } from '../../../base/common/themables.js';
13
import { localize } from '../../../nls.js';
14
import { getActionBarActions } from './menuEntryActionViewItem.js';
15
import { IToolBarRenderOptions } from './toolbar.js';
16
import { MenuId, IMenuService, MenuItemAction, IMenuActionOptions } from '../common/actions.js';
17
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
18
import { IContextMenuService } from '../../contextview/browser/contextView.js';
19
import { IHoverService } from '../../hover/browser/hover.js';
20
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
21
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
22
23
export type IButtonConfigProvider = (action: IAction, index: number) => {
24
showIcon?: boolean;
25
showLabel?: boolean;
26
isSecondary?: boolean;
27
} | undefined;
28
29
export interface IWorkbenchButtonBarOptions {
30
telemetrySource?: string;
31
buttonConfigProvider?: IButtonConfigProvider;
32
small?: boolean;
33
disableWhileRunning?: boolean;
34
}
35
36
export class WorkbenchButtonBar extends ButtonBar {
37
38
protected readonly _store = new DisposableStore();
39
protected readonly _updateStore = new DisposableStore();
40
41
private readonly _actionRunner: IActionRunner;
42
private readonly _onDidChange = new Emitter<this>();
43
readonly onDidChange: Event<this> = this._onDidChange.event;
44
45
46
constructor(
47
container: HTMLElement,
48
private readonly _options: IWorkbenchButtonBarOptions | undefined,
49
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
50
@IKeybindingService private readonly _keybindingService: IKeybindingService,
51
@ITelemetryService telemetryService: ITelemetryService,
52
@IHoverService private readonly _hoverService: IHoverService,
53
) {
54
super(container);
55
56
this._actionRunner = this._store.add(new ActionRunner());
57
if (_options?.telemetrySource) {
58
this._actionRunner.onDidRun(e => {
59
telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>(
60
'workbenchActionExecuted',
61
{ id: e.action.id, from: _options.telemetrySource! }
62
);
63
}, undefined, this._store);
64
}
65
}
66
67
override dispose() {
68
this._onDidChange.dispose();
69
this._updateStore.dispose();
70
this._store.dispose();
71
super.dispose();
72
}
73
74
update(actions: IAction[], secondary: IAction[]): void {
75
76
const conifgProvider: IButtonConfigProvider = this._options?.buttonConfigProvider ?? (() => ({ showLabel: true }));
77
78
this._updateStore.clear();
79
this.clear();
80
81
// Support instamt hover between buttons
82
const hoverDelegate = this._updateStore.add(createInstantHoverDelegate());
83
84
for (let i = 0; i < actions.length; i++) {
85
86
const secondary = i > 0;
87
const actionOrSubmenu = actions[i];
88
let action: IAction;
89
let btn: IButton;
90
let tooltip = actionOrSubmenu.tooltip || actionOrSubmenu.label;
91
if (!(actionOrSubmenu instanceof SubmenuAction)) {
92
tooltip = this._keybindingService.appendKeybinding(tooltip, actionOrSubmenu.id);
93
}
94
if (actionOrSubmenu instanceof SubmenuAction && actionOrSubmenu.actions.length > 0) {
95
const [first, ...rest] = actionOrSubmenu.actions;
96
action = <MenuItemAction>first;
97
btn = this.addButtonWithDropdown({
98
secondary: conifgProvider(action, i)?.isSecondary ?? secondary,
99
actionRunner: this._actionRunner,
100
actions: rest,
101
contextMenuProvider: this._contextMenuService,
102
ariaLabel: tooltip,
103
supportIcons: true,
104
small: this._options?.small,
105
});
106
} else {
107
action = actionOrSubmenu;
108
btn = this.addButton({
109
secondary: conifgProvider(action, i)?.isSecondary ?? secondary,
110
ariaLabel: tooltip,
111
supportIcons: true,
112
small: this._options?.small,
113
});
114
}
115
116
btn.enabled = action.enabled;
117
btn.checked = action.checked ?? false;
118
btn.element.classList.add('default-colors');
119
const showLabel = conifgProvider(action, i)?.showLabel ?? true;
120
if (showLabel) {
121
btn.label = action.label;
122
} else {
123
btn.element.classList.add('monaco-text-button');
124
}
125
if (conifgProvider(action, i)?.showIcon) {
126
if (action instanceof MenuItemAction && ThemeIcon.isThemeIcon(action.item.icon)) {
127
if (!showLabel) {
128
btn.icon = action.item.icon;
129
} else {
130
// this is REALLY hacky but combining a codicon and normal text is ugly because
131
// the former define a font which doesn't work for text
132
btn.label = `$(${action.item.icon.id}) ${action.label}`;
133
}
134
} else if (action.class) {
135
btn.element.classList.add(...action.class.split(' '));
136
}
137
}
138
139
this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, tooltip));
140
this._updateStore.add(btn.onDidClick(async () => {
141
if (this._options?.disableWhileRunning) {
142
btn.enabled = false;
143
try {
144
await this._actionRunner.run(action);
145
} finally {
146
btn.enabled = action.enabled;
147
}
148
} else {
149
this._actionRunner.run(action);
150
}
151
}));
152
}
153
154
if (secondary.length > 0) {
155
156
const btn = this.addButton({
157
secondary: true,
158
ariaLabel: localize('moreActions', "More Actions"),
159
small: this._options?.small,
160
});
161
162
btn.icon = Codicon.dropDownButton;
163
btn.element.classList.add('default-colors', 'monaco-text-button');
164
165
btn.enabled = true;
166
this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, localize('moreActions', "More Actions")));
167
this._updateStore.add(btn.onDidClick(async () => {
168
this._contextMenuService.showContextMenu({
169
getAnchor: () => btn.element,
170
getActions: () => secondary,
171
actionRunner: this._actionRunner,
172
onHide: () => btn.element.setAttribute('aria-expanded', 'false')
173
});
174
btn.element.setAttribute('aria-expanded', 'true');
175
176
}));
177
}
178
this._onDidChange.fire(this);
179
}
180
}
181
182
export interface IMenuWorkbenchButtonBarOptions extends IWorkbenchButtonBarOptions {
183
menuOptions?: IMenuActionOptions;
184
185
toolbarOptions?: IToolBarRenderOptions;
186
}
187
188
export class MenuWorkbenchButtonBar extends WorkbenchButtonBar {
189
190
constructor(
191
container: HTMLElement,
192
menuId: MenuId,
193
options: IMenuWorkbenchButtonBarOptions | undefined,
194
@IMenuService menuService: IMenuService,
195
@IContextKeyService contextKeyService: IContextKeyService,
196
@IContextMenuService contextMenuService: IContextMenuService,
197
@IKeybindingService keybindingService: IKeybindingService,
198
@ITelemetryService telemetryService: ITelemetryService,
199
@IHoverService hoverService: IHoverService,
200
) {
201
super(container, options, contextMenuService, keybindingService, telemetryService, hoverService);
202
203
const menu = menuService.createMenu(menuId, contextKeyService);
204
this._store.add(menu);
205
206
const update = () => {
207
208
this.clear();
209
210
const actions = getActionBarActions(
211
menu.getActions(options?.menuOptions),
212
options?.toolbarOptions?.primaryGroup
213
);
214
215
super.update(actions.primary, actions.secondary);
216
};
217
this._store.add(menu.onDidChange(update));
218
update();
219
}
220
221
override dispose() {
222
super.dispose();
223
}
224
225
override update(_actions: IAction[]): void {
226
throw new Error('Use Menu or WorkbenchButtonBar');
227
}
228
}
229
230