Skip to content

Commit e6f3f16

Browse files
committed
SubmenuEntryActionViewItem
1 parent 6d2a2f0 commit e6f3f16

6 files changed

Lines changed: 79 additions & 32 deletions

File tree

src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Emitter } from 'vs/base/common/event';
1313
import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
1414
import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown';
1515
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
16+
import { asArray } from 'vs/base/common/arrays';
1617

1718
export interface IKeybindingProvider {
1819
(action: IAction): ResolvedKeybinding | undefined;
@@ -26,7 +27,7 @@ export interface IDropdownMenuActionViewItemOptions extends IBaseActionViewItemO
2627
readonly actionViewItemProvider?: IActionViewItemProvider;
2728
readonly keybindingProvider?: IKeybindingProvider;
2829
readonly actionRunner?: IActionRunner;
29-
readonly clazz?: string;
30+
readonly classNames?: string[] | string;
3031
readonly anchorAlignmentProvider?: IAnchorAlignmentProvider;
3132
readonly menuAsChild?: boolean;
3233
}
@@ -57,11 +58,17 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
5758

5859
render(container: HTMLElement): void {
5960
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
60-
this.element = append(el, $('a.action-label.codicon')); // todo@aeschli: remove codicon, should come through `this.options.clazz`
61-
if (this.options.clazz) {
62-
addClasses(this.element, this.options.clazz);
61+
this.element = append(el, $('a.action-label'));
62+
63+
const classNames = this.options.classNames ? asArray(this.options.classNames) : [];
64+
65+
// todo@aeschli: remove codicon, should come through `this.options.classNames`
66+
if (!classNames.find(c => c === 'icon')) {
67+
classNames.push('codicon');
6368
}
6469

70+
addClasses(this.element, ...classNames);
71+
6572
this.element.tabIndex = 0;
6673
this.element.setAttribute('role', 'button');
6774
this.element.setAttribute('aria-haspopup', 'true');

src/vs/base/browser/ui/toolbar/toolbar.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,49 +61,57 @@ export class ToolBar extends Disposable {
6161
ariaLabel: options.ariaLabel,
6262
actionRunner: options.actionRunner,
6363
actionViewItemProvider: (action: IAction) => {
64-
if (action instanceof SubmenuAction) {
65-
const actions = Array.isArray(action.actions) ? action.actions : action.actions();
66-
const result = new DropdownMenuActionViewItem(
64+
if (action.id === ToggleMenuAction.ID) {
65+
this.toggleMenuActionViewItem = new DropdownMenuActionViewItem(
6766
action,
68-
actions,
67+
(<ToggleMenuAction>action).menuActions,
6968
contextMenuProvider,
7069
{
7170
actionViewItemProvider: this.options.actionViewItemProvider,
7271
actionRunner: this.actionRunner,
7372
keybindingProvider: this.options.getKeyBinding,
74-
clazz: action.class,
73+
classNames: toolBarMoreIcon.classNames,
7574
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
7675
menuAsChild: true
7776
}
7877
);
79-
result.setActionContext(this.actionBar.context);
80-
this.submenuActionViewItems.push(result);
81-
this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility));
78+
this.toggleMenuActionViewItem.setActionContext(this.actionBar.context);
79+
this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility));
8280

83-
return result;
81+
return this.toggleMenuActionViewItem;
8482
}
8583

86-
if (action.id === ToggleMenuAction.ID) {
87-
this.toggleMenuActionViewItem = new DropdownMenuActionViewItem(
84+
if (options.actionViewItemProvider) {
85+
const result = options.actionViewItemProvider(action);
86+
87+
if (result) {
88+
return result;
89+
}
90+
}
91+
92+
if (action instanceof SubmenuAction) {
93+
const actions = Array.isArray(action.actions) ? action.actions : action.actions();
94+
const result = new DropdownMenuActionViewItem(
8895
action,
89-
(<ToggleMenuAction>action).menuActions,
96+
actions,
9097
contextMenuProvider,
9198
{
9299
actionViewItemProvider: this.options.actionViewItemProvider,
93100
actionRunner: this.actionRunner,
94101
keybindingProvider: this.options.getKeyBinding,
95-
clazz: toolBarMoreIcon.classNames,
102+
classNames: action.class,
96103
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
97104
menuAsChild: true
98105
}
99106
);
100-
this.toggleMenuActionViewItem.setActionContext(this.actionBar.context);
101-
this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility));
107+
result.setActionContext(this.actionBar.context);
108+
this.submenuActionViewItems.push(result);
109+
this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility));
102110

103-
return this.toggleMenuActionViewItem;
111+
return result;
104112
}
105113

106-
return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined;
114+
return undefined;
107115
}
108116
}));
109117
}

src/vs/platform/actions/browser/menuEntryActionViewItem.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1717
import { INotificationService } from 'vs/platform/notification/common/notification';
1818
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
1919
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
20+
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
2021

