提交 690a8118 编写于 作者: B Benjamin Pasero 提交者: GitHub

Viewlet overflow handling (#16233)

上级 bf427e3d
......@@ -522,6 +522,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
this.editor.layout(new Dimension(editorSize.width, editorSize.height));
this.sidebar.layout(sidebarSize);
this.panel.layout(panelDimension);
this.activitybar.layout(activityBarSize);
// Propagate to Context View
this.contextViewService.layout();
......
......@@ -68,12 +68,11 @@ export class ViewletActivityAction extends ActivityAction {
private lastRun: number = 0;
constructor(
id: string,
private viewlet: ViewletDescriptor,
@IViewletService private viewletService: IViewletService,
@IPartService private partService: IPartService
) {
super(id, viewlet.name, viewlet.cssClass);
super(viewlet.id, viewlet.name, viewlet.cssClass);
}
public run(event): TPromise<any> {
......@@ -103,9 +102,94 @@ export class ViewletActivityAction extends ActivityAction {
}
}
export class ViewletOverflowActivityAction extends ActivityAction {
constructor(
private viewlets: ViewletDescriptor[],
private showMenu: () => void
) {
super('activitybar.additionalViewlets.action', nls.localize('additionalViewlets', "Additional Viewlets"), 'toggle-more');
}
public run(event): TPromise<any> {
this.showMenu();
return TPromise.as(true);
}
}
export class ViewletOverflowActivityActionItem extends BaseActionItem {
private $e: Builder;
private name: string;
private cssClass: string;
private actions: OpenViewletAction[];
constructor(
action: ActivityAction,
private viewlets: ViewletDescriptor[],
private getBadge: (viewlet: ViewletDescriptor) => IBadge,
@IInstantiationService private instantiationService: IInstantiationService,
@IViewletService private viewletService: IViewletService,
@IContextMenuService private contextMenuService: IContextMenuService,
) {
super(null, action);
this.cssClass = action.class;
this.name = action.label;
this.actions = viewlets.map(viewlet => this.instantiationService.createInstance(OpenViewletAction, viewlet));
}
public render(container: HTMLElement): void {
super.render(container);
this.$e = $('a.action-label').attr({
tabIndex: '0',
role: 'button',
title: this.name,
class: this.cssClass
}).appendTo(this.builder);
}
public showMenu(): void {
this.updateActions();
this.contextMenuService.showContextMenu({
getAnchor: () => this.builder.getHTMLElement(),
getActions: () => TPromise.as(this.actions)
});
}
private updateActions(): void {
const activeViewlet = this.viewletService.getActiveViewlet();
this.actions.forEach(action => {
action.checked = activeViewlet && activeViewlet.getId() === action.id;
const badge = this.getBadge(action.viewlet);
let suffix: string | number;
if (badge instanceof NumberBadge) {
suffix = badge.number;
} else if (badge instanceof TextBadge) {
suffix = badge.text;
}
if (suffix) {
action.label = nls.localize('numberBadge', "{0} ({1})", action.viewlet.name, suffix);
} else {
action.label = action.viewlet.name;
}
});
}
public dispose(): void {
super.dispose();
this.actions = dispose(this.actions);
}
}
let manageExtensionAction: ManageExtensionAction;
export class ActivityActionItem extends BaseActionItem {
private $e: Builder;
private name: string;
private _keybinding: string;
......@@ -289,10 +373,35 @@ class ManageExtensionAction extends Action {
constructor(
@ICommandService private commandService: ICommandService
) {
super('statusbar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
}
public run(extensionId: string): TPromise<any> {
return this.commandService.executeCommand('_extensions.manage', extensionId);
}
}
class OpenViewletAction extends Action {
constructor(
public viewlet: ViewletDescriptor,
@IPartService private partService: IPartService,
@IViewletService private viewletService: IViewletService
) {
super(viewlet.id, viewlet.name);
}
public run(): TPromise<any> {
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
const activeViewlet = this.viewletService.getActiveViewlet();
// Hide sidebar if selected viewlet already visible
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
this.partService.setSideBarHidden(true);
} else {
this.viewletService.openViewlet(this.viewlet.id, true).done(null, errors.onUnexpectedError);
}
return TPromise.as(true);
}
}
\ No newline at end of file
......@@ -7,13 +7,13 @@
import 'vs/css!./media/activitybarpart';
import nls = require('vs/nls');
import { Builder, $ } from 'vs/base/browser/builder';
import { Builder, $, Dimension } from 'vs/base/browser/builder';
import { Action } from 'vs/base/common/actions';
import { ActionsOrientation, ActionBar, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IComposite } from 'vs/workbench/common/composite';
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { Part } from 'vs/workbench/browser/part';
import { ViewletActivityAction, ActivityAction, ActivityActionItem } from 'vs/workbench/browser/parts/activitybar/activityAction';
import { ViewletActivityAction, ActivityAction, ActivityActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem } from 'vs/workbench/browser/parts/activitybar/activityAction';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IActivityService, IBadge } from 'vs/workbench/services/activity/common/activityService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
......@@ -21,12 +21,26 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
interface IViewletActivity {
badge: IBadge;
clazz: string;
}
export class ActivitybarPart extends Part implements IActivityService {
private static ACTIVITY_ACTION_HEIGHT = 50;
public _serviceBrand: any;
private dimension: Dimension;
private viewletSwitcherBar: ActionBar;
private viewletOverflowAction: ViewletOverflowActivityAction;
private viewletOverflowActionItem: ViewletOverflowActivityActionItem;
private activityActionItems: { [actionId: string]: IActionItem; };
private viewletIdToActions: { [viewletId: string]: ActivityAction; };
private viewletIdToActivity: { [viewletId: string]: IViewletActivity; };
constructor(
id: string,
......@@ -38,8 +52,9 @@ export class ActivitybarPart extends Part implements IActivityService {
) {
super(id);
this.activityActionItems = {};
this.viewletIdToActions = {};
this.activityActionItems = Object.create(null);
this.viewletIdToActions = Object.create(null);
this.viewletIdToActivity = Object.create(null);
// Update viewlet switcher when external viewlets become ready
this.extensionService.onReady().then(() => this.updateViewletSwitcher());
......@@ -68,7 +83,9 @@ export class ActivitybarPart extends Part implements IActivityService {
}
}
public showActivity(viewletId: string, badge: IBadge, clazz?: string): void {
public showActivity(viewletId: string, badge?: IBadge, clazz?: string): void {
// Update Action with activity
const action = this.viewletIdToActions[viewletId];
if (action) {
action.setBadge(badge);
......@@ -76,6 +93,13 @@ export class ActivitybarPart extends Part implements IActivityService {
action.class = clazz;
}
}
// Keep for future use
if (badge) {
this.viewletIdToActivity[viewletId] = { badge, clazz };
} else {
delete this.viewletIdToActivity[viewletId];
}
}
public clearActivity(viewletId: string): void {
......@@ -94,46 +118,97 @@ export class ActivitybarPart extends Part implements IActivityService {
private createViewletSwitcher(div: Builder): void {
this.viewletSwitcherBar = new ActionBar(div, {
actionItemProvider: (action: Action) => this.activityActionItems[action.id],
actionItemProvider: (action: Action) => action instanceof ViewletOverflowActivityAction ? this.viewletOverflowActionItem : this.activityActionItems[action.id],
orientation: ActionsOrientation.VERTICAL,
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher")
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
animated: false
});
this.updateViewletSwitcher();
}
private updateViewletSwitcher() {
const viewlets = this.viewletService.getViewlets();
let viewlets = this.viewletService.getViewlets();
let viewletsToShow = viewlets;
// Ensure we are not showing more viewlets than we have height for
let overflows = false;
if (this.dimension) {
const maxVisible = Math.floor(this.dimension.height / ActivitybarPart.ACTIVITY_ACTION_HEIGHT);
overflows = viewlets.length > maxVisible;
if (overflows) {
viewletsToShow = viewlets.slice(0, maxVisible - 1 /* make room for overflow action */);
}
}
const visibleViewlets = Object.keys(this.viewletIdToActions);
const visibleViewletsChange = (viewletsToShow.length !== visibleViewlets.length);
// Pull out overflow action if there is a viewlet change so that we can add it to the end later
if (this.viewletOverflowAction && visibleViewletsChange) {
this.viewletSwitcherBar.pull(this.viewletSwitcherBar.length() - 1);
this.viewletOverflowAction.dispose();
this.viewletOverflowAction = null;
this.viewletOverflowActionItem.dispose();
this.viewletOverflowActionItem = null;
}
// Pull out viewlets no longer needed
const newViewletIds = viewlets.map(v => v.id);
const existingViewletIds = Object.keys(this.viewletIdToActions);
existingViewletIds.forEach(viewletId => {
if (newViewletIds.indexOf(viewletId) === -1) {
// Pull out viewlets that overflow
const viewletIdsToShow = viewletsToShow.map(v => v.id);
visibleViewlets.forEach(viewletId => {
if (viewletIdsToShow.indexOf(viewletId) === -1) {
this.pullViewlet(viewletId);
}
});
// Built actions for viewlets to show
const actionsToPush = viewlets
const newViewletsToShow = viewletsToShow
.filter(viewlet => !this.viewletIdToActions[viewlet.id])
.map(viewlet => this.toAction(viewlet));
// Add to viewlet switcher
this.viewletSwitcherBar.push(actionsToPush, { label: true, icon: true });
// Update when we have new viewlets to show
if (newViewletsToShow.length) {
// Make sure to activate the active one
const activeViewlet = this.viewletService.getActiveViewlet();
if (activeViewlet) {
const activeViewletEntry = this.viewletIdToActions[activeViewlet.getId()];
if (activeViewletEntry) {
activeViewletEntry.activate();
// Add to viewlet switcher
this.viewletSwitcherBar.push(newViewletsToShow, { label: true, icon: true });
// Make sure to activate the active one
const activeViewlet = this.viewletService.getActiveViewlet();
if (activeViewlet) {
const activeViewletEntry = this.viewletIdToActions[activeViewlet.getId()];
if (activeViewletEntry) {
activeViewletEntry.activate();
}
}
// Make sure to restore activity
Object.keys(this.viewletIdToActions).forEach(viewletId => {
const activity = this.viewletIdToActivity[viewletId];
if (activity) {
this.showActivity(viewletId, activity.badge, activity.clazz);
} else {
this.showActivity(viewletId);
}
});
}
// Add overflow action as needed
if (visibleViewletsChange && overflows) {
const viewletsOverflowing = viewlets.slice(viewletsToShow.length);
this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, viewletsOverflowing, () => this.viewletOverflowActionItem.showMenu());
this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, viewletsOverflowing, viewlet => this.viewletIdToActivity[viewlet.id] && this.viewletIdToActivity[viewlet.id].badge);
this.viewletSwitcherBar.push(this.viewletOverflowAction, { label: true, icon: true });
}
}
private pullViewlet(viewletId: string): void {
const index = Object.keys(this.viewletIdToActions).indexOf(viewletId);
this.viewletSwitcherBar.pull(index);
const action = this.viewletIdToActions[viewletId];
action.dispose();
......@@ -142,18 +217,32 @@ export class ActivitybarPart extends Part implements IActivityService {
const actionItem = this.activityActionItems[action.id];
actionItem.dispose();
delete this.activityActionItems[action.id];
this.viewletSwitcherBar.pull(index);
}
private toAction(viewlet: ViewletDescriptor): ActivityAction {
const action = this.instantiationService.createInstance(ViewletActivityAction, `${viewlet.id}.activity-bar-action`, viewlet);
const action = this.instantiationService.createInstance(ViewletActivityAction, viewlet);
this.activityActionItems[action.id] = this.instantiationService.createInstance(ActivityActionItem, action, viewlet);
this.viewletIdToActions[viewlet.id] = action;
return action;
};
}
/**
* Layout title, content and status area in the given dimension.
*/
public layout(dimension: Dimension): Dimension[] {
// Pass to super
const sizes = super.layout(dimension);
this.dimension = sizes[1];
// Update switcher to handle overflow issues
this.updateViewletSwitcher();
return sizes;
}
public dispose(): void {
if (this.viewletSwitcherBar) {
......
......@@ -34,6 +34,10 @@
border-left: 2px solid;
}
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label.toggle-more {
background-image: url('ellipsis-global.svg');
}
.vs .monaco-workbench > .activitybar > .content .monaco-action-bar .action-item .action-label:focus:before,
.vs-dark .monaco-workbench > .activitybar > .content .monaco-action-bar .action-item .action-label:focus:before {
border-left-color: #007ACC;
......
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><g fill="#fff"><circle cx="6" cy="15" r="3"/><circle cx="16" cy="15" r="3"/><circle cx="26" cy="15" r="3"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0;}.cls-1{fill:#fff;}</style></defs><title>Ellipsis_32x</title><g id="canvas"><path class="icon-canvas-transparent" d="M32,32H0V0H32Z" transform="translate(0 0)"/></g><g id="iconBg"><path class="cls-1" d="M9,16a3,3,0,1,1-3-3A3,3,0,0,1,9,16Zm7-3a3,3,0,1,0,3,3A3,3,0,0,0,16,13Zm10,0a3,3,0,1,0,3,3A3,3,0,0,0,26,13Z" transform="translate(0 0)"/></g></svg>
\ No newline at end of file
......@@ -82,6 +82,7 @@ export class ContextMenuService implements IContextMenuService {
const item = new remote.MenuItem({
label: e.label,
checked: !!e.checked,
type: !!e.checked ? 'radio' : void 0,
accelerator,
enabled: !!e.enabled,
click: (menuItem, win, event) => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册