mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-06 17:23:53 -05:00
More Layering (#9139)
* move handling generated files to the serilization classes * remove unneeded methods * add more folders to strictire compile, add more strict compile options * update ci * wip * add more layering and fix issues * add more strictness * remove unnecessary assertion * add missing checks * fix indentation * wip * remove jsdoc * fix layering * fix compile * fix compile errors * wip * wip * finish layering * fix css * more layering * rip * reworking results serializer * move some files around * move capabilities to platform wip * implement capabilities register provider * fix capabilities service * fix usage of the regist4ry * add contribution * wip * wip * wip * remove no longer good parts * fix strict-nulls * fix issues with startup * another try * fix startup * fix imports * fix tests * fix tests * fix more tests * fix tests * fix more tests * fix broken test * fix tabbing * fix naming * wip * finished layering * fix imports * fix valid layers * fix layers
This commit is contained in:
@@ -1,329 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeView';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants';
|
||||
import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGroupController';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class RefreshAction extends Action {
|
||||
|
||||
public static ID = 'objectExplorer.refresh';
|
||||
public static LABEL = localize('connectionTree.refresh', "Refresh");
|
||||
private _tree: ITree;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
tree: ITree,
|
||||
private element: IConnectionProfile | TreeNode,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@ILogService private _logService: ILogService
|
||||
) {
|
||||
super(id, label);
|
||||
this._tree = tree;
|
||||
}
|
||||
public async run(): Promise<boolean> {
|
||||
let treeNode: TreeNode;
|
||||
if (this.element instanceof ConnectionProfile) {
|
||||
let connection: ConnectionProfile = this.element;
|
||||
if (this._connectionManagementService.isConnected(undefined, connection)) {
|
||||
treeNode = this._objectExplorerService.getObjectExplorerNode(connection);
|
||||
if (treeNode === undefined) {
|
||||
await this._objectExplorerService.updateObjectExplorerNodes(connection.toIConnectionProfile());
|
||||
treeNode = this._objectExplorerService.getObjectExplorerNode(connection);
|
||||
}
|
||||
}
|
||||
} else if (this.element instanceof TreeNode) {
|
||||
treeNode = this.element;
|
||||
}
|
||||
|
||||
if (treeNode) {
|
||||
try {
|
||||
try {
|
||||
await this._objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode);
|
||||
} catch (error) {
|
||||
this.showError(error);
|
||||
return true;
|
||||
}
|
||||
await this._tree.refresh(this.element);
|
||||
} catch (ex) {
|
||||
this._logService.error(ex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private showError(errorMessage: string) {
|
||||
this._logService.error(errorMessage);
|
||||
if (this._errorMessageService) {
|
||||
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DisconnectConnectionAction extends Action {
|
||||
public static ID = 'objectExplorer.disconnect';
|
||||
public static LABEL = localize('DisconnectAction', "Disconnect");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _connectionProfile: ConnectionProfile,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(actionContext: ObjectExplorerActionsContext): Promise<any> {
|
||||
if (!this._connectionProfile) {
|
||||
return true;
|
||||
}
|
||||
if (this._connectionManagementService.isProfileConnected(this._connectionProfile)) {
|
||||
let profileImpl = this._connectionProfile as ConnectionProfile;
|
||||
if (profileImpl) {
|
||||
profileImpl.isDisconnecting = true;
|
||||
}
|
||||
await this._connectionManagementService.disconnect(this._connectionProfile);
|
||||
if (profileImpl) {
|
||||
profileImpl.isDisconnecting = false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to add a server to the group
|
||||
*/
|
||||
export class AddServerAction extends Action {
|
||||
public static ID = 'registeredServers.addConnection';
|
||||
public static LABEL = localize('connectionTree.addConnection', "New Connection");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'add-server-action';
|
||||
}
|
||||
|
||||
public async run(element: ConnectionProfileGroup): Promise<boolean> {
|
||||
let connection: IConnectionProfile = element === undefined ? undefined : {
|
||||
connectionName: undefined,
|
||||
serverName: undefined,
|
||||
databaseName: undefined,
|
||||
userName: undefined,
|
||||
password: undefined,
|
||||
authenticationType: undefined,
|
||||
groupId: undefined,
|
||||
groupFullName: element.fullName,
|
||||
savePassword: undefined,
|
||||
getOptionsKey: undefined,
|
||||
matches: undefined,
|
||||
providerName: '',
|
||||
options: {},
|
||||
saveProfile: true,
|
||||
id: element.id
|
||||
};
|
||||
await this._connectionManagementService.showConnectionDialog(undefined, undefined, connection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to add a server to the group
|
||||
*/
|
||||
export class AddServerGroupAction extends Action {
|
||||
public static ID = 'registeredServers.addServerGroup';
|
||||
public static LABEL = localize('connectionTree.addServerGroup', "New Server Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IServerGroupController private readonly serverGroupController: IServerGroupController
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'add-server-group-action';
|
||||
}
|
||||
|
||||
public async run(): Promise<boolean> {
|
||||
await this.serverGroupController.showCreateGroupDialog();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to edit a server group
|
||||
*/
|
||||
export class EditServerGroupAction extends Action {
|
||||
public static ID = 'registeredServers.editServerGroup';
|
||||
public static LABEL = localize('connectionTree.editServerGroup', "Edit Server Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _group: ConnectionProfileGroup,
|
||||
@IServerGroupController private readonly serverGroupController: IServerGroupController
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'edit-server-group-action';
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
this.serverGroupController.showEditGroupDialog(this._group);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display active connections in the tree
|
||||
*/
|
||||
export class ActiveConnectionsFilterAction extends Action {
|
||||
public static ID = 'registeredServers.recentConnections';
|
||||
public static LABEL = localize('activeConnections', "Show Active Connections");
|
||||
private static enabledClass = 'active-connections-action';
|
||||
private static disabledClass = 'icon server-page';
|
||||
private static showAllConnectionsLabel = localize('showAllConnections', "Show All Connections");
|
||||
private _isSet: boolean;
|
||||
public static readonly ACTIVE = 'active';
|
||||
public get isSet(): boolean {
|
||||
return this._isSet;
|
||||
}
|
||||
public set isSet(value: boolean) {
|
||||
this._isSet = value;
|
||||
this.class = (!this._isSet) ?
|
||||
ActiveConnectionsFilterAction.enabledClass : ActiveConnectionsFilterAction.disabledClass;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private view: ServerTreeView
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = ActiveConnectionsFilterAction.enabledClass;
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
if (!this.view) {
|
||||
// return without doing anything
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (this.class === ActiveConnectionsFilterAction.enabledClass) {
|
||||
// show active connections in the tree
|
||||
this.view.showFilteredTree(ActiveConnectionsFilterAction.ACTIVE);
|
||||
this.isSet = true;
|
||||
this.label = ActiveConnectionsFilterAction.showAllConnectionsLabel;
|
||||
} else {
|
||||
// show full tree
|
||||
this.view.refreshTree();
|
||||
this.isSet = false;
|
||||
this.label = ActiveConnectionsFilterAction.LABEL;
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display recent connections in the tree
|
||||
*/
|
||||
export class RecentConnectionsFilterAction extends Action {
|
||||
public static ID = 'registeredServers.recentConnections';
|
||||
public static LABEL = localize('recentConnections', "Recent Connections");
|
||||
private static enabledClass = 'recent-connections-action';
|
||||
private static disabledClass = 'recent-connections-action-set';
|
||||
private _isSet: boolean;
|
||||
public get isSet(): boolean {
|
||||
return this._isSet;
|
||||
}
|
||||
public set isSet(value: boolean) {
|
||||
this._isSet = value;
|
||||
this.class = (!this._isSet) ?
|
||||
RecentConnectionsFilterAction.enabledClass : RecentConnectionsFilterAction.disabledClass;
|
||||
}
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private view: ServerTreeView
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = RecentConnectionsFilterAction.enabledClass;
|
||||
this._isSet = false;
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
if (!this.view) {
|
||||
// return without doing anything
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (this.class === RecentConnectionsFilterAction.enabledClass) {
|
||||
// show recent connections in the tree
|
||||
this.view.showFilteredTree('recent');
|
||||
this.isSet = true;
|
||||
} else {
|
||||
// show full tree
|
||||
this.view.refreshTree();
|
||||
this.isSet = false;
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to delete a server/group
|
||||
*/
|
||||
export class DeleteConnectionAction extends Action {
|
||||
public static ID = 'registeredServers.deleteConnection';
|
||||
public static DELETE_CONNECTION_LABEL = localize('deleteConnection', "Delete Connection");
|
||||
public static DELETE_CONNECTION_GROUP_LABEL = localize('deleteConnectionGroup', "Delete Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private element: IConnectionProfile | ConnectionProfileGroup,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'delete-connection-action';
|
||||
if (element instanceof ConnectionProfileGroup && element.id === UNSAVED_GROUP_ID) {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
if (element instanceof ConnectionProfile) {
|
||||
let parent: ConnectionProfileGroup = element.parent;
|
||||
if (parent && parent.id === UNSAVED_GROUP_ID) {
|
||||
this.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
if (this.element instanceof ConnectionProfile) {
|
||||
this._connectionManagementService.deleteConnection(this.element);
|
||||
} else if (this.element instanceof ConnectionProfileGroup) {
|
||||
this._connectionManagementService.deleteConnectionGroup(this.element);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ITree, IDragAndDrop, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
|
||||
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils';
|
||||
import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
|
||||
/**
|
||||
* Implements drag and drop for the server tree
|
||||
*/
|
||||
export class ServerTreeDragAndDrop implements IDragAndDrop {
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a uri if the given element should be allowed to drag.
|
||||
* Returns null, otherwise.
|
||||
*/
|
||||
public getDragURI(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>element).id;
|
||||
}
|
||||
else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>element).id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a label(name) to display when dragging the element.
|
||||
*/
|
||||
public getDragLabel(tree: ITree, elements: any[]): string {
|
||||
if (elements[0] instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>elements[0]).serverName;
|
||||
} else if (elements[0] instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>elements[0]).name;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the drag operation starts.
|
||||
*/
|
||||
public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
|
||||
TreeUpdateUtils.isInDragAndDrop = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DragOverReaction indicating whether sources can be
|
||||
* dropped into target or some parent of the target.
|
||||
* Returns DRAG_OVER_ACCEPT_BUBBLE_DOWN when element is a connection group or connection
|
||||
*/
|
||||
public onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction {
|
||||
|
||||
let canDragOver: boolean = true;
|
||||
if (targetElement instanceof ConnectionProfile || targetElement instanceof ConnectionProfileGroup) {
|
||||
let targetConnectionProfileGroup = this.getTargetGroup(targetElement);
|
||||
// Verify if the connection can be moved to the target group
|
||||
const source = data.getData()[0];
|
||||
if (source instanceof ConnectionProfile) {
|
||||
if (!this._connectionManagementService.canChangeConnectionConfig(source, targetConnectionProfileGroup.id)) {
|
||||
canDragOver = false;
|
||||
}
|
||||
} else if (source instanceof ConnectionProfileGroup) {
|
||||
// Dropping a group to itself or its descendants nodes is not allowed
|
||||
// to avoid creating a circular structure.
|
||||
canDragOver = source.id !== targetElement.id && !source.isAncestorOf(targetElement);
|
||||
}
|
||||
|
||||
} else {
|
||||
canDragOver = false;
|
||||
}
|
||||
|
||||
if (canDragOver) {
|
||||
return DRAG_OVER_ACCEPT_BUBBLE_DOWN(true);
|
||||
} else {
|
||||
return DRAG_OVER_REJECT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a drop in the server tree.
|
||||
*/
|
||||
public drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): void {
|
||||
TreeUpdateUtils.isInDragAndDrop = false;
|
||||
|
||||
let targetConnectionProfileGroup: ConnectionProfileGroup = this.getTargetGroup(targetElement);
|
||||
|
||||
const source = data.getData()[0];
|
||||
if (source && source.getParent) {
|
||||
let oldParent: ConnectionProfileGroup = source.getParent();
|
||||
const self = this;
|
||||
if (this.isDropAllowed(targetConnectionProfileGroup, oldParent, source)) {
|
||||
|
||||
if (source instanceof ConnectionProfile) {
|
||||
// Change group id of profile
|
||||
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id).then(() => {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
|
||||
});
|
||||
} else if (source instanceof ConnectionProfileGroup) {
|
||||
// Change parent id of group
|
||||
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dropAbort(tree: ITree, data: IDragAndDropData): void {
|
||||
TreeUpdateUtils.isInDragAndDrop = false;
|
||||
}
|
||||
|
||||
private getTargetGroup(targetElement: any): ConnectionProfileGroup {
|
||||
let targetConnectionProfileGroup: ConnectionProfileGroup;
|
||||
if (targetElement instanceof ConnectionProfile) {
|
||||
targetConnectionProfileGroup = (<ConnectionProfile>targetElement).getParent();
|
||||
}
|
||||
else {
|
||||
targetConnectionProfileGroup = <ConnectionProfileGroup>targetElement;
|
||||
}
|
||||
|
||||
return targetConnectionProfileGroup;
|
||||
}
|
||||
|
||||
private isDropAllowed(targetConnectionProfileGroup: ConnectionProfileGroup,
|
||||
oldParent: ConnectionProfileGroup,
|
||||
source: ConnectionProfile | ConnectionProfileGroup): boolean {
|
||||
|
||||
let isDropToItself = source && targetConnectionProfileGroup && (source instanceof ConnectionProfileGroup) && source.name === targetConnectionProfileGroup.name;
|
||||
let isDropToSameLevel = oldParent && oldParent.equals(targetConnectionProfileGroup);
|
||||
let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === UNSAVED_GROUP_ID);
|
||||
return (!isDropToSameLevel && !isDropToItself && !isUnsavedDrag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements drag and drop for the connection tree
|
||||
*/
|
||||
export class RecentConnectionsDragAndDrop implements IDragAndDrop {
|
||||
|
||||
/**
|
||||
* Returns a uri if the given element should be allowed to drag.
|
||||
* Returns null, otherwise.
|
||||
*/
|
||||
public getDragURI(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>element).id;
|
||||
}
|
||||
else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>element).id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a label(name) to display when dragging the element.
|
||||
*/
|
||||
public getDragLabel(tree: ITree, elements: any[]): string {
|
||||
if (elements[0] instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>elements[0]).serverName;
|
||||
}
|
||||
else if (elements[0] instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>elements[0]).name;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent when the drag operation is starting.
|
||||
*/
|
||||
public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DragOverReaction indicating whether sources can be
|
||||
* dropped into target or some parent of the target.
|
||||
*/
|
||||
public onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction {
|
||||
return DRAG_OVER_REJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drop in the server tree.
|
||||
*/
|
||||
public drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): void {
|
||||
// No op
|
||||
}
|
||||
|
||||
public dropAbort(tree: ITree, data: IDragAndDropData): void { }
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
class IconRenderer {
|
||||
private iconRegistered: Set<string> = new Set<string>();
|
||||
|
||||
public registerIcon(path: URI | IconPath): string {
|
||||
if (!path) { return undefined; }
|
||||
let iconPath: IconPath = this.toIconPath(path);
|
||||
let iconUid: string = this.getIconUid(iconPath);
|
||||
if (!this.iconRegistered.has(iconUid)) {
|
||||
createCSSRule(`.icon#${iconUid}`, `background: ${asCSSUrl(iconPath.light || iconPath.dark)} center center no-repeat`);
|
||||
createCSSRule(`.vs-dark .icon#${iconUid}, .hc-black .icon#${iconUid}`, `background: ${asCSSUrl(iconPath.dark)} center center no-repeat`);
|
||||
this.iconRegistered.add(iconUid);
|
||||
}
|
||||
return iconUid;
|
||||
}
|
||||
|
||||
public getIconUid(path: URI | IconPath): string {
|
||||
if (!path) { return undefined; }
|
||||
let iconPath: IconPath = this.toIconPath(path);
|
||||
return `icon${hash(iconPath.light.toString() + iconPath.dark.toString())}`;
|
||||
}
|
||||
|
||||
private toIconPath(path: URI | IconPath): IconPath {
|
||||
if (path['light']) {
|
||||
return path as IconPath;
|
||||
} else {
|
||||
let singlePath = path as URI;
|
||||
return { light: singlePath, dark: singlePath };
|
||||
}
|
||||
}
|
||||
|
||||
public putIcon(element: HTMLElement, path: URI | IconPath): void {
|
||||
if (!element || !path) { return undefined; }
|
||||
let iconUid: string = this.registerIcon(path);
|
||||
element.id = iconUid;
|
||||
}
|
||||
|
||||
public removeIcon(element: HTMLElement): void {
|
||||
if (!element) { return undefined; }
|
||||
element.id = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export const iconRenderer: IconRenderer = new IconRenderer();
|
||||
|
||||
class BadgeRenderer {
|
||||
public readonly serverConnected: string = 'serverConnected';
|
||||
public readonly serverDisconnected: string = 'serverDisconnected';
|
||||
public readonly newTag: string = 'newTag';
|
||||
|
||||
private badgeCreated: Set<string> = new Set<string>();
|
||||
|
||||
constructor() {
|
||||
this.createBadge(this.serverConnected, this.getConnectionStatusBadge(true));
|
||||
this.createBadge(this.serverDisconnected, this.getConnectionStatusBadge(false));
|
||||
this.createBadge(this.newTag, this.getNewTagBadge());
|
||||
}
|
||||
|
||||
private getConnectionStatusBadge(isConnected: boolean) {
|
||||
let circleColor: string = isConnected ? 'rgba(59, 180, 74, 100%)' : 'rgba(208, 46, 0, 100%)';
|
||||
let bgColor: string = isConnected ? 'rgba(59, 180, 74, 100%)' : 'rgba(255, 255, 255, 80%)';
|
||||
return `position: absolute;
|
||||
height: 0.25rem;
|
||||
width: 0.25rem;
|
||||
top: 14px;
|
||||
left: 19px;
|
||||
border: 0.12rem solid ${circleColor};
|
||||
border-radius: 100%;
|
||||
background: ${bgColor};
|
||||
content:"";
|
||||
font-size: 100%;
|
||||
line-height: 100%;
|
||||
color:white;
|
||||
text-align:center;
|
||||
vertical-align:middle;`
|
||||
.replace(/\t/g, ' ').replace(/\r?\n/g, ' ').replace(/ +/g, ' ');
|
||||
}
|
||||
|
||||
private getNewTagBadge(): string {
|
||||
return `position: absolute;
|
||||
height: 0.4rem;
|
||||
width: 0.4rem;
|
||||
top: 3px;
|
||||
left: 5px;
|
||||
border: 1px solid green;
|
||||
border-radius: 15%;
|
||||
background: green;
|
||||
content:"N";
|
||||
font-size: 0.3rem;
|
||||
font-weight: bold;
|
||||
line-height: 0.4rem;
|
||||
color: white;
|
||||
text-align:center;
|
||||
vertical-align:middle;`
|
||||
.replace(/\t/g, ' ').replace(/\r?\n/g, ' ').replace(/ +/g, ' ');
|
||||
}
|
||||
|
||||
private createBadge(badgeClass: string, badge: string): void {
|
||||
if (!this.badgeCreated.has(badgeClass)) {
|
||||
createCSSRule(`.${badgeClass}:after`, badge);
|
||||
this.badgeCreated.add(badgeClass);
|
||||
}
|
||||
}
|
||||
|
||||
public addBadge(element: HTMLElement, badgeClass: string): void {
|
||||
element.innerHTML = (element.innerHTML || '') +
|
||||
`<div class="${badgeClass}" style="width: 0px; height: 0px;"><div>`;
|
||||
}
|
||||
|
||||
public removeBadge(element: HTMLElement, badgeClass: string): void {
|
||||
let children: HTMLCollection = element.children;
|
||||
let current = children[0];
|
||||
while (current) {
|
||||
let next = current.nextElementSibling;
|
||||
if (current.classList.contains(badgeClass)) {
|
||||
current.remove();
|
||||
break;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const badgeRenderer: BadgeRenderer = new BadgeRenderer();
|
||||
|
||||
interface IconPath {
|
||||
light: URI;
|
||||
dark: URI;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.group-color-options {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.group-color-options .server-group-color {
|
||||
flex: 1 1 auto;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.server-group-dialog {
|
||||
padding: 15px
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils';
|
||||
import { TreeSelectionHandler } from 'sql/workbench/contrib/objectExplorer/browser/treeSelectionHandler';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
|
||||
export class ObjectExplorerActionsContext implements azdata.ObjectExplorerContext {
|
||||
public connectionProfile: azdata.IConnectionProfile;
|
||||
public nodeInfo: azdata.NodeInfo;
|
||||
public isConnectionNode: boolean = false;
|
||||
}
|
||||
|
||||
export async function getTreeNode(context: ObjectExplorerActionsContext, objectExplorerService: IObjectExplorerService): Promise<TreeNode> {
|
||||
if (context.isConnectionNode) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return await objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath);
|
||||
}
|
||||
|
||||
|
||||
export class OEAction extends ExecuteCommandAction {
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@ICommandService commandService: ICommandService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
|
||||
) {
|
||||
super(id, label, commandService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
|
||||
let profile: IConnectionProfile;
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
if (actionContext.isConnectionNode) {
|
||||
profile = new ConnectionProfile(this._capabilitiesService, actionContext.connectionProfile);
|
||||
} else {
|
||||
// Get the "correct" version from the tree
|
||||
let treeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
profile = TreeUpdateUtils.getConnectionProfile(treeNode);
|
||||
}
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
|
||||
return super.run(profile).then(() => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
|
||||
|
||||
/**
|
||||
* Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
|
||||
*/
|
||||
export class RecentConnectionDataSource implements IDataSource {
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the given element.
|
||||
* No more than one element may use a given identifier.
|
||||
*/
|
||||
public getId(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>element).id;
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>element).id;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean value indicating whether the element has children.
|
||||
*/
|
||||
public hasChildren(tree: ITree, element: any): boolean {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return false;
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>element).hasChildren();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's children as an array in a promise.
|
||||
*/
|
||||
public getChildren(tree: ITree, element: any): Promise<any> {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return Promise.resolve(null);
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return Promise.resolve((<ConnectionProfileGroup>element).getChildren());
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's parent in a promise.
|
||||
*/
|
||||
public getParent(tree: ITree, element: any): Promise<any> {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return Promise.resolve((<ConnectionProfile>element).getParent());
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return Promise.resolve((<ConnectionProfileGroup>element).getParent());
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/serverGroupDialog';
|
||||
|
||||
import { Colorbox } from 'sql/base/browser/ui/colorbox/colorbox';
|
||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { ServerGroupViewModel } from 'sql/workbench/contrib/objectExplorer/common/serverGroupViewModel';
|
||||
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
|
||||
|
||||
export class ServerGroupDialog extends Modal {
|
||||
private _addServerButton: Button;
|
||||
private _closeButton: Button;
|
||||
private _colorColorBoxesMap: Array<{ color: string, colorbox: Colorbox }> = [];
|
||||
private _selectedColorOption: number;
|
||||
private _groupNameInputBox: InputBox;
|
||||
private _groupDescriptionInputBox: InputBox;
|
||||
private _viewModel: ServerGroupViewModel;
|
||||
private _skipGroupNameValidation: boolean = false;
|
||||
private _serverGroupContainer: HTMLElement;
|
||||
|
||||
private _onAddServerGroup = new Emitter<void>();
|
||||
public onAddServerGroup: Event<void> = this._onAddServerGroup.event;
|
||||
|
||||
private _onCancel = new Emitter<void>();
|
||||
public onCancel: Event<void> = this._onCancel.event;
|
||||
|
||||
private _onCloseEvent = new Emitter<void>();
|
||||
public onCloseEvent: Event<void> = this._onCloseEvent.event;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(localize('ServerGroupsDialogTitle', "Server Groups"), TelemetryKeys.ServerGroups, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService);
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
const okLabel = localize('serverGroup.ok', "OK");
|
||||
const cancelLabel = localize('serverGroup.cancel', "Cancel");
|
||||
this._addServerButton = this.addFooterButton(okLabel, () => this.addGroup());
|
||||
this._closeButton = this.addFooterButton(cancelLabel, () => this.cancel());
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
const body = DOM.append(container, DOM.$('.server-group-dialog'));
|
||||
|
||||
// Connection Group Name
|
||||
const serverGroupNameLabel = localize('connectionGroupName', "Server group name");
|
||||
|
||||
DOM.append(body, DOM.$('.dialog-label')).innerText = serverGroupNameLabel;
|
||||
|
||||
this._groupNameInputBox = new InputBox(DOM.append(body, DOM.$('.input-divider')), this._contextViewService, {
|
||||
validationOptions: {
|
||||
validation: (value: string) => !value && !this._skipGroupNameValidation ? ({ type: MessageType.ERROR, content: localize('MissingGroupNameError', "Group name is required.") }) : null
|
||||
},
|
||||
ariaLabel: serverGroupNameLabel
|
||||
});
|
||||
|
||||
// Connection Group Description
|
||||
const groupDescriptionLabel = localize('groupDescription', "Group description");
|
||||
DOM.append(body, DOM.$('.dialog-label')).innerText = groupDescriptionLabel;
|
||||
|
||||
this._groupDescriptionInputBox = new InputBox(DOM.append(body, DOM.$('.input-divider')), this._contextViewService, {
|
||||
ariaLabel: groupDescriptionLabel
|
||||
});
|
||||
|
||||
// Connection Group Color
|
||||
const groupColorLabel = localize('groupColor', "Group color");
|
||||
DOM.append(body, DOM.$('.dialog-label')).innerText = groupColorLabel;
|
||||
|
||||
this._serverGroupContainer = DOM.append(body, DOM.$('.group-color-options'));
|
||||
this.fillGroupColors(this._serverGroupContainer);
|
||||
|
||||
DOM.addStandardDisposableListener(body, DOM.EventType.KEY_DOWN, (event: StandardKeyboardEvent) => {
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
this.preventDefaultKeyboardEvent(event);
|
||||
this.focusPrevious();
|
||||
} else if (event.equals(KeyCode.Tab)) {
|
||||
this.preventDefaultKeyboardEvent(event);
|
||||
this.focusNext();
|
||||
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.LeftArrow)) {
|
||||
this.preventDefaultKeyboardEvent(event);
|
||||
this.focusNextColor(event.equals(KeyCode.RightArrow));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private preventDefaultKeyboardEvent(e: StandardKeyboardEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
private isFocusOnColors(): boolean {
|
||||
let result = false;
|
||||
this._colorColorBoxesMap.forEach(({ colorbox: colorbox }) => {
|
||||
if (document.activeElement === colorbox.domNode) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private focusNext(): void {
|
||||
if (this._groupNameInputBox.hasFocus()) {
|
||||
this._groupDescriptionInputBox.focus();
|
||||
} else if (this._groupDescriptionInputBox.hasFocus()) {
|
||||
this._colorColorBoxesMap[this._selectedColorOption].colorbox.focus();
|
||||
} else if (this.isFocusOnColors()) {
|
||||
this._addServerButton.enabled ? this._addServerButton.focus() : this._closeButton.focus();
|
||||
} else if (document.activeElement === this._addServerButton.element) {
|
||||
this._closeButton.focus();
|
||||
}
|
||||
else if (document.activeElement === this._closeButton.element) {
|
||||
this._groupNameInputBox.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private focusPrevious(): void {
|
||||
if (document.activeElement === this._closeButton.element) {
|
||||
this._addServerButton.enabled ? this._addServerButton.focus() : this._colorColorBoxesMap[this._selectedColorOption].colorbox.focus();
|
||||
} else if (document.activeElement === this._addServerButton.element) {
|
||||
this._colorColorBoxesMap[this._selectedColorOption].colorbox.focus();
|
||||
} else if (this.isFocusOnColors()) {
|
||||
this._groupDescriptionInputBox.focus();
|
||||
} else if (this._groupDescriptionInputBox.hasFocus()) {
|
||||
this._groupNameInputBox.focus();
|
||||
} else if (this._groupNameInputBox.hasFocus()) {
|
||||
this._closeButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private focusNextColor(moveRight: boolean): void {
|
||||
let focusIndex: number = -1;
|
||||
for (let i = 0; i < this._colorColorBoxesMap.length; i++) {
|
||||
if (document.activeElement === this._colorColorBoxesMap[i].colorbox.domNode) {
|
||||
focusIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (focusIndex >= 0) {
|
||||
if (moveRight) {
|
||||
focusIndex++;
|
||||
}
|
||||
else {
|
||||
focusIndex--;
|
||||
}
|
||||
|
||||
// check for wraps
|
||||
if (focusIndex < 0) {
|
||||
focusIndex = this._colorColorBoxesMap.length - 1;
|
||||
} else if (focusIndex >= this._colorColorBoxesMap.length) {
|
||||
focusIndex = 0;
|
||||
}
|
||||
|
||||
this._colorColorBoxesMap[focusIndex].colorbox.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onSelectGroupColor(colorToSelect: string): void {
|
||||
this._viewModel.groupColor = colorToSelect;
|
||||
this._selectedColorOption = this._viewModel.colors.indexOf(colorToSelect);
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
// Theme styler
|
||||
this._register(attachInputBoxStyler(this._groupNameInputBox, this._themeService));
|
||||
this._register(attachInputBoxStyler(this._groupDescriptionInputBox, this._themeService));
|
||||
this._register(attachButtonStyler(this._addServerButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._closeButton, this._themeService));
|
||||
|
||||
// handler for name change events
|
||||
this._register(this._groupNameInputBox.onDidChange(groupName => {
|
||||
this.groupNameChanged(groupName);
|
||||
}));
|
||||
|
||||
// handler for description change events
|
||||
this._register(this._groupDescriptionInputBox.onDidChange(groupDescription => {
|
||||
this.groupDescriptionChanged(groupDescription);
|
||||
}));
|
||||
}
|
||||
|
||||
private fillGroupColors(container: HTMLElement): void {
|
||||
for (let i = 0; i < this._viewModel.colors.length; i++) {
|
||||
const color = this._viewModel.colors[i];
|
||||
|
||||
const colorColorBox = new Colorbox(container, {
|
||||
name: 'server-group-color',
|
||||
class: ['server-group-color'],
|
||||
label: `Colobox Color: ${color}`,
|
||||
});
|
||||
|
||||
this._register(colorColorBox.onSelect((viaKeyboard) => {
|
||||
this.onSelectGroupColor(color);
|
||||
}));
|
||||
colorColorBox.style({
|
||||
backgroundColor: Color.fromHex(color)
|
||||
});
|
||||
|
||||
// Theme styler
|
||||
this._register(attachCheckboxStyler(colorColorBox, this._themeService));
|
||||
|
||||
// add the new colorbox to the color map
|
||||
this._colorColorBoxesMap[i] = { color, colorbox: colorColorBox };
|
||||
}
|
||||
}
|
||||
|
||||
private groupNameChanged(groupName: string) {
|
||||
this._viewModel.groupName = groupName;
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private groupDescriptionChanged(groupDescription: string) {
|
||||
this._viewModel.groupDescription = groupDescription;
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
public get groupName(): string {
|
||||
return this._groupNameInputBox.value;
|
||||
}
|
||||
|
||||
public get groupDescription(): string {
|
||||
return this._groupDescriptionInputBox.value;
|
||||
}
|
||||
|
||||
public get selectedColor(): string {
|
||||
return this._colorColorBoxesMap[this._selectedColorOption].color;
|
||||
}
|
||||
|
||||
public get viewModel(): ServerGroupViewModel {
|
||||
return this._viewModel;
|
||||
}
|
||||
public set viewModel(theViewModel: ServerGroupViewModel) {
|
||||
this._viewModel = theViewModel;
|
||||
if (this._serverGroupContainer) {
|
||||
DOM.clearNode(this._serverGroupContainer);
|
||||
this.fillGroupColors(this._serverGroupContainer);
|
||||
}
|
||||
}
|
||||
|
||||
public addGroup(): void {
|
||||
if (this._addServerButton.enabled) {
|
||||
if (this.validateInputs()) {
|
||||
this._onAddServerGroup.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public hideError() {
|
||||
this.setError('');
|
||||
}
|
||||
|
||||
private validateInputs(): boolean {
|
||||
let validate = this._groupNameInputBox.validate();
|
||||
if (!validate) {
|
||||
this._groupNameInputBox.focus();
|
||||
}
|
||||
return validate;
|
||||
}
|
||||
|
||||
// initialize the view based on the current state of the view model
|
||||
private initializeView(): void {
|
||||
this.title = this._viewModel.getDialogTitle();
|
||||
|
||||
this._skipGroupNameValidation = true;
|
||||
this._groupNameInputBox.value = this._viewModel.groupName;
|
||||
this._skipGroupNameValidation = false;
|
||||
|
||||
this._groupDescriptionInputBox.value = this._viewModel.groupDescription;
|
||||
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
// update UI elements that have derivative behaviors based on other state changes
|
||||
private updateView(): void {
|
||||
// check the color buttons and if their checked state does not match the view model state then correct it
|
||||
for (let i = 0; i < this._colorColorBoxesMap.length; i++) {
|
||||
let { colorbox: colorbox, color } = this._colorColorBoxesMap[i];
|
||||
if ((this._viewModel.groupColor === color) && (colorbox.checked === false)) {
|
||||
colorbox.checked = true;
|
||||
this._selectedColorOption = i;
|
||||
} else if ((this._viewModel.groupColor !== color) && (colorbox.checked === true)) {
|
||||
colorbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// OK button state - enabled if there are pending changes that can be saved
|
||||
this._addServerButton.enabled = this._viewModel.hasPendingChanges();
|
||||
}
|
||||
|
||||
/* Overwrite escape key behavior */
|
||||
protected onClose() {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
/* Overwrite enter key behavior */
|
||||
protected onAccept() {
|
||||
this.addGroup();
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
this._onCancel.fire();
|
||||
this.close();
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.hide();
|
||||
this._groupNameInputBox.hideMessage();
|
||||
this._onCloseEvent.fire();
|
||||
}
|
||||
|
||||
public open() {
|
||||
// reset the dialog
|
||||
this.hideError();
|
||||
this.initializeView();
|
||||
this.show();
|
||||
this._groupNameInputBox.focus();
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import {
|
||||
DisconnectConnectionAction, AddServerAction,
|
||||
DeleteConnectionAction, RefreshAction, EditServerGroupAction
|
||||
} from 'sql/workbench/contrib/objectExplorer/browser/connectionTreeAction';
|
||||
import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { ConnectionContextKey } from 'sql/workbench/services/connection/common/connectionContextKey';
|
||||
import { TreeNodeContextKey } from 'sql/workbench/contrib/objectExplorer/common/treeNodeContextKey';
|
||||
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
|
||||
import { ServerInfoContextKey } from 'sql/workbench/contrib/connection/common/serverInfoContextKey';
|
||||
import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { firstIndex, find } from 'vs/base/common/arrays';
|
||||
|
||||
/**
|
||||
* Provides actions for the server tree elements
|
||||
*/
|
||||
export class ServerTreeActionProvider extends ContributableActionProvider {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public hasActions(tree: ITree, element: any): boolean {
|
||||
return element instanceof ConnectionProfileGroup || (element instanceof ConnectionProfile) || (element instanceof TreeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions given an element in the tree
|
||||
*/
|
||||
public getActions(tree: ITree, element: any): IAction[] {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return this.getConnectionActions(tree, element);
|
||||
}
|
||||
if (element instanceof ConnectionProfileGroup) {
|
||||
return this.getConnectionProfileGroupActions(tree, element);
|
||||
}
|
||||
if (element instanceof TreeNode) {
|
||||
return this.getObjectExplorerNodeActions({
|
||||
tree: tree,
|
||||
profile: element.getConnectionProfile(),
|
||||
treeNode: element
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for connection elements
|
||||
*/
|
||||
public getConnectionActions(tree: ITree, profile: ConnectionProfile): IAction[] {
|
||||
let node = new TreeNode(NodeType.Server, '', false, '', '', '', undefined, undefined, undefined, undefined);
|
||||
return this.getAllActions({
|
||||
tree: tree,
|
||||
profile: profile,
|
||||
treeNode: node
|
||||
}, (context) => this.getBuiltinConnectionActions(context));
|
||||
}
|
||||
|
||||
private getAllActions(context: ObjectExplorerContext, getDefaultActions: (context: ObjectExplorerContext) => IAction[]) {
|
||||
// Create metadata needed to get a useful set of actions
|
||||
let scopedContextService = this.getContextKeyService(context);
|
||||
let menu = this.menuService.createMenu(MenuId.ObjectExplorerItemContext, scopedContextService);
|
||||
|
||||
// Fill in all actions
|
||||
const builtIn = getDefaultActions(context);
|
||||
const actions = [];
|
||||
const options = { arg: undefined, shouldForwardArgs: true };
|
||||
const groups = menu.getActions(options);
|
||||
let insertIndex: number | undefined = 0;
|
||||
const queryIndex = firstIndex(groups, v => {
|
||||
if (v[0] === '0_query') {
|
||||
return true;
|
||||
} else {
|
||||
insertIndex += v[1].length;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
insertIndex = queryIndex > -1 ? insertIndex + groups[queryIndex][1].length : undefined;
|
||||
fillInActions(groups, actions, false);
|
||||
|
||||
if (insertIndex) {
|
||||
if (!(actions[insertIndex] instanceof Separator) && builtIn.length > 0) {
|
||||
builtIn.unshift(new Separator());
|
||||
}
|
||||
actions.splice(insertIndex, 0, ...builtIn);
|
||||
} else {
|
||||
if (actions.length > 0 && builtIn.length > 0) {
|
||||
builtIn.push(new Separator());
|
||||
}
|
||||
actions.unshift(...builtIn);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
scopedContextService.dispose();
|
||||
menu.dispose();
|
||||
return actions;
|
||||
|
||||
}
|
||||
|
||||
private getBuiltinConnectionActions(context: ObjectExplorerContext): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
|
||||
if (this._connectionManagementService.isProfileConnected(context.profile)) {
|
||||
actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL, context.profile));
|
||||
}
|
||||
actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, context.profile));
|
||||
|
||||
// Contribute refresh action for scriptable objects via contribution
|
||||
if (!this.isScriptableObject(context)) {
|
||||
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.profile));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
private getContextKeyService(context: ObjectExplorerContext): IContextKeyService {
|
||||
let scopedContextService = this._contextKeyService.createScoped();
|
||||
let connectionContextKey = new ConnectionContextKey(scopedContextService, this._queryManagementService);
|
||||
let connectionProfile = context && context.profile;
|
||||
connectionContextKey.set(connectionProfile);
|
||||
let serverInfoContextKey = new ServerInfoContextKey(scopedContextService);
|
||||
if (connectionProfile.id) {
|
||||
let serverInfo = this._connectionManagementService.getServerInfo(connectionProfile.id);
|
||||
serverInfoContextKey.set(serverInfo);
|
||||
}
|
||||
let treeNodeContextKey = new TreeNodeContextKey(scopedContextService);
|
||||
if (context.treeNode) {
|
||||
treeNodeContextKey.set(context.treeNode);
|
||||
}
|
||||
return scopedContextService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for connection group elements
|
||||
*/
|
||||
public getConnectionProfileGroupActions(tree: ITree, element: ConnectionProfileGroup): IAction[] {
|
||||
return [
|
||||
this._instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL),
|
||||
this._instantiationService.createInstance(EditServerGroupAction, EditServerGroupAction.ID, EditServerGroupAction.LABEL, element),
|
||||
this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_GROUP_LABEL, element)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for OE elements
|
||||
*/
|
||||
private getObjectExplorerNodeActions(context: ObjectExplorerContext): IAction[] {
|
||||
return this.getAllActions(context, (context) => this.getBuiltInNodeActions(context));
|
||||
}
|
||||
|
||||
private getBuiltInNodeActions(context: ObjectExplorerContext): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
let treeNode = context.treeNode;
|
||||
if (TreeUpdateUtils.isDatabaseNode(treeNode)) {
|
||||
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
|
||||
} else {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
// Contribute refresh action for scriptable objects via contribution
|
||||
if (!this.isScriptableObject(context)) {
|
||||
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.treeNode || context.profile));
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private isScriptableObject(context: ObjectExplorerContext): boolean {
|
||||
if (context.treeNode) {
|
||||
if (find(NodeType.SCRIPTABLE_OBJECTS, x => x === context.treeNode.nodeTypeId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
interface ObjectExplorerContext {
|
||||
tree: ITree;
|
||||
profile: ConnectionProfile;
|
||||
treeNode?: TreeNode;
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITree, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import * as treedefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ServerTreeActionProvider } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeActionProvider';
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions';
|
||||
import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
|
||||
/**
|
||||
* Extends the tree controller to handle clicks on the tree elements
|
||||
*/
|
||||
export class ServerTreeController extends treedefaults.DefaultController {
|
||||
|
||||
constructor(
|
||||
private actionProvider: ServerTreeActionProvider,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService
|
||||
) {
|
||||
super({
|
||||
clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN,
|
||||
openMode: treedefaults.OpenMode.SINGLE_CLICK
|
||||
});
|
||||
}
|
||||
|
||||
public onClick(tree: ITree, element: any, event: IMouseEvent): boolean {
|
||||
return super.onClick(tree, element, event);
|
||||
}
|
||||
|
||||
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean {
|
||||
return super.onLeftClick(tree, element, event, origin);
|
||||
}
|
||||
|
||||
// Do not allow left / right to expand and collapse groups #7848
|
||||
protected onLeft(tree: ITree, event: IKeyboardEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onRight(tree: ITree, event: IKeyboardEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
|
||||
return super.onEnter(tree, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions in the context menu
|
||||
*/
|
||||
public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean {
|
||||
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
||||
return false;
|
||||
}
|
||||
// Check if clicked on some element
|
||||
if (element === tree.getInput()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
tree.setFocus(element);
|
||||
|
||||
let actionContext: any;
|
||||
if (element instanceof TreeNode) {
|
||||
let context = new ObjectExplorerActionsContext();
|
||||
context.nodeInfo = element.toNodeInfo();
|
||||
// Note: getting DB name before, but intentionally not using treeUpdateUtils.getConnectionProfile as it replaces
|
||||
// the connection ID with a new one. This breaks a number of internal tasks
|
||||
context.connectionProfile = element.getConnectionProfile().toIConnectionProfile();
|
||||
context.connectionProfile.databaseName = element.getDatabaseName();
|
||||
actionContext = context;
|
||||
} else if (element instanceof ConnectionProfile) {
|
||||
let context = new ObjectExplorerActionsContext();
|
||||
context.connectionProfile = element.toIConnectionProfile();
|
||||
context.isConnectionNode = true;
|
||||
actionContext = context;
|
||||
} else {
|
||||
// TODO: because the connection group is used as a context object and isn't serializable,
|
||||
// the Group-level context menu is not currently extensible
|
||||
actionContext = element;
|
||||
}
|
||||
|
||||
let anchor = { x: event.posx + 1, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => this.actionProvider.getActions(tree, element),
|
||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id),
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
tree.domFocus();
|
||||
}
|
||||
},
|
||||
getActionsContext: () => (actionContext)
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
|
||||
/**
|
||||
* Implements the DataSource(that returns a parent/children of an element) for the server tree
|
||||
*/
|
||||
export class ServerTreeDataSource implements IDataSource {
|
||||
|
||||
constructor(
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the given element.
|
||||
* No more than one element may use a given identifier.
|
||||
*/
|
||||
public getId(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfile
|
||||
|| element instanceof ConnectionProfileGroup
|
||||
|| element instanceof TreeNode) {
|
||||
return element.id;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean value indicating whether the element has children.
|
||||
*/
|
||||
public hasChildren(tree: ITree, element: any): boolean {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return true;
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return element.hasChildren();
|
||||
} else if (element instanceof TreeNode) {
|
||||
return !element.isAlwaysLeaf;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's children as an array in a promise.
|
||||
*/
|
||||
public async getChildren(tree: ITree, element: any): Promise<(ConnectionProfile | ConnectionProfileGroup | TreeNode)[]> {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return TreeUpdateUtils.getObjectExplorerNode(<ConnectionProfile>element, this._connectionManagementService, this._objectExplorerService);
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return (element as ConnectionProfileGroup).getChildren();
|
||||
} else if (element instanceof TreeNode) {
|
||||
let node = element;
|
||||
if (node.children) {
|
||||
return node.children;
|
||||
} else {
|
||||
try {
|
||||
return this._objectExplorerService.resolveTreeNodeChildren(node.getSession(), node);
|
||||
} catch (expandError) {
|
||||
await node.setExpandedState(TreeItemCollapsibleState.Collapsed);
|
||||
node.errorStateMessage = expandError;
|
||||
this.showError(expandError);
|
||||
// collapse node and refresh in case of error so remove tree cache
|
||||
setTimeout(() => {
|
||||
tree.collapse(element).then(() => tree.refresh(element));
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's parent in a promise.
|
||||
*/
|
||||
public getParent(tree: ITree, element: any): Promise<any> {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return Promise.resolve(element.getParent());
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return Promise.resolve(element.getParent());
|
||||
} else if (element instanceof TreeNode) {
|
||||
return Promise.resolve(TreeUpdateUtils.getObjectExplorerParent(element, this._connectionManagementService));
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
private showError(errorMessage: string) {
|
||||
if (this._errorMessageService) {
|
||||
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/media/objectTypes/objecttypes';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { badgeRenderer, iconRenderer } from 'sql/workbench/contrib/objectExplorer/browser/iconRenderer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export interface IConnectionTemplateData {
|
||||
root: HTMLElement;
|
||||
label: HTMLSpanElement;
|
||||
icon: HTMLElement;
|
||||
connectionProfile: ConnectionProfile;
|
||||
}
|
||||
|
||||
export interface IConnectionProfileGroupTemplateData {
|
||||
root: HTMLElement;
|
||||
name: HTMLSpanElement;
|
||||
inputBox: InputBox;
|
||||
}
|
||||
|
||||
export interface IObjectExplorerTemplateData {
|
||||
root: HTMLElement;
|
||||
label: HTMLSpanElement;
|
||||
icon: HTMLElement;
|
||||
treeNode: TreeNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the tree items.
|
||||
* Uses the dom template to render connection groups and connections.
|
||||
*/
|
||||
export class ServerTreeRenderer implements IRenderer {
|
||||
|
||||
public static CONNECTION_HEIGHT = 23;
|
||||
public static CONNECTION_GROUP_HEIGHT = 38;
|
||||
private static CONNECTION_TEMPLATE_ID = 'connectionProfile';
|
||||
private static CONNECTION_GROUP_TEMPLATE_ID = 'connectionProfileGroup';
|
||||
public static OBJECTEXPLORER_HEIGHT = 23;
|
||||
private static OBJECTEXPLORER_TEMPLATE_ID = 'objectExplorer';
|
||||
/**
|
||||
* _isCompact is used to render connections tiles with and without the action buttons.
|
||||
* When set to true, like in the connection dialog recent connections tree, the connection
|
||||
* tile is rendered without the action buttons( such as connect, new query).
|
||||
*/
|
||||
private _isCompact: boolean = false;
|
||||
|
||||
constructor(
|
||||
isCompact: boolean,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
// isCompact defaults to false unless explicitly set by instantiation call.
|
||||
if (isCompact) {
|
||||
this._isCompact = isCompact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's height in the tree, in pixels.
|
||||
*/
|
||||
public getHeight(tree: ITree, element: any): number {
|
||||
if (element instanceof ConnectionProfileGroup) {
|
||||
return ServerTreeRenderer.CONNECTION_GROUP_HEIGHT;
|
||||
} else if (element instanceof ConnectionProfile) {
|
||||
return ServerTreeRenderer.CONNECTION_HEIGHT;
|
||||
}
|
||||
return ServerTreeRenderer.OBJECTEXPLORER_HEIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a template ID for a given element.
|
||||
*/
|
||||
public getTemplateId(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfileGroup) {
|
||||
return ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID;
|
||||
} else if (element instanceof ConnectionProfile) {
|
||||
return ServerTreeRenderer.CONNECTION_TEMPLATE_ID;
|
||||
}
|
||||
return ServerTreeRenderer.OBJECTEXPLORER_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render template in a dom element based on template id
|
||||
*/
|
||||
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
|
||||
|
||||
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
|
||||
const connectionTemplate: IObjectExplorerTemplateData = Object.create(null);
|
||||
connectionTemplate.root = dom.append(container, dom.$('.connection-tile'));
|
||||
connectionTemplate.icon = dom.append(connectionTemplate.root, dom.$('div.icon server-page'));
|
||||
connectionTemplate.label = dom.append(connectionTemplate.root, dom.$('div.label'));
|
||||
return connectionTemplate;
|
||||
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
|
||||
container.classList.add('server-group');
|
||||
const groupTemplate: IConnectionProfileGroupTemplateData = Object.create(null);
|
||||
groupTemplate.root = dom.append(container, dom.$('.server-group'));
|
||||
groupTemplate.name = dom.append(groupTemplate.root, dom.$('span.name'));
|
||||
return groupTemplate;
|
||||
} else {
|
||||
const objectExplorerTemplate: IObjectExplorerTemplateData = Object.create(null);
|
||||
objectExplorerTemplate.root = dom.append(container, dom.$('.object-element-group'));
|
||||
objectExplorerTemplate.icon = dom.append(objectExplorerTemplate.root, dom.$('div.object-icon'));
|
||||
objectExplorerTemplate.label = dom.append(objectExplorerTemplate.root, dom.$('div.label'));
|
||||
return objectExplorerTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a element, given an object bag returned by the template
|
||||
*/
|
||||
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
|
||||
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
|
||||
this.renderConnection(element, templateData);
|
||||
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
|
||||
this.renderConnectionProfileGroup(element, templateData);
|
||||
} else {
|
||||
this.renderObjectExplorer(element, templateData);
|
||||
}
|
||||
}
|
||||
|
||||
private renderObjectExplorer(treeNode: TreeNode, templateData: IObjectExplorerTemplateData): void {
|
||||
// Use an explicitly defined iconType first. If not defined, fall back to using nodeType and
|
||||
// other compount indicators instead.
|
||||
let iconName: string = undefined;
|
||||
if (treeNode.iconType) {
|
||||
iconName = (typeof treeNode.iconType === 'string') ? treeNode.iconType : treeNode.iconType.id;
|
||||
} else {
|
||||
iconName = treeNode.nodeTypeId;
|
||||
if (treeNode.nodeStatus) {
|
||||
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeStatus;
|
||||
}
|
||||
if (treeNode.nodeSubType) {
|
||||
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeSubType;
|
||||
}
|
||||
}
|
||||
|
||||
let tokens: string[] = [];
|
||||
for (let index = 1; index < templateData.icon.classList.length; index++) {
|
||||
tokens.push(templateData.icon.classList.item(index));
|
||||
}
|
||||
templateData.icon.classList.remove(...tokens);
|
||||
templateData.icon.classList.add('icon');
|
||||
let iconLowerCaseName = iconName.toLocaleLowerCase();
|
||||
templateData.icon.classList.add(iconLowerCaseName);
|
||||
|
||||
if (treeNode.iconPath) {
|
||||
iconRenderer.putIcon(templateData.icon, treeNode.iconPath);
|
||||
}
|
||||
|
||||
templateData.label.textContent = treeNode.label;
|
||||
templateData.root.title = treeNode.label;
|
||||
}
|
||||
|
||||
private getIconPath(connection: ConnectionProfile): IconPath {
|
||||
if (!connection) { return undefined; }
|
||||
|
||||
if (connection['iconPath']) {
|
||||
return connection['iconPath'];
|
||||
}
|
||||
|
||||
let iconId = this._connectionManagementService.getConnectionIconId(connection.id);
|
||||
if (!iconId) { return undefined; }
|
||||
|
||||
let providerProperties = this._connectionManagementService.getProviderProperties(connection.providerName);
|
||||
if (!providerProperties) { return undefined; }
|
||||
|
||||
let iconPath: IconPath = undefined;
|
||||
let pathConfig: URI | IconPath | { id: string, path: IconPath }[] = providerProperties['iconPath'];
|
||||
if (Array.isArray(pathConfig)) {
|
||||
for (const e of pathConfig) {
|
||||
if (!e.id || e.id === iconId) {
|
||||
iconPath = e.path;
|
||||
connection['iconPath'] = iconPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (pathConfig['light']) {
|
||||
iconPath = pathConfig as IconPath;
|
||||
connection['iconPath'] = iconPath;
|
||||
} else {
|
||||
let singlePath = pathConfig as URI;
|
||||
iconPath = { light: singlePath, dark: singlePath };
|
||||
connection['iconPath'] = iconPath;
|
||||
}
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
private renderServerIcon(element: HTMLElement, iconPath: IconPath, isConnected: boolean): void {
|
||||
if (!element) { return; }
|
||||
if (iconPath) {
|
||||
iconRenderer.putIcon(element, iconPath);
|
||||
}
|
||||
let badgeToRemove: string = isConnected ? badgeRenderer.serverDisconnected : badgeRenderer.serverConnected;
|
||||
let badgeToAdd: string = isConnected ? badgeRenderer.serverConnected : badgeRenderer.serverDisconnected;
|
||||
badgeRenderer.removeBadge(element, badgeToRemove);
|
||||
badgeRenderer.addBadge(element, badgeToAdd);
|
||||
}
|
||||
|
||||
private renderConnection(connection: ConnectionProfile, templateData: IConnectionTemplateData): void {
|
||||
if (!this._isCompact) {
|
||||
let iconPath: IconPath = this.getIconPath(connection);
|
||||
if (this._connectionManagementService.isConnected(undefined, connection)) {
|
||||
templateData.icon.classList.remove('disconnected');
|
||||
templateData.icon.classList.add('connected');
|
||||
this.renderServerIcon(templateData.icon, iconPath, true);
|
||||
} else {
|
||||
templateData.icon.classList.remove('connected');
|
||||
templateData.icon.classList.add('disconnected');
|
||||
this.renderServerIcon(templateData.icon, iconPath, false);
|
||||
}
|
||||
}
|
||||
|
||||
let label = connection.title;
|
||||
if (!connection.isConnectionOptionsValid) {
|
||||
label = localize('loading', "Loading...");
|
||||
}
|
||||
|
||||
templateData.label.textContent = label;
|
||||
templateData.root.title = connection.serverInfo;
|
||||
templateData.connectionProfile = connection;
|
||||
}
|
||||
|
||||
private renderConnectionProfileGroup(connectionProfileGroup: ConnectionProfileGroup, templateData: IConnectionProfileGroupTemplateData): void {
|
||||
|
||||
let rowElement = this.findParentElement(templateData.root, 'monaco-tree-row');
|
||||
if (rowElement) {
|
||||
if (connectionProfileGroup.color) {
|
||||
rowElement.style.background = connectionProfileGroup.color;
|
||||
} else {
|
||||
// If the group doesn't contain specific color, assign the default color
|
||||
rowElement.style.background = '#515151';
|
||||
}
|
||||
}
|
||||
if (connectionProfileGroup.description && (connectionProfileGroup.description !== '')) {
|
||||
templateData.root.title = connectionProfileGroup.description;
|
||||
}
|
||||
templateData.name.hidden = false;
|
||||
templateData.name.textContent = connectionProfileGroup.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first parent which contains the className
|
||||
*/
|
||||
private findParentElement(container: HTMLElement, className: string): HTMLElement {
|
||||
let currentElement = container;
|
||||
while (currentElement) {
|
||||
if (currentElement.className.indexOf(className) > -1) {
|
||||
break;
|
||||
}
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
return currentElement;
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
|
||||
// no op
|
||||
// InputBox disposed in wrapUp
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface IconPath {
|
||||
light: URI;
|
||||
dark: URI;
|
||||
}
|
||||
@@ -19,28 +19,29 @@ import { append, $, hide, show } from 'vs/base/browser/dom';
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import * as ConnectionUtils from 'sql/platform/connection/common/utils';
|
||||
import { ActiveConnectionsFilterAction } from 'sql/workbench/contrib/objectExplorer/browser/connectionTreeAction';
|
||||
import { ActiveConnectionsFilterAction } from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { TreeCreationUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeCreationUtils';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils';
|
||||
import { TreeSelectionHandler } from 'sql/workbench/contrib/objectExplorer/browser/treeSelectionHandler';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { TreeCreationUtils } from 'sql/workbench/services/objectExplorer/browser/treeCreationUtils';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
|
||||
import { TreeSelectionHandler } from 'sql/workbench/services/objectExplorer/browser/treeSelectionHandler';
|
||||
import { IObjectExplorerService, IServerTreeView } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import { SERVER_GROUP_CONFIG, SERVER_GROUP_AUTOEXPAND_CONFIG } from 'sql/workbench/contrib/objectExplorer/common/serverGroup.contribution';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/workbench/services/objectExplorer/common/treeNode';
|
||||
import { SERVER_GROUP_AUTOEXPAND_CONFIG } from 'sql/workbench/contrib/objectExplorer/common/serverGroup.contribution';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { ServerTreeActionProvider } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeActionProvider';
|
||||
import { ServerTreeActionProvider } from 'sql/workbench/services/objectExplorer/browser/serverTreeActionProvider';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { isHidden } from 'sql/base/browser/dom';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { SERVER_GROUP_CONFIG } from 'sql/workbench/services/serverGroup/common/interfaces';
|
||||
|
||||
/**
|
||||
* ServerTreeview implements the dynamic tree view.
|
||||
*/
|
||||
export class ServerTreeView extends Disposable {
|
||||
export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
|
||||
public messages: HTMLElement;
|
||||
private _buttonSection: HTMLElement;
|
||||
@@ -121,7 +122,7 @@ export class ServerTreeView extends Disposable {
|
||||
/**
|
||||
* Render the view body
|
||||
*/
|
||||
public renderBody(container: HTMLElement): Thenable<void> {
|
||||
public renderBody(container: HTMLElement): Promise<void> {
|
||||
// Add div to display no connections found message and hide it by default
|
||||
this.messages = append(container, $('.title'));
|
||||
const messageText = append(this.messages, $('span'));
|
||||
@@ -267,7 +268,7 @@ export class ServerTreeView extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public deleteObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): Thenable<void> {
|
||||
public deleteObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): Promise<void> {
|
||||
if (connection) {
|
||||
const conn = this.getConnectionInTreeInput(connection.id);
|
||||
if (conn) {
|
||||
@@ -286,7 +287,7 @@ export class ServerTreeView extends Disposable {
|
||||
return TreeUpdateUtils.registeredServerUpdate(this._tree, this._connectionManagementService);
|
||||
}
|
||||
|
||||
public refreshElement(element: any): Thenable<void> {
|
||||
public refreshElement(element: any): Promise<void> {
|
||||
return this._tree.refresh(element);
|
||||
}
|
||||
|
||||
@@ -474,7 +475,7 @@ export class ServerTreeView extends Disposable {
|
||||
/**
|
||||
* Set whether the given element is expanded or collapsed
|
||||
*/
|
||||
public setExpandedState(element: TreeNode | ConnectionProfile, expandedState: TreeItemCollapsibleState): Thenable<void> {
|
||||
public setExpandedState(element: TreeNode | ConnectionProfile, expandedState: TreeItemCollapsibleState): Promise<void> {
|
||||
if (expandedState === TreeItemCollapsibleState.Collapsed) {
|
||||
return this._tree.collapse(element);
|
||||
} else if (expandedState === TreeItemCollapsibleState.Expanded) {
|
||||
@@ -486,14 +487,14 @@ export class ServerTreeView extends Disposable {
|
||||
/**
|
||||
* Reveal the given element in the tree
|
||||
*/
|
||||
public reveal(element: TreeNode | ConnectionProfile): Thenable<void> {
|
||||
public reveal(element: TreeNode | ConnectionProfile): Promise<void> {
|
||||
return this._tree.reveal(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the given element in the tree and clear any other selections
|
||||
*/
|
||||
public setSelected(element: TreeNode | ConnectionProfile, selected: boolean, clearOtherSelections: boolean): Thenable<void> {
|
||||
public setSelected(element: TreeNode | ConnectionProfile, selected: boolean, clearOtherSelections: boolean): Promise<void> {
|
||||
if (clearOtherSelections || (selected && clearOtherSelections !== false)) {
|
||||
this._tree.clearSelection();
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/serverTreeActions';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServerTreeRenderer } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeRenderer';
|
||||
import { ServerTreeDataSource } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeDataSource';
|
||||
import { ServerTreeController } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeController';
|
||||
import { ServerTreeActionProvider } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeActionProvider';
|
||||
import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IController } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ServerTreeDragAndDrop, RecentConnectionsDragAndDrop } from 'sql/workbench/contrib/objectExplorer/browser/dragAndDropController';
|
||||
import { RecentConnectionDataSource } from 'sql/workbench/contrib/objectExplorer/browser/recentConnectionDataSource';
|
||||
|
||||
export class TreeCreationUtils {
|
||||
/**
|
||||
* Create a Recent Connections tree
|
||||
*/
|
||||
public static createConnectionTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, useController?: IController): Tree {
|
||||
const dataSource = instantiationService.createInstance(RecentConnectionDataSource);
|
||||
const renderer = instantiationService.createInstance(ServerTreeRenderer, true);
|
||||
const controller = useController ? useController : new DefaultController();
|
||||
const dnd = instantiationService.createInstance(RecentConnectionsDragAndDrop);
|
||||
const filter = new DefaultFilter();
|
||||
const sorter = undefined;
|
||||
const accessibilityProvider = new DefaultAccessibilityProvider();
|
||||
|
||||
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
|
||||
{
|
||||
indentPixels: 0,
|
||||
twistiePixels: 0,
|
||||
ariaLabel: nls.localize('treeAriaLabel', "Recent Connections")
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Servers viewlet tree
|
||||
*/
|
||||
public static createRegisteredServersTree(treeContainer: HTMLElement, instantiationService: IInstantiationService): Tree {
|
||||
|
||||
const dataSource = instantiationService.createInstance(ServerTreeDataSource);
|
||||
const actionProvider = instantiationService.createInstance(ServerTreeActionProvider);
|
||||
const renderer = instantiationService.createInstance(ServerTreeRenderer, false);
|
||||
const controller = instantiationService.createInstance(ServerTreeController, actionProvider);
|
||||
const dnd = instantiationService.createInstance(ServerTreeDragAndDrop);
|
||||
const filter = new DefaultFilter();
|
||||
const sorter = undefined;
|
||||
const accessibilityProvider = new DefaultAccessibilityProvider();
|
||||
|
||||
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
|
||||
{
|
||||
indentPixels: 10,
|
||||
twistiePixels: 20,
|
||||
ariaLabel: nls.localize('treeCreation.regTreeAriaLabel', "Servers")
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConnectionManagementService, IConnectionCompletionOptions } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
|
||||
// import { IProgressRunner, IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils';
|
||||
|
||||
export class TreeSelectionHandler {
|
||||
// progressRunner: IProgressRunner;
|
||||
|
||||
private _lastClicked: any[];
|
||||
private _clickTimer: any = undefined;
|
||||
private _otherTimer: any = undefined;
|
||||
|
||||
// constructor(@IProgressService private _progressService: IProgressService) {
|
||||
|
||||
// }
|
||||
|
||||
public onTreeActionStateChange(started: boolean): void {
|
||||
// if (this.progressRunner) {
|
||||
// this.progressRunner.done();
|
||||
// }
|
||||
|
||||
// if (started) {
|
||||
// this.progressRunner = this._progressService.show(true);
|
||||
// } else {
|
||||
// this.progressRunner = null;
|
||||
// }
|
||||
}
|
||||
|
||||
private isMouseEvent(event: any): boolean {
|
||||
return event && event.payload && event.payload.origin === 'mouse';
|
||||
}
|
||||
|
||||
private isKeyboardEvent(event: any): boolean {
|
||||
return event && event.payload && event.payload.origin === 'keyboard';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle select ion of tree element
|
||||
*/
|
||||
public onTreeSelect(event: any, tree: ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
|
||||
let sendSelectionEvent = ((event: any, selection: any, isDoubleClick: boolean, userInteraction: boolean) => {
|
||||
// userInteraction: defensive - don't touch this something else is handling it.
|
||||
if (userInteraction === true && this._lastClicked && this._lastClicked[0] === selection[0]) {
|
||||
this._lastClicked = undefined;
|
||||
}
|
||||
if (!TreeUpdateUtils.isInDragAndDrop) {
|
||||
this.handleTreeItemSelected(connectionManagementService, objectExplorerService, isDoubleClick, this.isKeyboardEvent(event), selection, tree, connectionCompleteCallback);
|
||||
}
|
||||
});
|
||||
|
||||
let selection = tree.getSelection();
|
||||
|
||||
if (!selection || selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
let specificSelection = selection[0];
|
||||
|
||||
if (this.isMouseEvent(event) || this.isKeyboardEvent(event)) {
|
||||
if (this._lastClicked !== undefined) {
|
||||
clearTimeout(this._clickTimer);
|
||||
let lastSpecificClick = this._lastClicked[0];
|
||||
|
||||
if (lastSpecificClick === specificSelection) {
|
||||
sendSelectionEvent(event, selection, true, true);
|
||||
return;
|
||||
} else {
|
||||
sendSelectionEvent(event, this._lastClicked, false, true);
|
||||
}
|
||||
}
|
||||
this._lastClicked = selection;
|
||||
|
||||
this._clickTimer = setTimeout(() => {
|
||||
sendSelectionEvent(event, selection, false, true);
|
||||
}, 400);
|
||||
} else {
|
||||
clearTimeout(this._otherTimer);
|
||||
this._otherTimer = setTimeout(() => {
|
||||
sendSelectionEvent(event, selection, false, false);
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param connectionCompleteCallback A function that gets called after a connection is established due to the selection, if needed
|
||||
*/
|
||||
private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: ITree, connectionCompleteCallback: () => void): void {
|
||||
let connectionProfile: ConnectionProfile = undefined;
|
||||
let options: IConnectionCompletionOptions = {
|
||||
params: undefined,
|
||||
saveTheConnection: true,
|
||||
showConnectionDialogOnError: true,
|
||||
showFirewallRuleOnError: true,
|
||||
showDashboard: isDoubleClick // only show the dashboard if the action is double click
|
||||
};
|
||||
if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) {
|
||||
connectionProfile = <ConnectionProfile>selection[0];
|
||||
|
||||
if (connectionProfile) {
|
||||
this.onTreeActionStateChange(true);
|
||||
|
||||
TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, connectionManagementService, objectExplorerService, tree).then(sessionCreated => {
|
||||
if (!sessionCreated) {
|
||||
this.onTreeActionStateChange(false);
|
||||
}
|
||||
if (connectionCompleteCallback) {
|
||||
connectionCompleteCallback();
|
||||
}
|
||||
}, error => {
|
||||
this.onTreeActionStateChange(false);
|
||||
});
|
||||
}
|
||||
} else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) {
|
||||
let treeNode = selection[0];
|
||||
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
|
||||
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
|
||||
if (connectionProfile) {
|
||||
connectionManagementService.showDashboard(connectionProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isKeyboard) {
|
||||
tree.toggleExpansion(selection[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { IConnectionManagementService, IConnectionCompletionOptions, IConnectionCallbacks } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||
|
||||
import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IExpandableTree extends ITree {
|
||||
// {{SQL CARBON EDIT }} - add back deleted VS Code tree methods
|
||||
/**
|
||||
* Returns a list of the currently expanded elements.
|
||||
*/
|
||||
getExpandedElements(): any[];
|
||||
|
||||
/**
|
||||
* Returns a number between 0 and 1 representing how much the tree is scroll down. 0 means all the way
|
||||
* to the top; 1 means all the way down.
|
||||
*/
|
||||
getScrollPosition(): number;
|
||||
|
||||
/**
|
||||
* Sets the scroll position with a number between 0 and 1 representing how much the tree is scroll down. 0 means all the way
|
||||
* to the top; 1 means all the way down.
|
||||
*/
|
||||
setScrollPosition(pos: number): void;
|
||||
|
||||
/**
|
||||
* Returns the total height of the tree's content.
|
||||
*/
|
||||
getContentHeight(): number;
|
||||
// {{SQL CARBON EDIT }} - end block
|
||||
}
|
||||
|
||||
|
||||
export class TreeUpdateUtils {
|
||||
|
||||
public static isInDragAndDrop: boolean = false;
|
||||
|
||||
/**
|
||||
* Set input for the tree.
|
||||
*/
|
||||
public static structuralTreeUpdate(tree: ITree, viewKey: string, connectionManagementService: IConnectionManagementService, providers?: string[]): Promise<void> {
|
||||
// convert to old VS Code tree interface with expandable methods
|
||||
let expandableTree: IExpandableTree = <IExpandableTree>tree;
|
||||
|
||||
let selectedElement: any;
|
||||
let targetsToExpand: any[];
|
||||
if (tree) {
|
||||
let selection = tree.getSelection();
|
||||
if (selection && selection.length === 1) {
|
||||
selectedElement = <any>selection[0];
|
||||
}
|
||||
targetsToExpand = expandableTree.getExpandedElements();
|
||||
}
|
||||
let groups;
|
||||
let treeInput = new ConnectionProfileGroup('root', null, undefined, undefined, undefined);
|
||||
if (viewKey === 'recent') {
|
||||
groups = connectionManagementService.getRecentConnections(providers);
|
||||
treeInput.addConnections(groups);
|
||||
} else if (viewKey === 'active') {
|
||||
groups = connectionManagementService.getActiveConnections(providers);
|
||||
treeInput.addConnections(groups);
|
||||
} else if (viewKey === 'saved') {
|
||||
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService, providers);
|
||||
}
|
||||
const previousTreeInput: any = tree.getInput();
|
||||
return tree.setInput(treeInput).then(async () => {
|
||||
if (previousTreeInput instanceof Disposable) {
|
||||
previousTreeInput.dispose();
|
||||
}
|
||||
// Make sure to expand all folders that where expanded in the previous session
|
||||
if (targetsToExpand) {
|
||||
await tree.expandAll(targetsToExpand);
|
||||
}
|
||||
if (selectedElement) {
|
||||
tree.select(selectedElement);
|
||||
}
|
||||
tree.getFocus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set input for the registered servers tree.
|
||||
*/
|
||||
public static registeredServerUpdate(tree: ITree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise<void> {
|
||||
// convert to old VS Code tree interface with expandable methods
|
||||
let expandableTree: IExpandableTree = <IExpandableTree>tree;
|
||||
|
||||
let selectedElement: any = elementToSelect;
|
||||
let targetsToExpand: any[];
|
||||
|
||||
// Focus
|
||||
tree.domFocus();
|
||||
|
||||
if (tree) {
|
||||
let selection = tree.getSelection();
|
||||
if (!selectedElement) {
|
||||
if (selection && selection.length === 1) {
|
||||
selectedElement = <any>selection[0];
|
||||
}
|
||||
}
|
||||
targetsToExpand = expandableTree.getExpandedElements();
|
||||
if (selectedElement && targetsToExpand.indexOf(selectedElement) === -1) {
|
||||
targetsToExpand.push(selectedElement);
|
||||
}
|
||||
}
|
||||
|
||||
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
if (treeInput) {
|
||||
if (treeInput !== tree.getInput()) {
|
||||
return tree.setInput(treeInput).then(async () => {
|
||||
// Make sure to expand all folders that where expanded in the previous session
|
||||
if (targetsToExpand) {
|
||||
await tree.expandAll(targetsToExpand);
|
||||
}
|
||||
if (selectedElement) {
|
||||
tree.select(selectedElement);
|
||||
}
|
||||
tree.getFocus();
|
||||
}, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup {
|
||||
|
||||
let groups = connectionManagementService.getConnectionGroups(providers);
|
||||
if (groups && groups.length > 0) {
|
||||
let treeInput = groups[0];
|
||||
treeInput.name = 'root';
|
||||
groups.forEach(cpg => cpg.dispose());
|
||||
return treeInput;
|
||||
}
|
||||
// Should never get to this case.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static hasObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService): boolean {
|
||||
let isConnected = connectionManagementService.isConnected(undefined, connection);
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
public static async connectIfNotConnected(
|
||||
connection: IConnectionProfile,
|
||||
options: IConnectionCompletionOptions,
|
||||
connectionManagementService: IConnectionManagementService,
|
||||
tree: ITree): Promise<ConnectionProfile | undefined> {
|
||||
if (!connectionManagementService.isProfileConnected(connection)) {
|
||||
// don't try to reconnect if currently connecting
|
||||
if (connectionManagementService.isProfileConnecting(connection)) {
|
||||
return undefined;
|
||||
|
||||
// else if we aren't connected or connecting then try to connect
|
||||
} else {
|
||||
let callbacks: IConnectionCallbacks = undefined;
|
||||
if (tree) {
|
||||
// Show the spinner in OE by adding the 'loading' trait to the connection, and set up callbacks to hide the spinner
|
||||
tree.addTraits('loading', [connection]);
|
||||
let rejectOrCancelCallback = () => {
|
||||
tree.collapse(connection);
|
||||
tree.removeTraits('loading', [connection]);
|
||||
};
|
||||
callbacks = {
|
||||
onConnectStart: undefined,
|
||||
onConnectReject: rejectOrCancelCallback,
|
||||
onConnectSuccess: () => tree.removeTraits('loading', [connection]),
|
||||
onDisconnect: undefined,
|
||||
onConnectCanceled: rejectOrCancelCallback,
|
||||
};
|
||||
}
|
||||
const result = await connectionManagementService.connect(connection, undefined, options, callbacks);
|
||||
if (result.connected) {
|
||||
let existingConnection = connectionManagementService.findExistingConnection(connection);
|
||||
return existingConnection;
|
||||
} else {
|
||||
throw new Error('connection failed');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let existingConnection = connectionManagementService.findExistingConnection(connection);
|
||||
if (options && options.showDashboard) {
|
||||
await connectionManagementService.showDashboard(connection);
|
||||
}
|
||||
return existingConnection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a connection if the not already connected and try to create new object explorer session
|
||||
* I the profile is already connected, tries to do the action requested in the options (e.g. open dashboard)
|
||||
* Returns true if new object explorer session created for the connection, otherwise returns false
|
||||
* @param connection Connection Profile
|
||||
* @param options Includes the actions to happened after connection is made
|
||||
* @param connectionManagementService Connection management service instance
|
||||
* @param objectExplorerService Object explorer service instance
|
||||
*/
|
||||
public static async connectAndCreateOeSession(connection: IConnectionProfile, options: IConnectionCompletionOptions,
|
||||
connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): Promise<boolean> {
|
||||
const connectedConnection = await TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree);
|
||||
if (connectedConnection) {
|
||||
// append group ID and original display name to build unique OE session ID
|
||||
connectedConnection.options['groupId'] = connection.groupId;
|
||||
connectedConnection.options['databaseDisplayName'] = connection.databaseName;
|
||||
|
||||
let rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
|
||||
if (!rootNode) {
|
||||
await objectExplorerService.updateObjectExplorerNodes(connectedConnection);
|
||||
rootNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
|
||||
return true;
|
||||
// The oe request is sent. an event will be raised when the session is created
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static getObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService): Promise<TreeNode[]> {
|
||||
return new Promise<TreeNode[]>((resolve, reject) => {
|
||||
if (connection.isDisconnecting) {
|
||||
resolve([]);
|
||||
} else {
|
||||
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
|
||||
if (rootNode) {
|
||||
objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
|
||||
resolve(rootNode.children);
|
||||
}, expandError => {
|
||||
resolve([]);
|
||||
});
|
||||
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getObjectExplorerParent(objectExplorerNode: TreeNode, connectionManagementService: IConnectionManagementService): any {
|
||||
if (objectExplorerNode && objectExplorerNode.parent) {
|
||||
// if object explorer node's parent is root, return connection profile
|
||||
if (!objectExplorerNode.parent.parent) {
|
||||
let connectionId = objectExplorerNode.getConnectionProfile().id;
|
||||
|
||||
// get connection profile from connection profile groups
|
||||
let root = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
let connections = ConnectionProfileGroup.getConnectionsInGroup(root);
|
||||
let results = connections.filter(con => {
|
||||
if (connectionId === con.id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (results && results.length > 0) {
|
||||
return results[0];
|
||||
}
|
||||
} else {
|
||||
return objectExplorerNode.parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param treeNode Returns true if the tree node is a database node
|
||||
*/
|
||||
public static isDatabaseNode(treeNode: TreeNode): boolean {
|
||||
return treeNode && treeNode.nodeTypeId === NodeType.Database;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param treeNode Returns true if the tree node is an available database node
|
||||
*/
|
||||
public static isAvailableDatabaseNode(treeNode: TreeNode): boolean {
|
||||
return treeNode && treeNode.nodeTypeId === NodeType.Database && treeNode.nodeStatus !== 'Unavailable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection profile with the current database
|
||||
*/
|
||||
public static getConnectionProfile(treeNode: TreeNode): ConnectionProfile {
|
||||
let connectionProfile = treeNode.getConnectionProfile();
|
||||
let databaseName = treeNode.getDatabaseName();
|
||||
if (databaseName !== undefined && connectionProfile.databaseName !== databaseName) {
|
||||
connectionProfile = connectionProfile.cloneWithDatabase(databaseName);
|
||||
}
|
||||
return connectionProfile;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user