2122
// The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136
2223
class AlternativeKeyEmitter extends Emitter<boolean> {
@@ -126,9 +127,9 @@ function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemActi
126127

127128
const ids = new IdGenerator('menu-item-action-item-icon-');
128129

129-
export class MenuEntryActionViewItem extends ActionViewItem {
130+
const ICON_PATH_TO_CSS_RULES = new Map<string /* path*/, string /* CSS rule */>();
130131

131-
static readonly ICON_PATH_TO_CSS_RULES: Map<string /* path*/, string /* CSS rule */> = new Map<string, string>();
132+
export class MenuEntryActionViewItem extends ActionViewItem {
132133

133134
private _wantsAltCommand: boolean = false;
134135
private readonly _itemClassDispose = this._register(new MutableDisposable());
@@ -227,7 +228,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
227228
}
228229
}
229230

230-
_updateItemClass(item: ICommandAction): void {
231+
private _updateItemClass(item: ICommandAction): void {
231232
this._itemClassDispose.value = undefined;
232233

233234
const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon;
@@ -252,13 +253,13 @@ export class MenuEntryActionViewItem extends ActionViewItem {
252253

253254
const iconPathMapKey = icon.dark.toString();
254255

255-
if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
256-
iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
256+
if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
257+
iconClass = ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
257258
} else {
258259
iconClass = ids.nextId();
259260
createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`);
260261
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`);
261-
MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
262+
ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
262263
}
263264

264265
if (this.label) {
@@ -274,3 +275,34 @@ export class MenuEntryActionViewItem extends ActionViewItem {
274275
}
275276
}
276277
}
278+
279+
export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
280+
281+
constructor(
282+
action: SubmenuItemAction,
283+
@INotificationService _notificationService: INotificationService,
284+
@IContextMenuService _contextMenuService: IContextMenuService
285+
) {
286+
const classNames: string[] = [];
287+
288+
if (action.item.icon) {
289+
if (ThemeIcon.isThemeIcon(action.item.icon)) {
290+
classNames.push(ThemeIcon.asClassName(action.item.icon)!);
291+
} else if (action.item.icon.dark?.scheme) {
292+
const iconPathMapKey = action.item.icon.dark.toString();
293+
294+
if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
295+
classNames.push('icon', ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!);
296+
} else {
297+
const className = ids.nextId();
298+
classNames.push('icon', className);
299+
createCSSRule(`.icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.light || action.item.icon.dark)}`);
300+
createCSSRule(`.vs-dark .icon.${className}, .hc-black .icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.dark)}`);
301+
ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, className);
302+
}
303+
}
304+
}
305+
306+
super(action, Array.isArray(action.actions) ? action.actions : action.actions(), _contextMenuService, { classNames });
307+
}
308+
}

src/vs/workbench/browser/parts/notifications/notificationsViewer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export class NotificationRenderer implements IListRenderer<INotificationViewItem
211211
ariaLabel: localize('notificationActions', "Notification Actions"),
212212
actionViewItemProvider: action => {
213213
if (action && action instanceof ConfigureNotificationAction) {
214-
const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, { actionRunner: this.actionRunner, clazz: action.class });
214+
const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, { actionRunner: this.actionRunner, classNames: action.class });
215215
data.toDispose.add(item);
216216

217217
return item;

src/vs/workbench/contrib/comments/browser/commentNode.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export class CommentNode extends Disposable {
158158
{
159159
actionViewItemProvider: action => this.actionViewItemProvider(action as Action),
160160
actionRunner: this.actionRunner,
161-
clazz: 'toolbar-toggle-pickReactions codicon codicon-reactions',
161+
classNames: ['toolbar-toggle-pickReactions', 'codicon', 'codicon-reactions'],
162162
anchorAlignmentProvider: () => AnchorAlignment.RIGHT
163163
}
164164
);
@@ -267,7 +267,7 @@ export class CommentNode extends Disposable {
267267
return this.actionViewItemProvider(action as Action);
268268
},
269269
actionRunner: this.actionRunner,
270-
clazz: 'toolbar-toggle-pickReactions',
270+
classNames: 'toolbar-toggle-pickReactions',
271271
anchorAlignmentProvider: () => AnchorAlignment.RIGHT
272272
}
273273
);
@@ -287,7 +287,7 @@ export class CommentNode extends Disposable {
287287
{
288288
actionViewItemProvider: action => this.actionViewItemProvider(action as Action),
289289
actionRunner: this.actionRunner,
290-
clazz: 'toolbar-toggle-pickReactions',
290+
classNames: 'toolbar-toggle-pickReactions',
291291
anchorAlignmentProvider: () => AnchorAlignment.RIGHT
292292
}
293293
);

src/vs/workbench/contrib/markers/browser/markersViewActions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem {
182182
contextMenuService,
183183
{
184184
actionRunner,
185-
clazz: action.class,
185+
classNames: action.class,
186186
anchorAlignmentProvider: () => AnchorAlignment.RIGHT
187187
}
188188
);

0 commit comments

Comments
 (0)