add data explorer default (#4653)

This commit is contained in:
Anthony Dresser
2019-03-21 16:10:54 -07:00
committed by GitHub
parent b00352570b
commit 24b5f41065
11 changed files with 101 additions and 220 deletions

View File

@@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* 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 { 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 'sql/base/browser/builder';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IAction } from 'vs/base/common/actions';
import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
import { ClearSearchAction, ActiveConnectionsFilterAction,
AddServerAction, AddServerGroupAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
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 - 46); // account for search box and horizontal scroll bar
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();
this._serverTreeView.tree.domFocus();
}
}

View File

@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* 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, ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { Registry } from 'vs/platform/registry/common/platform';
import { VIEWLET_ID } from 'sql/workbench/parts/dataExplorer/browser/dataExplorerExtensionPoint';
import { DataExplorerViewlet, DataExplorerViewletViewsContribution } from 'sql/workbench/parts/dataExplorer/browser/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 { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
// Viewlet Action
export class OpenDataExplorerViewletAction extends ShowViewletAction {
public static ID = VIEWLET_ID;
public static LABEL = localize('showDataExplorer', "Show Data Explorer");
constructor(
id: string,
label: string,
@IViewletService viewletService: IViewletService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService);
}
}
// Data Explorer Viewlet
const viewletDescriptor = new ViewletDescriptor(
DataExplorerViewlet,
VIEWLET_ID,
localize('workbench.dataExplorer', "Data Explorer"),
'dataExplorer',
0
);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(DataExplorerViewletViewsContribution, LifecyclePhase.Starting);
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
new SyncActionDescriptor(
OpenDataExplorerViewletAction,
OpenDataExplorerViewletAction.ID,
OpenDataExplorerViewletAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyCode.Shift | KeyCode.KEY_C }),
'View: Show Data Explorer',
localize('dataExplorer.view', "View")
);
let configurationRegistry = <IConfigurationRegistry>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
}
}
});

View File

@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* 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, ITreeViewDescriptor, IViewsRegistry } 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 } 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/viewsExtensionPoint';
import { CustomTreeView } from 'sql/workbench/browser/parts/views/customView';
export const DataExplorerViewlet = {
DataExplorer: 'dataExplorer'
};
export const VIEWLET_ID = 'workbench.view.dataExplorer';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(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[] }>({ extensionPoint: 'dataExplorer', deps: [viewsContainersExtensionPoint], jsonSchema: dataExplorerContribution });
class DataExplorerContainerExtensionHandler implements IWorkbenchContribution {
private viewContainersRegistry: IViewContainersRegistry;
constructor(
@IInstantiationService private instantiationService: IInstantiationService
) {
this.viewContainersRegistry = Registry.as<IViewContainersRegistry>(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 = Registry.as<IViewsRegistry>(ViewContainerExtensions.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 = <ITreeViewDescriptor>{
id: item.id,
name: item.name,
ctorDescriptor: { ctor: CustomTreeViewPanel },
when: ContextKeyExpr.deserialize(item.when),
canToggleVisibility: true,
collapsed: this.showCollapsed(container),
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, container)
};
viewIds.push(viewDescriptor.id);
return viewDescriptor;
}));
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews(viewDescriptors, container);
});
}
});
}
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<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(DataExplorerContainerExtensionHandler, LifecyclePhase.Starting);

View File

@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* 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 { 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 { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
import { VIEWLET_ID, VIEW_CONTAINER } from 'sql/workbench/parts/dataExplorer/browser/dataExplorerExtensionPoint';
import { ConnectionViewletPanel } from 'sql/workbench/parts/dataExplorer/browser/connectionViewletPanel';
import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry } from 'vs/workbench/common/views';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { Registry } from 'vs/platform/registry/common/platform';
export class DataExplorerViewletViewsContribution implements IWorkbenchContribution {
constructor() {
if (process.env.NODE_ENV === 'development') {
this.registerViews();
}
}
private registerViews(): void {
let viewDescriptors = [];
viewDescriptors.push(this.createObjectExplorerViewDescriptor());
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews(viewDescriptors, VIEW_CONTAINER);
}
private createObjectExplorerViewDescriptor(): IViewDescriptor {
return {
id: 'dataExplorer.servers',
name: localize('dataExplorer.servers', "Servers"),
ctorDescriptor: { 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(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@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,
@IConfigurationService configurationService: IConfigurationService
) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables));
}
create(parent: HTMLElement): void {
addClass(parent, 'dataExplorer-viewlet');
this.root = parent;
this.dataSourcesBox = append(this.root, $('.dataSources'));
return super.create(this.dataSourcesBox);
}
public updateStyles(): void {
super.updateStyles();
}
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);
Promise.all(addedViews);
return addedViews;
}
protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel {
return this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.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();
}
}