mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 01:25:36 -05:00
375 lines
10 KiB
TypeScript
375 lines
10 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
|
|
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
|
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
|
import {
|
|
IActionBarOptions, ActionsOrientation, IActionViewItem,
|
|
IActionOptions, ActionViewItem, BaseActionViewItem
|
|
} from 'vs/base/browser/ui/actionbar/actionbar';
|
|
import * as lifecycle from 'vs/base/common/lifecycle';
|
|
import * as DOM from 'vs/base/browser/dom';
|
|
import * as types from 'vs/base/common/types';
|
|
|
|
const defaultOptions: IActionBarOptions = {
|
|
orientation: ActionsOrientation.HORIZONTAL,
|
|
context: null
|
|
};
|
|
|
|
/**
|
|
* Contains logic for displaying and handling callbacks for clickable icons. Based on
|
|
* ActionBar vs/base/browser/ui/actionbar/actionbar. This class was needed because we
|
|
* want the ability to display content other than Action icons in the QueryTaskbar.
|
|
*/
|
|
export class ActionBar extends ActionRunner implements IActionRunner {
|
|
|
|
private _options: IActionBarOptions;
|
|
private _actionRunner: IActionRunner;
|
|
private _context: any;
|
|
|
|
// Items
|
|
private _items: IActionViewItem[];
|
|
private _focusedItem?: number;
|
|
private _focusTracker: DOM.IFocusTracker;
|
|
|
|
// Elements
|
|
private _domNode: HTMLElement;
|
|
private _actionsList: HTMLElement;
|
|
|
|
constructor(container: HTMLElement, options: IActionBarOptions = defaultOptions) {
|
|
super();
|
|
this._options = options;
|
|
this._context = options.context;
|
|
this._toDispose = [];
|
|
|
|
if (this._options.actionRunner) {
|
|
this._actionRunner = this._options.actionRunner;
|
|
} else {
|
|
this._actionRunner = new ActionRunner();
|
|
this._toDispose.push(this._actionRunner);
|
|
}
|
|
|
|
//this._toDispose.push(this.addEmitter(this._actionRunner));
|
|
|
|
this._items = [];
|
|
this._focusedItem = undefined;
|
|
|
|
this._domNode = document.createElement('div');
|
|
this._domNode.className = 'monaco-action-bar';
|
|
|
|
if (options.animated !== false) {
|
|
DOM.addClass(this._domNode, 'animated');
|
|
}
|
|
|
|
let isVertical = this._options.orientation === ActionsOrientation.VERTICAL;
|
|
if (isVertical) {
|
|
this._domNode.className += ' vertical';
|
|
}
|
|
|
|
this._register(DOM.addDisposableListener(this._domNode, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
|
let event = new StandardKeyboardEvent(e);
|
|
let eventHandled = true;
|
|
|
|
if (event.equals(isVertical ? KeyCode.UpArrow : KeyCode.LeftArrow)) {
|
|
this.focusPrevious();
|
|
} else if (event.equals(isVertical ? KeyCode.DownArrow : KeyCode.RightArrow)) {
|
|
this.focusNext();
|
|
} else if (event.equals(KeyCode.Escape)) {
|
|
this.cancel();
|
|
} else if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
|
// Nothing, just staying out of the else branch
|
|
} else {
|
|
eventHandled = false;
|
|
}
|
|
|
|
if (eventHandled) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
}));
|
|
|
|
// Prevent native context menu on actions
|
|
this._register(DOM.addDisposableListener(this._domNode, DOM.EventType.CONTEXT_MENU, (e: Event) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}));
|
|
|
|
this._register(DOM.addDisposableListener(this._domNode, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
|
let event = new StandardKeyboardEvent(e);
|
|
|
|
// Run action on Enter/Space
|
|
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
|
this.doTrigger(event);
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
// Recompute focused item
|
|
else if (event.equals(KeyCode.Tab) || event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
|
this.updateFocusedItem();
|
|
}
|
|
}));
|
|
|
|
this._focusTracker = this._register(DOM.trackFocus(this._domNode));
|
|
this._focusTracker.onDidBlur(() => {
|
|
if (document.activeElement === this._domNode || !DOM.isAncestor(document.activeElement, this._domNode)) {
|
|
|
|
// @SQLTODO
|
|
//this.emit(DOM.EventType.BLUR, {});
|
|
this._focusedItem = undefined;
|
|
}
|
|
});
|
|
|
|
this._focusTracker.onDidFocus(() => this.updateFocusedItem());
|
|
|
|
this._actionsList = document.createElement('ul');
|
|
this._actionsList.className = 'actions-container';
|
|
this._actionsList.setAttribute('role', 'toolbar');
|
|
if (this._options.ariaLabel) {
|
|
this._actionsList.setAttribute('aria-label', this._options.ariaLabel);
|
|
}
|
|
|
|
this._domNode.appendChild(this._actionsList);
|
|
|
|
container.appendChild(this._domNode);
|
|
}
|
|
|
|
public setAriaLabel(label: string): void {
|
|
if (label) {
|
|
this._actionsList.setAttribute('aria-label', label);
|
|
} else {
|
|
this._actionsList.removeAttribute('aria-label');
|
|
}
|
|
}
|
|
|
|
private updateFocusedItem(): void {
|
|
let actionIndex = 0;
|
|
for (let i = 0; i < this._actionsList.children.length; i++) {
|
|
let elem = this._actionsList.children[i];
|
|
|
|
if (DOM.isAncestor(document.activeElement, elem)) {
|
|
this._focusedItem = actionIndex;
|
|
break;
|
|
}
|
|
|
|
if (elem.classList.contains('action-item')) {
|
|
actionIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public get context(): any {
|
|
return this._context;
|
|
}
|
|
|
|
public set context(context: any) {
|
|
this._context = context;
|
|
this._items.forEach(i => i.setActionContext(context));
|
|
}
|
|
|
|
public get actionRunner(): IActionRunner {
|
|
return this._actionRunner;
|
|
}
|
|
|
|
public set actionRunner(actionRunner: IActionRunner) {
|
|
if (actionRunner) {
|
|
this._actionRunner = actionRunner;
|
|
this._items.forEach(item => item.actionRunner = actionRunner);
|
|
}
|
|
}
|
|
|
|
public getContainer(): HTMLElement {
|
|
return this._domNode;
|
|
}
|
|
|
|
/**
|
|
* Push an HTML Element onto the action bar UI in the position specified by options.
|
|
* Pushes to the last position if no options are provided.
|
|
*/
|
|
public pushElement(element: HTMLElement, options: IActionOptions = {}): void {
|
|
let index = types.isNumber(options.index) ? options.index : null;
|
|
|
|
if (index === null || index < 0 || index >= this._actionsList.children.length) {
|
|
this._actionsList.appendChild(element);
|
|
} else {
|
|
this._actionsList.insertBefore(element, this._actionsList.children[index++]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push an action onto the action bar UI in the position specified by options.
|
|
* Pushes to the last position if no options are provided.
|
|
*/
|
|
public pushAction(arg: IAction | IAction[], options: IActionOptions = {}): void {
|
|
|
|
const actions: IAction[] = !Array.isArray(arg) ? [arg] : arg;
|
|
|
|
let index = types.isNumber(options.index) ? options.index : null;
|
|
|
|
actions.forEach((action: IAction) => {
|
|
const actionItemElement = document.createElement('li');
|
|
actionItemElement.className = 'action-item';
|
|
actionItemElement.setAttribute('role', 'presentation');
|
|
|
|
let item: IActionViewItem | undefined = undefined;
|
|
|
|
if (this._options.actionViewItemProvider) {
|
|
item = this._options.actionViewItemProvider(action);
|
|
}
|
|
|
|
if (!item) {
|
|
item = new ActionViewItem(this.context, action, options);
|
|
}
|
|
|
|
item.actionRunner = this._actionRunner;
|
|
item.setActionContext(this.context);
|
|
//this.addEmitter(item);
|
|
item.render(actionItemElement);
|
|
|
|
if (index === null || index < 0 || index >= this._actionsList.children.length) {
|
|
this._actionsList.appendChild(actionItemElement);
|
|
} else {
|
|
this._actionsList.insertBefore(actionItemElement, this._actionsList.children[index++]);
|
|
}
|
|
|
|
this._items.push(item);
|
|
});
|
|
}
|
|
|
|
public pull(index: number): void {
|
|
if (index >= 0 && index < this._items.length) {
|
|
this._items.splice(index, 1);
|
|
this._actionsList.removeChild(this._actionsList.childNodes[index]);
|
|
}
|
|
}
|
|
|
|
public clear(): void {
|
|
// Do not dispose action items if they were provided from outside
|
|
this._items = this._options.actionViewItemProvider ? [] : lifecycle.dispose(this._items);
|
|
DOM.clearNode(this._actionsList);
|
|
}
|
|
|
|
public length(): number {
|
|
return this._items.length;
|
|
}
|
|
|
|
public isEmpty(): boolean {
|
|
return this._items.length === 0;
|
|
}
|
|
|
|
public focus(selectFirst?: boolean): void {
|
|
if (selectFirst && typeof this._focusedItem === 'undefined') {
|
|
this._focusedItem = 0;
|
|
}
|
|
|
|
this.updateFocus();
|
|
}
|
|
|
|
private focusNext(): void {
|
|
if (typeof this._focusedItem === 'undefined') {
|
|
this._focusedItem = this._items.length - 1;
|
|
}
|
|
|
|
let startIndex = this._focusedItem;
|
|
let item: IActionViewItem;
|
|
|
|
do {
|
|
this._focusedItem = (this._focusedItem + 1) % this._items.length;
|
|
item = this._items[this._focusedItem];
|
|
} while (this._focusedItem !== startIndex && !item.isEnabled());
|
|
|
|
if (this._focusedItem === startIndex && !item.isEnabled()) {
|
|
this._focusedItem = undefined;
|
|
}
|
|
|
|
this.updateFocus();
|
|
}
|
|
|
|
private focusPrevious(): void {
|
|
if (typeof this._focusedItem === 'undefined') {
|
|
this._focusedItem = 0;
|
|
}
|
|
|
|
let startIndex = this._focusedItem;
|
|
let item: IActionViewItem;
|
|
|
|
do {
|
|
this._focusedItem = this._focusedItem - 1;
|
|
|
|
if (this._focusedItem < 0) {
|
|
this._focusedItem = this._items.length - 1;
|
|
}
|
|
|
|
item = this._items[this._focusedItem];
|
|
} while (this._focusedItem !== startIndex && !item.isEnabled());
|
|
|
|
if (this._focusedItem === startIndex && !item.isEnabled()) {
|
|
this._focusedItem = undefined;
|
|
}
|
|
|
|
this.updateFocus();
|
|
}
|
|
|
|
private updateFocus(): void {
|
|
if (typeof this._focusedItem === 'undefined') {
|
|
this._domNode.focus();
|
|
return;
|
|
}
|
|
|
|
for (let i = 0; i < this._items.length; i++) {
|
|
let item = this._items[i];
|
|
|
|
let actionItem = <any>item;
|
|
|
|
if (i === this._focusedItem) {
|
|
if (types.isFunction(actionItem.focus)) {
|
|
actionItem.focus();
|
|
}
|
|
} else {
|
|
if (types.isFunction(actionItem.blur)) {
|
|
actionItem.blur();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private doTrigger(event: StandardKeyboardEvent): void {
|
|
if (typeof this._focusedItem === 'undefined') {
|
|
return; //nothing to focus
|
|
}
|
|
|
|
// trigger action
|
|
let actionItem = this._items[this._focusedItem];
|
|
if (actionItem instanceof BaseActionViewItem) {
|
|
const context = (actionItem._context === null || actionItem._context === undefined) ? event : actionItem._context;
|
|
this.run(actionItem._action, context);
|
|
}
|
|
}
|
|
|
|
private cancel(): void {
|
|
if (document.activeElement instanceof HTMLElement) {
|
|
(<HTMLElement>document.activeElement).blur(); // remove focus from focussed action
|
|
}
|
|
|
|
//this.emit('cancel');
|
|
}
|
|
|
|
public run(action: IAction, context?: any): Promise<any> {
|
|
return this._actionRunner.run(action, context);
|
|
}
|
|
|
|
public dispose(): void {
|
|
lifecycle.dispose(this._items);
|
|
this._items = [];
|
|
|
|
this._toDispose = lifecycle.dispose(this._toDispose);
|
|
|
|
this._domNode.remove();
|
|
|
|
super.dispose();
|
|
}
|
|
}
|