提交 c786c088 编写于 作者: S Sandeep Somavarapu

Refactor composite bar

- Create a composite model
- Show the context menu in the same order
- Retain the position after unpin
上级 6662b08d
......@@ -182,6 +182,11 @@ export class ActivitybarPart extends Part {
const canShow = this.canShow(viewlet);
if (canShow) {
this.compositeBar.addComposite(viewlet, false);
const activeViewlet = this.viewletService.getActiveViewlet();
if (activeViewlet && activeViewlet.getId() === viewlet.id) {
this.compositeBar.pin(viewlet.id);
this.compositeBar.activateComposite(viewlet.id);
}
} else {
this.compositeBar.removeComposite(viewlet.id);
}
......
......@@ -9,11 +9,11 @@ import * as nls from 'vs/nls';
import { Action, IAction } from 'vs/base/common/actions';
import { illegalArgument } from 'vs/base/common/errors';
import * as arrays from 'vs/base/common/arrays';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ActionBar, IActionItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
import { TPromise } from 'vs/base/common/winjs.base';
import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
......@@ -37,11 +37,6 @@ export interface ICompositeBarOptions {
hidePart: () => TPromise<any>;
}
interface CompositeState {
id: string;
pinned: boolean;
}
export class CompositeBar extends Widget implements ICompositeBar {
private dimension: Dimension;
......@@ -50,16 +45,11 @@ export class CompositeBar extends Widget implements ICompositeBar {
private compositeOverflowAction: CompositeOverflowActivityAction;
private compositeOverflowActionItem: CompositeOverflowActivityActionItem;
private compositeIdToActions: { [compositeId: string]: ActivityAction; };
private compositeIdToActionItems: { [compositeId: string]: IActionItem; };
private compositeIdToActivityStack: { [compositeId: string]: ICompositeActivity[]; };
private model: CompositeBarModel;
private storedState: ISerializedCompositeBarItem[];
private visibleComposites: string[];
private compositeSizeInBar: Map<string, number>;
private initialCompositesStates: CompositeState[];
private pinnedComposites: string[];
private activeCompositeId: string;
private activeUnpinnedCompositeId: string;
constructor(
private options: ICompositeBarOptions,
@IInstantiationService private instantiationService: IInstantiationService,
......@@ -67,77 +57,120 @@ export class CompositeBar extends Widget implements ICompositeBar {
@IContextMenuService private contextMenuService: IContextMenuService
) {
super();
this.compositeIdToActionItems = Object.create(null);
this.compositeIdToActions = Object.create(null);
this.compositeIdToActivityStack = Object.create(null);
this.model = new CompositeBarModel(options);
this.storedState = this.loadCompositeItemsFromStorage();
this.visibleComposites = [];
this.compositeSizeInBar = new Map<string, number>();
this.initialCompositesStates = this.loadCompositesStates();
this.pinnedComposites = this.initialCompositesStates
.filter(c => c.pinned)
.map(c => c.id)
.filter(id => this.options.composites.some(c => c.id === id));
let index = 0;
for (const state of this.storedState) {
const composite = this.options.composites.filter(c => c.id === state.id)[0];
if (composite) {
this.model.add(composite.id, composite.name, composite.order, state.pinned, index++);
}
}
}
public create(parent: HTMLElement): HTMLElement {
const actionBarDiv = parent.appendChild($('.composite-bar'));
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
actionItemProvider: (action: Action) => {
if (action instanceof CompositeOverflowActivityAction) {
return this.compositeOverflowActionItem;
}
const item = this.model.findItem(action.id);
return item && this.instantiationService.createInstance(CompositeActionItem, action, item.pinnedAction, this.options.colors, this.options.icon, this);
},
orientation: this.options.orientation,
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
animated: false,
}));
// Contextmenu for composites
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
// Allow to drop at the end to move composites to the end
this._register(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
if (draggedCompositeId) {
EventHelper.stop(e, true);
CompositeActionItem.clearDraggedComposite();
const targetItem = this.model.items[this.model.items.length - 1];
if (targetItem && targetItem.id !== draggedCompositeId) {
this.move(draggedCompositeId, targetItem.id);
}
}
}));
return actionBarDiv;
}
public addComposite(compositeData: { id: string; name: string, order: number }, activate: boolean): void {
if (this.options.composites.filter(c => c.id === compositeData.id).length) {
public layout(dimension: Dimension): void {
this.dimension = dimension;
if (dimension.height === 0 || dimension.width === 0) {
// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
return;
}
this.options.composites.push(compositeData);
const compositeState = this.initialCompositesStates.filter(c => c.id === compositeData.id)[0];
if (!compositeState /* new composites are pinned by default */ || compositeState.pinned) {
let index;
if (compositeState) {
index = this.initialCompositesStates.indexOf(compositeState);
if (this.compositeSizeInBar.size === 0) {
// Compute size of each composite by getting the size from the css renderer
// Size is later used for overflow computation
this.compositeSwitcherBar.clear();
this.compositeSwitcherBar.push(this.model.items.map(item => item.activityAction));
this.model.items.map((c, index) => this.compositeSizeInBar.set(c.id, this.options.orientation === ActionsOrientation.VERTICAL
? this.compositeSwitcherBar.getHeight(index)
: this.compositeSwitcherBar.getWidth(index)
));
this.compositeSwitcherBar.clear();
}
this.updateCompositeSwitcher();
}
public addComposite({ id, name, order }: { id: string; name: string, order: number }, open: boolean): void {
const state = this.storedState.filter(s => s.id === id)[0];
const pinned = state ? state.pinned : true;
const index = state ? this.storedState.indexOf(state) : void 0;
// Add to the model
if (this.model.add(id, name, order, pinned, index)) {
if (open) {
this.pin(id, true);
} else {
index = 0;
while (index < this.options.composites.length && this.options.composites[index].order < compositeData.order) {
index++;
}
this.updateCompositeSwitcher();
}
this.pin(compositeData.id, true, index, activate);
}
}
public removeComposite(id: string): void {
if (this.options.composites.filter(c => c.id === id).length === 0) {
return;
// If it pinned, unpin it first
if (this.isPinned(id)) {
this.unpin(id);
}
this.options.composites = this.options.composites.filter(c => c.id !== id);
this.unpin(id);
this.pullComposite(id);
// Only at the end deactivate composite so the unpin and pull properly finish
this.deactivateComposite(id);
// Remove from the model
if (this.model.remove(id)) {
this.updateCompositeSwitcher();
}
}
public activateComposite(id: string): void {
if (this.compositeIdToActions[id]) {
if (this.compositeIdToActions[this.activeCompositeId]) {
this.compositeIdToActions[this.activeCompositeId].deactivate();
const previousActiveItem = this.model.activeItem;
if (this.model.activate(id)) {
// Update if either current or previous active items are not pinned
if (!this.model.activeItem.pinned || (previousActiveItem && !previousActiveItem.pinned)) {
this.updateCompositeSwitcher();
}
this.compositeIdToActions[id].activate();
}
this.activeCompositeId = id;
const activeUnpinnedCompositeShouldClose = this.activeUnpinnedCompositeId && this.activeUnpinnedCompositeId !== id;
const activeUnpinnedCompositeShouldShow = !this.pinnedComposites.some(pid => pid === id);
if (activeUnpinnedCompositeShouldShow || activeUnpinnedCompositeShouldClose) {
this.updateCompositeSwitcher();
}
}
public deactivateComposite(id: string): void {
if (this.compositeIdToActions[id]) {
this.compositeIdToActions[id].deactivate();
}
if (this.activeCompositeId === id) {
this.activeCompositeId = undefined;
}
if (this.activeUnpinnedCompositeId === id) {
this.updateCompositeSwitcher();
this.activeUnpinnedCompositeId = undefined;
const previousActiveItem = this.model.activeItem;
if (this.model.deactivate()) {
if (previousActiveItem && !previousActiveItem.pinned) {
this.updateCompositeSwitcher();
}
}
}
......@@ -151,128 +184,83 @@ export class CompositeBar extends Widget implements ICompositeBar {
}
const activity: ICompositeActivity = { badge, clazz, priority };
const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []);
for (let i = 0; i <= stack.length; i++) {
if (i === stack.length) {
stack.push(activity);
break;
} else if (stack[i].priority <= priority) {
stack.splice(i, 0, activity);
break;
}
}
this.updateActivity(compositeId);
return {
dispose: () => {
const stack = this.compositeIdToActivityStack[compositeId];
if (!stack) {
return;
}
const idx = stack.indexOf(activity);
if (idx < 0) {
return;
}
this.model.addActivity(compositeId, activity);
return toDisposable(() => this.model.removeActivity(compositeId, activity));
}
stack.splice(idx, 1);
if (stack.length === 0) {
delete this.compositeIdToActivityStack[compositeId];
}
public pin(compositeId: string, open?: boolean): void {
if (this.model.setPinned(compositeId, true)) {
this.updateCompositeSwitcher();
// Persist
this.saveCompositeItems();
this.updateActivity(compositeId);
if (open) {
this.options.openComposite(compositeId)
.done(() => this.activateComposite(compositeId)); // Activate after opening
}
};
}
}
private updateActivity(compositeId: string) {
const action = this.compositeIdToActions[compositeId];
if (!action) {
return;
}
public unpin(compositeId: string): void {
if (this.model.setPinned(compositeId, false)) {
const stack = this.compositeIdToActivityStack[compositeId];
this.updateCompositeSwitcher();
// Persist
this.saveCompositeItems();
// reset
if (!stack || !stack.length) {
action.setBadge(undefined);
}
const defaultCompositeId = this.options.getDefaultCompositeId();
// update
else {
const [{ badge, clazz }] = stack;
action.setBadge(badge);
if (clazz) {
action.class = clazz;
// Case: composite is not the active one or the active one is a different one
// Solv: we do nothing
if (!this.model.activeItem || this.model.activeItem.id !== compositeId) {
return;
}
}
}
public create(parent: HTMLElement): HTMLElement {
const actionBarDiv = parent.appendChild($('.composite-bar'));
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
actionItemProvider: (action: Action) => action instanceof CompositeOverflowActivityAction ? this.compositeOverflowActionItem : this.compositeIdToActionItems[action.id],
orientation: this.options.orientation,
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
animated: false,
}));
// Deactivate itself
this.deactivateComposite(compositeId);
// Contextmenu for composites
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
// Case: composite is not the default composite and default composite is still showing
// Solv: we open the default composite
if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
this.options.openComposite(defaultCompositeId);
}
// Allow to drop at the end to move composites to the end
this._register(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
if (draggedCompositeId) {
EventHelper.stop(e, true);
CompositeActionItem.clearDraggedComposite();
// Case: we closed the last visible composite
// Solv: we hide the part
else if (this.visibleComposites.length === 1) {
this.options.hidePart();
}
const targetId = this.pinnedComposites[this.pinnedComposites.length - 1];
if (targetId !== draggedCompositeId) {
this.move(draggedCompositeId, this.pinnedComposites[this.pinnedComposites.length - 1]);
}
// Case: we closed the default composite
// Solv: we open the next visible composite from top
else {
this.options.openComposite(this.visibleComposites.filter(cid => cid !== compositeId)[0]);
}
}));
return actionBarDiv;
}
}
public getAction(compositeId): ActivityAction {
return this.compositeIdToActions[compositeId];
public isPinned(compositeId: string): boolean {
const item = this.model.findItem(compositeId);
return item && item.pinned;
}
private showContextMenu(e: MouseEvent): void {
EventHelper.stop(e, true);
const event = new StandardMouseEvent(e);
this.options.composites.sort((c1, c2) => c1.order < c2.order ? -1 : 1);
const actions: IAction[] = this.options.composites
.map(({ id, name }) => (<IAction>{
id,
label: name,
checked: this.isPinned(id),
enabled: true,
run: () => this.togglePin(id)
}));
const otherActions = this.options.getContextMenuActions();
if (otherActions.length) {
actions.push(new Separator());
actions.push(...otherActions);
}
this.contextMenuService.showContextMenu({
getAnchor: () => { return { x: event.posx, y: event.posy }; },
getActions: () => TPromise.as(actions),
onHide: () => dispose(actions)
});
public move(compositeId: string, toCompositeId: string): void {
this.model.move(compositeId, toCompositeId);
// timeout helps to prevent artifacts from showing up
setTimeout(() => {
this.updateCompositeSwitcher();
// Persist
this.saveCompositeItems();
}, 0);
}
private togglePin(id: string): void {
if (this.isPinned(id)) {
this.unpin(id);
} else {
this.pin(id);
}
public getAction(compositeId): ActivityAction {
const item = this.model.findItem(compositeId);
return item && item.activityAction;
}
private updateCompositeSwitcher(): void {
......@@ -280,15 +268,10 @@ export class CompositeBar extends Widget implements ICompositeBar {
return; // We have not been rendered yet so there is nothing to update.
}
let compositesToShow = this.pinnedComposites.slice(0); // never modify original array
// Always show the active composite even if it is marked to be hidden
if (this.activeCompositeId && !compositesToShow.some(id => id === this.activeCompositeId)) {
this.activeUnpinnedCompositeId = this.activeCompositeId;
compositesToShow = compositesToShow.concat(this.activeUnpinnedCompositeId);
} else {
this.activeUnpinnedCompositeId = void 0;
}
let compositesToShow = this.model.items.filter(item =>
item.pinned
|| (this.model.activeItem && this.model.activeItem.id === item.id) /* Show the active composite even if it is not pinned */
).map(item => item.id);
// Ensure we are not showing more composites than we have height for
let overflows = false;
......@@ -312,19 +295,20 @@ export class CompositeBar extends Widget implements ICompositeBar {
if (size > limit) {
size -= this.compositeSizeInBar.get(compositesToShow.pop());
}
// We always try show the active composite
if (this.activeCompositeId && compositesToShow.length && compositesToShow.indexOf(this.activeCompositeId) === -1) {
if (this.model.activeItem && compositesToShow.every(compositeId => compositeId !== this.model.activeItem.id)) {
const removedComposite = compositesToShow.pop();
size = size - this.compositeSizeInBar.get(removedComposite) + this.compositeSizeInBar.get(this.activeCompositeId);
compositesToShow.push(this.activeCompositeId);
size = size - this.compositeSizeInBar.get(removedComposite) + this.compositeSizeInBar.get(this.model.activeItem.id);
compositesToShow.push(this.model.activeItem.id);
}
// The active composite might have bigger size than the removed composite, check for overflow again
if (size > limit) {
compositesToShow.length ? compositesToShow.splice(compositesToShow.length - 2, 1) : compositesToShow.pop();
}
const visibleComposites = Object.keys(this.compositeIdToActions);
const visibleCompositesChange = !arrays.equals(compositesToShow, visibleComposites);
const visibleCompositesChange = !arrays.equals(compositesToShow, this.visibleComposites);
// Pull out overflow action if there is a composite change so that we can add it to the end later
if (this.compositeOverflowAction && visibleCompositesChange) {
......@@ -337,37 +321,35 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.compositeOverflowActionItem = null;
}
// Pull out composites that overflow, got hidden or changed position
visibleComposites.forEach((compositeId, index) => {
if (compositesToShow.indexOf(compositeId) !== index) {
this.pullComposite(compositeId);
// Pull out composites that overflow or got hidden
const compositesToRemove: number[] = [];
this.visibleComposites.forEach((compositeId, index) => {
if (compositesToShow.indexOf(compositeId) === -1) {
compositesToRemove.push(index);
}
});
compositesToRemove.reverse().forEach(index => {
const actionItem = this.compositeSwitcherBar.items[index];
this.compositeSwitcherBar.pull(index);
actionItem.dispose();
this.visibleComposites.splice(index, 1);
});
// Built actions for composites to show
const newCompositesToShow = compositesToShow
.filter(compositeId => !this.compositeIdToActions[compositeId])
.map(compositeId => this.toAction(compositeId));
// Update when we have new composites to show
if (newCompositesToShow.length) {
// Add to composite switcher
this.compositeSwitcherBar.push(newCompositesToShow, { label: true, icon: this.options.icon });
// Make sure to activate the active one
if (this.activeCompositeId) {
const activeCompositeEntry = this.compositeIdToActions[this.activeCompositeId];
if (activeCompositeEntry) {
activeCompositeEntry.activate();
// Update the positions of the composites
compositesToShow.forEach((compositeId, newIndex) => {
const currentIndex = this.visibleComposites.indexOf(compositeId);
if (newIndex !== currentIndex) {
if (currentIndex !== -1) {
const actionItem = this.compositeSwitcherBar.items[currentIndex];
this.compositeSwitcherBar.pull(currentIndex);
actionItem.dispose();
this.visibleComposites.splice(currentIndex, 1);
}
}
// Make sure to restore activity
Object.keys(this.compositeIdToActions).forEach(compositeId => {
this.updateActivity(compositeId);
});
}
this.compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex });
this.visibleComposites.splice(newIndex, 0, compositeId);
}
});
// Add overflow action as needed
if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) {
......@@ -376,8 +358,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
CompositeOverflowActivityActionItem,
this.compositeOverflowAction,
() => this.getOverflowingComposites(),
() => this.activeCompositeId,
(compositeId: string) => this.compositeIdToActivityStack[compositeId] && this.compositeIdToActivityStack[compositeId][0].badge,
() => this.model.activeItem ? this.model.activeItem.id : void 0,
(compositeId: string) => { const item = this.model.findItem(compositeId); return item && item.activity[0].badge; },
this.options.getOnCompositeClickAction,
this.options.colors
);
......@@ -387,195 +369,266 @@ export class CompositeBar extends Widget implements ICompositeBar {
}
private getOverflowingComposites(): { id: string, name: string }[] {
let overflowingIds = this.pinnedComposites;
if (this.activeUnpinnedCompositeId) {
overflowingIds = overflowingIds.concat(this.activeUnpinnedCompositeId);
let overflowingIds = this.model.items.filter(item => item.pinned).map(item => item.id);
// Show the active composite even if it is not pinned
if (this.model.activeItem && !this.model.activeItem.pinned) {
overflowingIds.push(this.model.activeItem.id);
}
const visibleComposites = Object.keys(this.compositeIdToActions);
overflowingIds = overflowingIds.filter(compositeId => visibleComposites.indexOf(compositeId) === -1);
return this.options.composites.filter(c => overflowingIds.indexOf(c.id) !== -1);
overflowingIds = overflowingIds.filter(compositeId => this.visibleComposites.indexOf(compositeId) === -1);
return this.model.items.filter(c => overflowingIds.indexOf(c.id) !== -1);
}
private getVisibleComposites(): string[] {
return Object.keys(this.compositeIdToActions);
private showContextMenu(e: MouseEvent): void {
EventHelper.stop(e, true);
const event = new StandardMouseEvent(e);
const actions: IAction[] = this.model.items
.map(({ id, name }) => (<IAction>{
id,
label: name,
checked: this.isPinned(id),
enabled: true,
run: () => {
if (this.isPinned(id)) {
this.unpin(id);
} else {
this.pin(id, true);
}
}
}));
const otherActions = this.options.getContextMenuActions();
if (otherActions.length) {
actions.push(new Separator());
actions.push(...otherActions);
}
this.contextMenuService.showContextMenu({
getAnchor: () => { return { x: event.posx, y: event.posy }; },
getActions: () => TPromise.as(actions),
});
}
private pullComposite(compositeId: string): void {
const index = Object.keys(this.compositeIdToActions).indexOf(compositeId);
if (index >= 0) {
this.compositeSwitcherBar.pull(index);
const action = this.compositeIdToActions[compositeId];
action.dispose();
delete this.compositeIdToActions[compositeId];
private loadCompositeItemsFromStorage(): ISerializedCompositeBarItem[] {
const storedStates = <Array<string | ISerializedCompositeBarItem>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
const isOldData = storedStates && storedStates.length && typeof storedStates[0] === 'string';
const compositeStates = <ISerializedCompositeBarItem[]>storedStates.map(c =>
typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true } : c);
const actionItem = this.compositeIdToActionItems[action.id];
actionItem.dispose();
delete this.compositeIdToActionItems[action.id];
if (!isOldData) { /* Add new composites only if it is new data */
const newComposites = this.options.composites.filter(c => compositeStates.every(s => s.id !== c.id));
newComposites.sort((c1, c2) => c1.order < c2.order ? -1 : 1);
newComposites.forEach(c => compositeStates.push({ id: c.id, pinned: true, order: c.order /* new composites are pinned by default */ }));
}
}
private toAction(compositeId: string): ActivityAction {
if (this.compositeIdToActions[compositeId]) {
return this.compositeIdToActions[compositeId];
}
return compositeStates;
}
const compositeActivityAction = this.options.getActivityAction(compositeId);
const pinnedAction = this.options.getCompositePinnedAction(compositeId);
this.compositeIdToActionItems[compositeId] = this.instantiationService.createInstance(CompositeActionItem, compositeActivityAction, pinnedAction, this.options.colors, this.options.icon, this);
this.compositeIdToActions[compositeId] = compositeActivityAction;
private saveCompositeItems(): void {
this.storedState = this.model.toJSON();
this.storageService.store(this.options.storageId, JSON.stringify(this.storedState), StorageScope.GLOBAL);
}
return compositeActivityAction;
public shutdown(): void {
this.saveCompositeItems();
}
}
public unpin(compositeId: string): void {
if (!this.isPinned(compositeId)) {
return;
}
interface ISerializedCompositeBarItem {
id: string;
pinned: boolean;
order: number;
}
const defaultCompositeId = this.options.getDefaultCompositeId();
const visibleComposites = this.getVisibleComposites();
interface ICompositeBarItem extends ISerializedCompositeBarItem, IDisposable {
name: string;
activityAction: ActivityAction;
pinnedAction: Action;
activity: ICompositeActivity[];
}
let unpinPromise: TPromise<any>;
class CompositeBarModel {
// remove from pinned
const index = this.pinnedComposites.indexOf(compositeId);
this.pinnedComposites.splice(index, 1);
readonly items: ICompositeBarItem[] = [];
activeItem: ICompositeBarItem;
// Case: composite is not the active one or the active one is a different one
// Solv: we do nothing
if (!this.activeCompositeId || this.activeCompositeId !== compositeId) {
unpinPromise = TPromise.as(null);
}
constructor(private options: ICompositeBarOptions) { }
// Case: composite is not the default composite and default composite is still showing
// Solv: we open the default composite
else if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
unpinPromise = this.options.openComposite(defaultCompositeId);
}
private createCompositeBarItem(id: string, name: string, order: number, pinned: boolean): ICompositeBarItem {
const options = this.options;
let activityAction, pinnedAction;
const dispose = () => {
if (activityAction) {
activityAction.dispose();
}
if (pinnedAction) {
pinnedAction.dispose();
}
};
return {
id, name, pinned, order,
get activityAction() {
if (!activityAction) {
activityAction = options.getActivityAction(id);
}
return activityAction;
},
get pinnedAction() {
if (!pinnedAction) {
pinnedAction = options.getCompositePinnedAction(id);
}
return pinnedAction;
},
activity: [], dispose
};
}
// Case: we closed the last visible composite
// Solv: we hide the part
else if (visibleComposites.length === 1) {
unpinPromise = this.options.hidePart();
add(id: string, name: string, order: number, pinned: boolean, index: number): boolean {
const item = this.findItem(id);
if (item) {
item.order = order;
item.name = name;
return false;
} else {
if (index === void 0) {
index = 0;
while (index < this.items.length && this.items[index].order < order) {
index++;
}
}
this.items.splice(index, 0, this.createCompositeBarItem(id, name, order, pinned));
return true;
}
}
// Case: we closed the default composite
// Solv: we open the next visible composite from top
else {
unpinPromise = this.options.openComposite(visibleComposites.filter(cid => cid !== compositeId)[0]);
remove(id: string): boolean {
for (let index = 0; index < this.items.length; index++) {
if (this.items[index].id === id) {
const item = this.items.splice(index, 1)[0];
item.dispose();
return true;
}
}
unpinPromise.then(() => {
this.updateCompositeSwitcher();
});
// Persist
this.saveCompositesStates();
return false;
}
public isPinned(compositeId: string): boolean {
return this.pinnedComposites.indexOf(compositeId) >= 0;
}
move(compositeId: string, toCompositeId: string): boolean {
public pin(compositeId: string, update = true, index = this.pinnedComposites.length, activate: boolean = true): void {
if (this.isPinned(compositeId)) {
return;
const fromIndex = this.findIndex(compositeId);
const toIndex = this.findIndex(toCompositeId);
// Make sure both items are known to the model
if (fromIndex === -1 || toIndex === -1) {
return false;
}
const activatePromise = activate ? this.options.openComposite(compositeId) : TPromise.as(null);
activatePromise.then(() => {
this.pinnedComposites.splice(index, 0, compositeId);
this.pinnedComposites = arrays.distinct(this.pinnedComposites);
const sourceItem = this.items.splice(fromIndex, 1)[0];
this.items.splice(toIndex, 0, sourceItem);
if (update) {
this.updateCompositeSwitcher();
}
// Make sure a moved composite gets pinned
sourceItem.pinned = true;
// Persist
this.saveCompositesStates();
});
return true;
}
public move(compositeId: string, toCompositeId: string): void {
// Make sure both composites are known to this composite bar
if (this.options.composites.filter(c => c.id === compositeId || c.id === toCompositeId).length !== 2) {
return;
}
// Make sure a moved composite gets pinned
if (!this.isPinned(compositeId)) {
this.pin(compositeId, false /* defer update, we take care of it */);
setPinned(id: string, pinned: boolean): boolean {
for (let index = 0; index < this.items.length; index++) {
const item = this.items[index];
if (item.id === id) {
if (item.pinned !== pinned) {
item.pinned = pinned;
return true;
}
return false;
}
}
return false;
}
const fromIndex = this.pinnedComposites.indexOf(compositeId);
const toIndex = this.pinnedComposites.indexOf(toCompositeId);
this.pinnedComposites.splice(fromIndex, 1);
this.pinnedComposites.splice(toIndex, 0, compositeId);
// Clear composites that are impacted by the move
const visibleComposites = Object.keys(this.compositeIdToActions);
for (let i = Math.min(fromIndex, toIndex); i < visibleComposites.length; i++) {
this.pullComposite(visibleComposites[i]);
addActivity(id: string, activity: ICompositeActivity): boolean {
const item = this.findItem(id);
if (item) {
const stack = item.activity;
for (let i = 0; i <= stack.length; i++) {
if (i === stack.length) {
stack.push(activity);
break;
} else if (stack[i].priority <= activity.priority) {
stack.splice(i, 0, activity);
break;
}
}
this.updateActivity(id);
return true;
}
// timeout helps to prevent artifacts from showing up
setTimeout(() => {
this.updateCompositeSwitcher();
}, 0);
// Persist
this.saveCompositesStates();
return false;
}
public layout(dimension: Dimension): void {
this.dimension = dimension;
if (dimension.height === 0 || dimension.width === 0) {
// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
return;
removeActivity(id: string, activity: ICompositeActivity): boolean {
const item = this.findItem(id);
if (item) {
const index = item.activity.indexOf(activity);
if (index !== -1) {
item.activity.splice(index, 1);
this.updateActivity(id);
return true;
}
}
return false;
}
if (this.compositeSizeInBar.size === 0) {
// Compute size of each composite by getting the size from the css renderer
// Size is later used for overflow computation
this.compositeSwitcherBar.clear();
this.compositeSwitcherBar.push(this.options.composites.map(c => this.options.getActivityAction(c.id)));
this.options.composites.map((c, index) => this.compositeSizeInBar.set(c.id, this.options.orientation === ActionsOrientation.VERTICAL
? this.compositeSwitcherBar.getHeight(index)
: this.compositeSwitcherBar.getWidth(index)
));
this.compositeSwitcherBar.clear();
updateActivity(id: string): void {
const item = this.findItem(id);
if (item) {
if (item.activity.length) {
const [{ badge, clazz }] = item.activity;
item.activityAction.setBadge(badge, clazz);
}
else {
item.activityAction.setBadge(undefined);
}
}
this.updateCompositeSwitcher();
}
private loadCompositesStates(): CompositeState[] {
const storedStates = <Array<string | CompositeState>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
const isOldData = storedStates && storedStates.length && typeof storedStates[0] === 'string';
const compositeStates = <CompositeState[]>storedStates.map(c =>
typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true } : c);
activate(id: string): boolean {
if (!this.activeItem || this.activeItem.id !== id) {
if (this.activeItem) {
this.deactivate();
}
for (let index = 0; index < this.items.length; index++) {
const item = this.items[index];
if (item.id === id) {
this.activeItem = item;
this.activeItem.activityAction.activate();
return true;
}
}
}
return false;
}
if (!isOldData) { /* Add new composites only if it is new data */
const newComposites = this.options.composites.filter(c => compositeStates.every(s => s.id !== c.id));
newComposites.sort((c1, c2) => c1.order < c2.order ? -1 : 1);
newComposites.forEach(c => compositeStates.push({ id: c.id, pinned: true /* new composites are pinned by default */ }));
deactivate(): boolean {
if (this.activeItem) {
this.activeItem.activityAction.deactivate();
this.activeItem = void 0;
return true;
}
return false;
}
return compositeStates;
findItem(id: string): ICompositeBarItem {
return this.items.filter(item => item.id === id)[0];
}
private saveCompositesStates(): void {
const toSave = this.pinnedComposites.map(id => (<CompositeState>{ id, pinned: true }));
for (const composite of this.options.composites) {
if (this.pinnedComposites.indexOf(composite.id) === -1) { // Unpinned composites
toSave.push({ id: composite.id, pinned: false });
findIndex(id: string): number {
for (let index = 0; index < this.items.length; index++) {
if (this.items[index].id === id) {
return index;
}
}
this.storageService.store(this.options.storageId, JSON.stringify(toSave), StorageScope.GLOBAL);
return -1;
}
public shutdown(): void {
this.saveCompositesStates();
toJSON(): ISerializedCompositeBarItem[] {
return this.items.map(({ id, pinned, order }) => ({ id, pinned, order }));
}
}
......@@ -172,6 +172,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
descriptor.enabled = enabled;
if (enabled) {
this.compositeBar.addComposite(descriptor, true);
this.openPanel(descriptor.id, true).done(() => {
this.compositeBar.pin(descriptor.id);
this.compositeBar.activateComposite(descriptor.id);
});
} else {
this.compositeBar.removeComposite(id);
}
......
......@@ -49,6 +49,10 @@ export class OutputPanel extends AbstractTextResourceEditor {
return OUTPUT_PANEL_ID;
}
public getTitle(): string {
return nls.localize('output', "Output");
}
public getActions(): IAction[] {
if (!this.actions) {
this.actions = [
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册