From 88c33214c68dd16ed8f84f04b432f2f5b47897e6 Mon Sep 17 00:00:00 2001 From: Aditya Bist Date: Thu, 7 Feb 2019 17:18:05 -0800 Subject: [PATCH] Dataexplorer viewlet (#3967) * added initial data explorer viewlet * added dataexplorer contribution point * removed test view * remove unused imports * added flag and tests * CR comments * added icon for data explorer --- extensions/azurecore/package.json | 8 + src/sql/media/actionBarLabel.css | 6 + .../common/dataExplorer.contribution.ts | 74 ++++++++ .../common/dataExplorerExtensionPoint.ts | 166 +++++++++++++++++ .../connectionViewletPanel.ts | 173 ++++++++++++++++++ .../viewlet/dataExplorerViewlet.css | 0 .../viewlet/dataExplorerViewlet.ts | 146 +++++++++++++++ .../common/registeredServer.contribution.ts | 79 ++++---- .../viewlet/connectionTreeAction.ts | 3 +- .../dataExplorer/dataExplorerViewlet.test.ts | 57 ++++++ .../api/browser/viewsExtensionPoint.ts | 2 +- src/vs/workbench/workbench.main.ts | 3 + 12 files changed, 677 insertions(+), 40 deletions(-) create mode 100644 src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts create mode 100644 src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts create mode 100644 src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts create mode 100644 src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.css create mode 100644 src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts create mode 100644 src/sqltest/parts/dataExplorer/dataExplorerViewlet.test.ts diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index a18be62e36..a62d3abc46 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -122,6 +122,14 @@ } ] }, + "dataExplorer": { + "azureResource": [ + { + "id": "azureResourceExplorer", + "name": "%azure.resource.explorer.title%" + } + ] + }, "menus": { "view/title": [ { diff --git a/src/sql/media/actionBarLabel.css b/src/sql/media/actionBarLabel.css index c121c26afc..37b50a4903 100644 --- a/src/sql/media/actionBarLabel.css +++ b/src/sql/media/actionBarLabel.css @@ -32,3 +32,9 @@ -webkit-mask: url('icons/search_inverse.svg') no-repeat 50% 50%; -webkit-mask-size: 25px 25px; } + +/* Activity Bar - Data Explorer */ +.monaco-workbench > .activitybar .monaco-action-bar .action-label.dataExplorer { + -webkit-mask: url('icons/server_page_inverse.svg') no-repeat 50% 50%; + -webkit-mask-size: 25px 25px; +} diff --git a/src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts b/src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts new file mode 100644 index 0000000000..5f3ab6c2b2 --- /dev/null +++ b/src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * 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/actionBarLabel'; +import { localize } from 'vs/nls'; +import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { VIEWLET_ID } from 'sql/parts/dataExplorer/common/dataExplorerExtensionPoint'; +import { DataExplorerViewlet, DataExplorerViewletViewsContribution } from 'sql/parts/dataExplorer/viewlet/dataExplorerViewlet'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { OpenConnectionsViewletAction } from 'sql/parts/objectExplorer/common/registeredServer.contribution'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; + +// Data Explorer Viewlet +const viewletDescriptor = new ViewletDescriptor( + DataExplorerViewlet, + VIEWLET_ID, + localize('workbench.dataExplorer', 'Data Explorer'), + 'dataExplorer', + 0 +); + +if (process.env.NODE_ENV === 'development') { + Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); + Registry.as(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID); + const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); + workbenchRegistry.registerWorkbenchContribution(DataExplorerViewletViewsContribution, LifecyclePhase.Starting); + const registry = Registry.as(ActionExtensions.WorkbenchActions); + registry.registerWorkbenchAction( + new SyncActionDescriptor( + OpenConnectionsViewletAction, + OpenConnectionsViewletAction.ID, + OpenConnectionsViewletAction.LABEL, + { primary: KeyMod.CtrlCmd | KeyCode.Shift | KeyCode.KEY_C }), + 'View: Show Servers', + localize('registeredServers.view', "View") + ); + + let configurationRegistry = Registry.as(Extensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': 'databaseConnections', + 'order': 0, + 'title': localize('databaseConnections', 'Database Connections'), + 'type': 'object', + 'properties': { + 'datasource.connections': { + 'description': localize('datasource.connections', 'data source connections'), + 'type': 'array' + }, + 'datasource.connectionGroups': { + 'description': localize('datasource.connectionGroups', 'data source groups'), + 'type': 'array' + } + } + }); + configurationRegistry.registerConfiguration({ + 'id': 'startupConfig', + 'title': localize('startupConfig', 'Startup Configuration'), + 'type': 'object', + 'properties': { + 'startup.alwaysShowServersView': { + 'type': 'boolean', + 'description': localize('startup.alwaysShowServersView', 'True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown'), + 'default': true + } + } + }); +} \ No newline at end of file diff --git a/src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts b/src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts new file mode 100644 index 0000000000..66d0b326f7 --- /dev/null +++ b/src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts @@ -0,0 +1,166 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { forEach } from 'vs/base/common/collections'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions, ICustomViewDescriptor, ViewsRegistry } from 'vs/workbench/common/views'; +import { IExtensionPoint, ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { CustomTreeViewPanel, CustomTreeViewer } from 'vs/workbench/browser/parts/views/customView'; +import { coalesce } from 'vs/base/common/arrays'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { viewsContainersExtensionPoint } from 'vs/workbench/api/browser/viewsContainersExtensionPoint'; + +export const DataExplorerViewlet = { + DataExplorer: 'dataExplorer' +}; +export const VIEWLET_ID = 'workbench.view.dataExplorer'; +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); + + + +interface IUserFriendlyViewDescriptor { + id: string; + name: string; + when?: string; +} + +const viewDescriptor: IJSONSchema = { + type: 'object', + properties: { + id: { + description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), + type: 'string' + }, + name: { + description: localize('vscode.extension.contributes.view.name', 'The human-readable name of the view. Will be shown'), + type: 'string' + }, + when: { + description: localize('vscode.extension.contributes.view.when', 'Condition which must be true to show this view'), + type: 'string' + }, + } +}; + +const dataExplorerContribution: IJSONSchema = { + description: localize('extension.contributes.dataExplorer', "Contributes views to the editor"), + type: 'object', + properties: { + 'dataExplorer': { + description: localize('extension.dataExplorer', "Contributes views to Data Explorer container in the Activity bar"), + type: 'array', + items: viewDescriptor, + default: [] + } + }, + additionalProperties: { + description: localize('dataExplorer.contributed', "Contributes views to contributed views container"), + type: 'array', + items: viewDescriptor, + default: [] + } +}; + + +const dataExplorerExtensionPoint: IExtensionPoint<{ [loc: string]: IUserFriendlyViewDescriptor[] }> = ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: IUserFriendlyViewDescriptor[] }>('dataExplorer', [viewsContainersExtensionPoint], dataExplorerContribution); + +class DataExplorerContainerExtensionHandler implements IWorkbenchContribution { + + private viewContainersRegistry: IViewContainersRegistry; + + constructor( + @IInstantiationService private instantiationService: IInstantiationService + ) { + this.viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + this.handleAndRegisterCustomViews(); + } + + private handleAndRegisterCustomViews() { + if (process.env.NODE_ENV !== 'development') { + return; + } + dataExplorerExtensionPoint.setHandler(extensions => { + for (let extension of extensions) { + const { value, collector } = extension; + + forEach(value, entry => { + if (!this.isValidViewDescriptors(entry.value, collector)) { + return; + } + + let container = this.viewContainersRegistry.get(VIEWLET_ID); + if (!container) { + collector.warn(localize('ViewsContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'.", entry.key)); + container = this.viewContainersRegistry.get(VIEWLET_ID); + } + const registeredViews = ViewsRegistry.getViews(container); + const viewIds = []; + const viewDescriptors = coalesce(entry.value.map(item => { + // validate + if (viewIds.indexOf(item.id) !== -1) { + collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}` in the view container `{1}`", item.id, container.id)); + return null; + } + if (registeredViews.some(v => v.id === item.id)) { + collector.error(localize('duplicateView2', "A view with id `{0}` is already registered in the view container `{1}`", item.id, container.id)); + return null; + } + + const viewDescriptor = { + id: item.id, + name: item.name, + ctor: CustomTreeViewPanel, + container, + when: ContextKeyExpr.deserialize(item.when), + canToggleVisibility: true, + collapsed: this.showCollapsed(container), + treeViewer: this.instantiationService.createInstance(CustomTreeViewer, item.id, container) + }; + + viewIds.push(viewDescriptor.id); + return viewDescriptor; + })); + ViewsRegistry.registerViews(viewDescriptors); + }); + } + }); + } + + private isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean { + if (!Array.isArray(viewDescriptors)) { + collector.error(localize('requirearray', "views must be an array")); + return false; + } + + for (let descriptor of viewDescriptors) { + if (typeof descriptor.id !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'id')); + return false; + } + if (typeof descriptor.name !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'name')); + return false; + } + if (descriptor.when && typeof descriptor.when !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); + return false; + } + } + + return true; + } + + private showCollapsed(container: ViewContainer): boolean { + return false; + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(DataExplorerContainerExtensionHandler, LifecyclePhase.Starting); \ No newline at end of file diff --git a/src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts b/src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts new file mode 100644 index 0000000000..c0f7527c5f --- /dev/null +++ b/src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import { localize } from 'vs/nls'; +import * as DOM from 'vs/base/browser/dom'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionTipsService, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { Builder } from 'vs/base/browser/builder'; +import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView'; +import { ClearSearchAction, ActiveConnectionsFilterAction, + AddServerAction, AddServerGroupAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction'; +import { IAction } from 'vs/base/common/actions'; +import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService'; + +export class ConnectionViewletPanel extends ViewletPanel { + + private _root: HTMLElement; + private _searchBox: InputBox; + private _toDisposeViewlet: IDisposable[] = []; + private _serverTreeView: ServerTreeView; + private _clearSearchAction: ClearSearchAction; + private _addServerAction: IAction; + private _addServerGroupAction: IAction; + private _activeConnectionsFilterAction: ActiveConnectionsFilterAction; + + constructor( + private options: IViewletViewOptions, + @INotificationService protected notificationService: INotificationService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService private themeService: IThemeService, + @IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionTipsService protected tipsService: IExtensionTipsService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IExtensionManagementServerService protected extensionManagementServerService: IExtensionManagementServerService, + @IObjectExplorerService private objectExplorerService: IObjectExplorerService + ) { + super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); + this._clearSearchAction = this.instantiationService.createInstance(ClearSearchAction, ClearSearchAction.ID, ClearSearchAction.LABEL, this); + this._addServerAction = this.instantiationService.createInstance(AddServerAction, + AddServerAction.ID, + AddServerAction.LABEL); + this._addServerGroupAction = this.instantiationService.createInstance(AddServerGroupAction, + AddServerGroupAction.ID, + AddServerGroupAction.LABEL); + this._serverTreeView = this.instantiationService.createInstance(ServerTreeView); + this._activeConnectionsFilterAction = this._serverTreeView.activeConnectionsFilterAction; + + this.objectExplorerService.registerServerTreeView(this._serverTreeView); + } + + protected renderHeader(container: HTMLElement): void { + super.renderHeader(container); + } + + renderHeaderTitle(container: HTMLElement): void { + super.renderHeaderTitle(container, this.options.title); + } + + renderBody(container: HTMLElement): void { + let parentBuilder = new Builder(container); + parentBuilder.div({ class: 'server-explorer-viewlet' }, (viewletContainer) => { + viewletContainer.div({ class: 'search-box' }, (searchBoxContainer) => { + let searchServerString = localize('Search server names', 'Search server names'); + this._searchBox = new InputBox( + searchBoxContainer.getHTMLElement(), + null, + { + placeholder: searchServerString, + actions: [this._clearSearchAction], + ariaLabel: searchServerString + } + ); + + this._searchBox.onDidChange(() => { + this.search(this._searchBox.value); + }); + + // Theme styler + this._toDisposeViewlet.push(attachInputBoxStyler(this._searchBox, this.themeService)); + + }); + viewletContainer.div({ Class: 'object-explorer-view' }, (viewContainer) => { + this._serverTreeView.renderBody(viewContainer.getHTMLElement()).then(() => { + Promise.resolve(null); + }, error => { + console.warn('render registered servers: ' + error); + Promise.resolve(null); + }); + }); + }); + this._root = container; + } + + layoutBody(size: number): void { + this._searchBox.layout(); + this._serverTreeView.layout(size); // account for search box + DOM.toggleClass(this._root, 'narrow', this._root.clientWidth < 300); + } + + show(): void { + } + + select(): void { + } + + showPrevious(): void { + } + + showPreviousPage(): void { + } + + showNext(): void { + } + + showNextPage(): void { + } + + count(): number { + return 0; + } + + public getActions(): IAction[] { + return [ + this._addServerAction, + this._addServerGroupAction, + this._activeConnectionsFilterAction + ]; + } + + public clearSearch() { + this._serverTreeView.refreshTree(); + this._searchBox.value = ''; + this._clearSearchAction.enabled = false; + this._searchBox.focus(); + } + + public search(value: string): void { + if (value) { + this._clearSearchAction.enabled = true; + this._serverTreeView.searchTree(value); + } else { + this.clearSearch(); + } + } + + dispose(): void { + this._serverTreeView.dispose(); + super.dispose(); + this.disposables = dispose(this.disposables); + } + + focus(): void { + super.focus(); + } +} diff --git a/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.css b/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts b/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts new file mode 100644 index 0000000000..ac54468077 --- /dev/null +++ b/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { localize } from 'vs/nls'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IAction } from 'vs/base/common/actions'; +import { IViewlet } from 'vs/workbench/common/viewlet'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; +import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { VIEWLET_ID, VIEW_CONTAINER } from 'sql/parts/dataExplorer/common/dataExplorerExtensionPoint'; +import { ConnectionViewletPanel } from 'sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel'; +import { Extensions as ViewContainerExtensions, ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views'; + +export class DataExplorerViewletViewsContribution implements IWorkbenchContribution { + + constructor() { + if (process.env.NODE_ENV === 'development') { + this.registerViews(); + } + } + + private registerViews(): void { + let viewDescriptors = []; + viewDescriptors.push(this.createObjectExplorerViewDescriptor()); + ViewsRegistry.registerViews(viewDescriptors); + } + + private createObjectExplorerViewDescriptor(): IViewDescriptor { + return { + id: 'dataExplorer.servers', + name: localize('dataExplorer.servers', "Servers"), + container: VIEW_CONTAINER, + ctor: ConnectionViewletPanel, + weight: 100, + canToggleVisibility: true, + order: 0 + }; + } +} + +export class DataExplorerViewlet extends ViewContainerViewlet { + private root: HTMLElement; + + private dataSourcesBox: HTMLElement; + private primaryActions: IAction[]; + private disposables: IDisposable[] = []; + + constructor( + @IPartService partService: IPartService, + @ITelemetryService telemetryService: ITelemetryService, + @IInstantiationService instantiationService: IInstantiationService, + @IViewletService private viewletService: IViewletService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService + ) { + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables)); + } + + create(parent: HTMLElement): TPromise { + addClass(parent, 'dataExplorer-viewlet'); + this.root = parent; + + this.dataSourcesBox = append(this.root, $('.dataSources')); + + return super.create(this.dataSourcesBox); + } + + public updateStyles(): void { + super.updateStyles(); + } + + setVisible(visible: boolean): TPromise { + const isVisibilityChanged = this.isVisible() !== visible; + return super.setVisible(visible).then(() => { + if (isVisibilityChanged) { + if (visible) { + } + } + }); + } + + focus(): void { + } + + layout(dimension: Dimension): void { + toggleClass(this.root, 'narrow', dimension.width <= 300); + super.layout(new Dimension(dimension.width, dimension.height)); + } + + getOptimalWidth(): number { + return 400; + } + + getActions(): IAction[] { + if (!this.primaryActions) { + this.primaryActions = []; + } + return []; + } + + getSecondaryActions(): IAction[] { + return []; + } + + protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { + const addedViews = super.onDidAddViews(added); + TPromise.join(addedViews); + return addedViews; + } + + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { + return this.instantiationService.createInstance(viewDescriptor.ctor, options) as ViewletPanel; + } + + private onViewletOpen(viewlet: IViewlet): void { + if (!viewlet || viewlet.getId() === VIEWLET_ID) { + return; + } + } + + dispose(): void { + this.disposables = dispose(this.disposables); + super.dispose(); + } +} \ No newline at end of file diff --git a/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts b/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts index f0df4c3282..c0318193f9 100644 --- a/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts +++ b/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts @@ -42,46 +42,49 @@ const viewletDescriptor = new ViewletDescriptor( 0 ); -Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); +if (process.env.NODE_ENV !== 'development') { + Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); -Registry.as(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID); + Registry.as(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID); -const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction( - new SyncActionDescriptor( - OpenConnectionsViewletAction, - OpenConnectionsViewletAction.ID, - OpenConnectionsViewletAction.LABEL, - { primary: KeyMod.CtrlCmd | KeyCode.Shift | KeyCode.KEY_C }), - 'View: Show Servers', - localize('registeredServers.view', "View") -); + const registry = Registry.as(ActionExtensions.WorkbenchActions); + registry.registerWorkbenchAction( + new SyncActionDescriptor( + OpenConnectionsViewletAction, + OpenConnectionsViewletAction.ID, + OpenConnectionsViewletAction.LABEL, + { primary: KeyMod.CtrlCmd | KeyCode.Shift | KeyCode.KEY_C }), + 'View: Show Servers', + localize('registeredServers.view', "View") + ); -let configurationRegistry = Registry.as(Extensions.Configuration); -configurationRegistry.registerConfiguration({ - 'id': 'databaseConnections', - 'title': localize('databaseConnections', 'Database Connections'), - 'type': 'object', - 'properties': { - 'datasource.connections': { - 'description': localize('datasource.connections', 'data source connections'), - 'type': 'array' - }, - 'datasource.connectionGroups': { - 'description': localize('datasource.connectionGroups', 'data source groups'), - 'type': 'array' + let configurationRegistry = Registry.as(Extensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': 'databaseConnections', + 'order': 0, + 'title': localize('databaseConnections', 'Database Connections'), + 'type': 'object', + 'properties': { + 'datasource.connections': { + 'description': localize('datasource.connections', 'data source connections'), + 'type': 'array' + }, + 'datasource.connectionGroups': { + 'description': localize('datasource.connectionGroups', 'data source groups'), + 'type': 'array' + } } - } -}); -configurationRegistry.registerConfiguration({ - 'id': 'startupConfig', - 'title': localize('startupConfig', 'Startup Configuration'), - 'type': 'object', - 'properties': { - 'startup.alwaysShowServersView': { - 'type': 'boolean', - 'description': localize('startup.alwaysShowServersView', 'True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown'), - 'default': true + }); + configurationRegistry.registerConfiguration({ + 'id': 'startupConfig', + 'title': localize('startupConfig', 'Startup Configuration'), + 'type': 'object', + 'properties': { + 'startup.alwaysShowServersView': { + 'type': 'boolean', + 'description': localize('startup.alwaysShowServersView', 'True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown'), + 'default': true + } } - } -}); + }); +} \ No newline at end of file diff --git a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts index 6b4be813a3..9c9b50c0fd 100644 --- a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts +++ b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts @@ -23,6 +23,7 @@ import Severity from 'vs/base/common/severity'; import { ObjectExplorerActionsContext } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; +import { ConnectionViewletPanel } from 'sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel'; export class RefreshAction extends Action { @@ -389,7 +390,7 @@ export class ClearSearchAction extends Action { constructor( id: string, label: string, - private _viewlet: ConnectionViewlet, + private _viewlet: ConnectionViewlet | ConnectionViewletPanel, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService ) { super(id, label); diff --git a/src/sqltest/parts/dataExplorer/dataExplorerViewlet.test.ts b/src/sqltest/parts/dataExplorer/dataExplorerViewlet.test.ts new file mode 100644 index 0000000000..bcfb811452 --- /dev/null +++ b/src/sqltest/parts/dataExplorer/dataExplorerViewlet.test.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import * as Platform from 'vs/platform/registry/common/platform'; +import { ViewletDescriptor, Extensions, Viewlet, ViewletRegistry } from 'vs/workbench/browser/viewlet'; +import * as Types from 'vs/base/common/types'; + +suite('Data Explorer Viewlet', () => { + + class DataExplorerTestViewlet extends Viewlet { + + constructor() { + super('dataExplorer', null, null, null); + } + + public layout(dimension: any): void { + throw new Error('Method not implemented.'); + } + } + + test('ViewletDescriptor API', function () { + let d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 1); + assert.strictEqual(d.id, 'id'); + assert.strictEqual(d.name, 'name'); + assert.strictEqual(d.cssClass, 'class'); + assert.strictEqual(d.order, 1); + }); + + test('Editor Aware ViewletDescriptor API', function () { + let d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 5); + assert.strictEqual(d.id, 'id'); + assert.strictEqual(d.name, 'name'); + + d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 5); + assert.strictEqual(d.id, 'id'); + assert.strictEqual(d.name, 'name'); + }); + + test('Data Explorer Viewlet extension point and registration', function () { + assert(Types.isFunction(Platform.Registry.as(Extensions.Viewlets).registerViewlet)); + assert(Types.isFunction(Platform.Registry.as(Extensions.Viewlets).getViewlet)); + assert(Types.isFunction(Platform.Registry.as(Extensions.Viewlets).getViewlets)); + + let oldCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length; + let d = new ViewletDescriptor(DataExplorerTestViewlet, 'dataExplorer-test-id', 'name'); + Platform.Registry.as(Extensions.Viewlets).registerViewlet(d); + let retrieved = Platform.Registry.as(Extensions.Viewlets).getViewlet('dataExplorer-test-id'); + assert(d === retrieved); + let newCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length; + assert.equal(oldCount + 1, newCount); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 586dacd445..6e19bafa4d 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -108,7 +108,7 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution { let container = this.getViewContainer(entry.key); if (!container) { - collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key)); + collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'.", entry.key)); container = this.viewContainersRegistry.get(EXPLORER); } const registeredViews = ViewsRegistry.getViews(container); diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 0265b969be..38a247056e 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -156,6 +156,9 @@ import 'vs/workbench/parts/experiments/electron-browser/experiments.contribution import 'sql/parts/taskHistory/common/taskHistory.contribution'; import 'sql/parts/taskHistory/viewlet/taskHistoryViewlet'; import 'sql/parts/tasks/common/tasks.contribution'; +import 'sql/parts/dataExplorer/common/dataExplorer.contribution'; +import 'sql/parts/dataExplorer/viewlet/dataExplorerViewlet'; +import 'sql/parts/dataExplorer/common/dataExplorerExtensionPoint'; import 'sql/parts/objectExplorer/common/registeredServer.contribution'; import 'sql/workbench/parts/connection/electron-browser/connectionViewlet'; import 'sql/workbench/api/node/sqlExtHost.contribution';