From 34dc6cbb29166c29bda387d71c5e400957f1ac7d Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 9 Jun 2020 16:36:30 +0300 Subject: [PATCH] feat(sql-editor): open new editor for current context CB-41 --- .../NavigationTreeNode/useNavigationTree.ts | 8 +- .../NavigationTree/NavigationTreeService.ts | 21 ++- .../ConnectionSchemaManagerService.ts | 169 +++++++++++------- webapp/packages/core/src/app/index.ts | 1 + .../NodesManager/NavNodeExtensionsService.ts | 56 ++++++ webapp/packages/core/src/manifest.ts | 4 +- .../sql-editor/src/SqlEditorBootstrap.ts | 18 +- .../src/SqlEditorNavigatorService.ts | 27 +-- 8 files changed, 223 insertions(+), 81 deletions(-) create mode 100644 webapp/packages/core/src/app/shared/NodesManager/NavNodeExtensionsService.ts diff --git a/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts b/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts index 47637a330a..3d2806e829 100644 --- a/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts +++ b/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts @@ -75,10 +75,16 @@ export function useNavigationTree(nodeId: string, parentId: string) { }, [isExpanded, children.isLoaded, children.isLoading, children.children, nodeLoaded, nodeId]); // Here we subscribe to selected nodes if current node selected (mobx) - if (isSelected && !navigationTreeService.selectedNodes.includes(nodeId)) { + if (isSelected && !navigationTreeService.isNodeSelected(nodeId)) { switchSelect(false); } + useEffect(() => () => { + if (navigationTreeService.isNodeSelected(nodeId)) { + navigationTreeService.selectNode(nodeId, true); + } + }, [navigationTreeService]); + return { name, node, diff --git a/webapp/packages/core/src/app/NavigationTree/NavigationTreeService.ts b/webapp/packages/core/src/app/NavigationTree/NavigationTreeService.ts index c68fb25492..974344d11d 100644 --- a/webapp/packages/core/src/app/NavigationTree/NavigationTreeService.ts +++ b/webapp/packages/core/src/app/NavigationTree/NavigationTreeService.ts @@ -7,6 +7,7 @@ */ import { observable } from 'mobx'; +import { Subject, Observable } from 'rxjs'; import { injectable } from '@dbeaver/core/di'; import { NotificationService } from '@dbeaver/core/eventsLog'; @@ -15,13 +16,18 @@ import { NavNodeManagerService, ROOT_NODE_PATH } from '../shared/NodesManager/Na @injectable() export class NavigationTreeService { + readonly selectedNodes = observable.array([]); + readonly onNodeSelect: Observable<[string, boolean]>; - selectedNodes = observable.array([]); + private nodeSelectSubject: Subject<[string, boolean]>; constructor( private NavNodeManagerService: NavNodeManagerService, private notificationService: NotificationService - ) { } + ) { + this.nodeSelectSubject = new Subject(); + this.onNodeSelect = this.nodeSelectSubject.asObservable(); + } async loadNestedNodes(id = ROOT_NODE_PATH) { try { @@ -35,13 +41,22 @@ export class NavigationTreeService { selectNode(id: string, isMultiple?: boolean) { if (!isMultiple) { + for (const id of this.selectedNodes) { + this.nodeSelectSubject.next([id, false]); + } this.selectedNodes.clear(); } - if (!this.selectedNodes.includes(id)) { + if (!this.isNodeSelected(id)) { this.selectedNodes.push(id); + this.nodeSelectSubject.next([id, true]); return true; } this.selectedNodes.remove(id); + this.nodeSelectSubject.next([id, false]); return false; } + + isNodeSelected(navNodeId: string) { + return this.selectedNodes.includes(navNodeId); + } } diff --git a/webapp/packages/core/src/app/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts b/webapp/packages/core/src/app/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts index 284975a4a9..3d9306f3aa 100644 --- a/webapp/packages/core/src/app/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts +++ b/webapp/packages/core/src/app/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts @@ -10,7 +10,9 @@ import { computed, observable } from 'mobx'; import { injectable } from '@dbeaver/core/di'; import { NotificationService } from '@dbeaver/core/eventsLog'; +import { IExtension } from '@dbeaver/core/extensions'; +import { NavigationTreeService } from '../../NavigationTree/NavigationTreeService'; import { ConnectionsManagerService, ObjectContainer } from '../../shared/ConnectionsManager/ConnectionsManagerService'; import { isConnectionProvider, IConnectionProvider } from '../../shared/ConnectionsManager/extensions/IConnectionProvider'; import { isConnectionSetter, IConnectionSetter } from '../../shared/ConnectionsManager/extensions/IConnectionSetter'; @@ -20,34 +22,46 @@ import { IObjectCatalogProvider, isObjectCatalogProvider } from '../../shared/No import { IObjectCatalogSetter, isObjectCatalogSetter } from '../../shared/NodesManager/extensions/IObjectCatalogSetter'; import { IObjectSchemaProvider, isObjectSchemaProvider } from '../../shared/NodesManager/extensions/IObjectSchemaProvider'; import { IObjectSchemaSetter, isObjectSchemaSetter } from '../../shared/NodesManager/extensions/IObjectSchemaSetter'; +import { NavNodeExtensionsService } from '../../shared/NodesManager/NavNodeExtensionsService'; export interface IConnectionInfo { name?: string; driverIcon?: string; } +interface IActiveItem { + id: string; + context: T; + getCurrentConnectionId?: IConnectionProvider; + getCurrentSchemaId?: IObjectSchemaProvider; + getCurrentCatalogId?: IObjectCatalogProvider; + changeConnectionId?: IConnectionSetter; + changeCatalogId?: IObjectCatalogSetter; + changeSchemaId?: IObjectSchemaSetter; +} + @injectable() export class ConnectionSchemaManagerService { get currentConnectionId(): string | undefined { - if (!this.getCurrentConnectionId || !this.currentTab) { + if (!this.activeItem?.getCurrentConnectionId) { return; } - return this.getCurrentConnectionId(this.currentTab); + return this.activeItem.getCurrentConnectionId(this.activeItem.context); } get currentObjectCatalogId(): string | undefined { - if (!this.getCurrentCatalogId || !this.currentTab) { + if (!this.activeItem?.getCurrentCatalogId) { return; } - return this.getCurrentCatalogId(this.currentTab); + return this.activeItem.getCurrentCatalogId(this.activeItem.context); } get currentObjectSchemaId(): string | undefined { - if (!this.getCurrentSchemaId || !this.currentTab) { + if (!this.activeItem?.getCurrentSchemaId) { return; } - return this.getCurrentSchemaId(this.currentTab); + return this.activeItem.getCurrentSchemaId(this.activeItem.context); } @computed get currentObjectCatalog(): ObjectContainer | undefined { @@ -82,31 +96,30 @@ export class ConnectionSchemaManagerService { } get isConnectionChangeable(): boolean { - return !!this.changeConnectionId + return !!this.activeItem?.changeConnectionId && !this.connectionsManagerService.connectionObjectContainers.isLoading(); } get isObjectCatalogChangeable(): boolean { - return !!this.changeCatalogId + return !!this.activeItem?.changeCatalogId && !this.connectionsManagerService.connectionObjectContainers.isLoading(); } get isObjectSchemaChangeable(): boolean { - return !!this.changeSchemaId + return !!this.activeItem?.changeSchemaId && !this.connectionsManagerService.connectionObjectContainers.isLoading(); } - @observable private currentTab: ITab | null = null; - @observable private getCurrentConnectionId: IConnectionProvider | null = null; - @observable private getCurrentSchemaId: IObjectSchemaProvider | null = null; - @observable private getCurrentCatalogId: IObjectCatalogProvider | null = null; - @observable private changeConnectionId: IConnectionSetter | null = null; - @observable private changeCatalogId: IObjectCatalogSetter | null = null; - @observable private changeSchemaId: IObjectSchemaSetter | null = null; + @observable private activeItem: IActiveItem | null = null; + @observable private activeItemHistory: IActiveItem[] = []; - constructor(private navigationTabsService: NavigationTabsService, - private connectionsManagerService: ConnectionsManagerService, - private notificationService: NotificationService) { + constructor( + private navigationTabsService: NavigationTabsService, + private connectionsManagerService: ConnectionsManagerService, + private notificationService: NotificationService, + private navigationTreeService: NavigationTreeService, + private navNodeExtensionsService: NavNodeExtensionsService, + ) { } @@ -116,16 +129,19 @@ export class ConnectionSchemaManagerService { this.navigationTabsService.onTabClose .subscribe(this.onTabClose.bind(this)); + + this.navigationTreeService.onNodeSelect + .subscribe(this.onNodeSelect.bind(this)); } /** * Trigger when user select connection in dropdown */ async selectConnection(connectionId: string) { - if (!this.changeConnectionId || !this.currentTab) { + if (!this.activeItem?.changeConnectionId) { return; } - this.changeConnectionId(connectionId, this.currentTab); + this.activeItem.changeConnectionId(connectionId, this.activeItem); this.loadConnection(connectionId); } @@ -133,10 +149,10 @@ export class ConnectionSchemaManagerService { * Trigger when user select catalog in dropdown */ selectCatalog(catalogId: string) { - if (!this.changeCatalogId || !this.currentTab) { + if (!this.activeItem?.changeCatalogId) { throw new Error('The try to change catalog without connection'); } - this.changeCatalogId(catalogId, this.currentTab); + this.activeItem.changeCatalogId(catalogId, this.activeItem); this.loadConnection(this.currentConnectionId!, catalogId); } @@ -144,60 +160,91 @@ export class ConnectionSchemaManagerService { * Trigger when user select schema in dropdown */ selectSchema(schemaId: string) { - if (!this.changeSchemaId || !this.currentTab) { + if (!this.activeItem?.changeSchemaId) { throw new Error('The try to change schema without connection'); } - this.changeSchemaId(schemaId, this.currentTab); + this.activeItem.changeSchemaId(schemaId, this.activeItem); + } + + private onNodeSelect([navNodeId, selected]: [string, boolean]) { + if (selected) { + const item: IActiveItem = { + id: navNodeId, + context: navNodeId, + }; + this.setExtensions(item, this.navNodeExtensionsService.extensions); + + this.setActiveItem(item); + } else { + this.removeActiveItem(navNodeId); + } } private onTabSelect(tab: ITab) { - this.clear(); - this.currentTab = tab; + const item: IActiveItem = { + id: tab.id, + context: tab, + }; const handler = this.navigationTabsService.getTabHandler(tab.handlerId); if (handler && handler.extensions) { - for (const extension of handler.extensions) { - if (isConnectionProvider(extension)) { - this.getCurrentConnectionId = extension; - } - if (isObjectCatalogProvider(extension)) { - this.getCurrentCatalogId = extension; - } - if (isObjectSchemaProvider(extension)) { - this.getCurrentSchemaId = extension; - } + this.setExtensions(item, handler.extensions); + } - if (isConnectionSetter(extension)) { - this.changeConnectionId = extension; - } - if (isObjectCatalogSetter(extension)) { - this.changeCatalogId = extension; - } - if (isObjectSchemaSetter(extension)) { - this.changeSchemaId = extension; - } + this.setActiveItem(item); + } + + private onTabClose(tab: ITab) { + this.removeActiveItem(tab.id); + } + + private setExtensions(item: IActiveItem, extensions: IExtension[]) { + for (const extension of extensions) { + if (isConnectionProvider(extension)) { + item.getCurrentConnectionId = extension; + } + if (isObjectCatalogProvider(extension)) { + item.getCurrentCatalogId = extension; + } + if (isObjectSchemaProvider(extension)) { + item.getCurrentSchemaId = extension; + } + + if (isConnectionSetter(extension)) { + item.changeConnectionId = extension; + } + if (isObjectCatalogSetter(extension)) { + item.changeCatalogId = extension; + } + if (isObjectSchemaSetter(extension)) { + item.changeSchemaId = extension; } } + } + + private removeActiveItem(id: string) { + this.clearHistory(id); + if (id === this.activeItem?.id) { + this.setActiveItem(this.activeItemHistory[0] || null); + } + } + + private setActiveItem(item: IActiveItem | null) { + if (!item) { + this.activeItem = item; + return; + } + this.clearHistory(item.id); + this.activeItem = item; + this.activeItemHistory.push(item); if (this.currentConnectionId) { this.loadConnection(this.currentConnectionId, this.currentObjectCatalogId); } } - private onTabClose(tab: ITab) { - if (tab.id === this.currentTab?.id) { - this.clear(); - } - } - - private clear() { - this.getCurrentConnectionId = null; - this.getCurrentCatalogId = null; - this.getCurrentSchemaId = null; - this.changeConnectionId = null; - this.changeCatalogId = null; - this.changeSchemaId = null; - this.currentTab = null; + private clearHistory(id: string) { + this.activeItemHistory = this.activeItemHistory.filter(item => item.id !== id); } private async loadConnection(connectionId: string, catalogId?: string) { @@ -207,7 +254,7 @@ export class ConnectionSchemaManagerService { this.notificationService.logException(exception, 'Can\'t load database drivers', true); } - if (this.changeCatalogId || this.changeSchemaId) { + if (this.activeItem?.changeCatalogId || this.activeItem?.changeSchemaId) { try { await this.connectionsManagerService.loadObjectContainer(connectionId, catalogId); } catch (exception) { diff --git a/webapp/packages/core/src/app/index.ts b/webapp/packages/core/src/app/index.ts index e3d6387a31..a2ee8b941b 100644 --- a/webapp/packages/core/src/app/index.ts +++ b/webapp/packages/core/src/app/index.ts @@ -17,6 +17,7 @@ export * from './shared/NodesManager/extensions/IObjectSchemaProvider'; export * from './shared/NodesManager/extensions/IObjectSchemaSetter'; export * from './shared/NodesManager/DBObjectService'; export * from './shared/NodesManager/NavNodeManagerService'; +export * from './shared/NodesManager/NavNodeExtensionsService'; export * from './shared/NodesManager/NodeManagerUtils'; export * from './shared/Navigation/NavigationService'; export * from './shared/NavigationTabs/NavigationTabsService'; diff --git a/webapp/packages/core/src/app/shared/NodesManager/NavNodeExtensionsService.ts b/webapp/packages/core/src/app/shared/NodesManager/NavNodeExtensionsService.ts new file mode 100644 index 0000000000..ca4e9691b1 --- /dev/null +++ b/webapp/packages/core/src/app/shared/NodesManager/NavNodeExtensionsService.ts @@ -0,0 +1,56 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { injectable } from '@dbeaver/core/di'; +import { IExtension } from '@dbeaver/core/extensions'; + +import { connectionProvider } from '../ConnectionsManager/extensions/IConnectionProvider'; +import { objectCatalogProvider } from './extensions/IObjectCatalogProvider'; +import { objectSchemaProvider } from './extensions/IObjectSchemaProvider'; +import { NavNodeManagerService } from './NavNodeManagerService'; +import { NodeManagerUtils } from './NodeManagerUtils'; + +@injectable() +export class NavNodeExtensionsService { + readonly extensions: IExtension[]; + + constructor( + private navNodeManagerService: NavNodeManagerService, + ) { + this.extensions = [ + connectionProvider(this.getConnection.bind(this)), + objectCatalogProvider(this.getDBObjectCatalog.bind(this)), + objectSchemaProvider(this.getDBObjectSchema.bind(this)), + ]; + } + + private getConnection(context: string) { + const nodeInfo = this.navNodeManagerService + .getNodeContainerInfo(context); + + if (!nodeInfo.connectionId) { + return; + } + // connection node id differs from connection id + return NodeManagerUtils.connectionNodeIdToConnectionId(nodeInfo.connectionId); + } + + private getDBObjectCatalog(context: string) { + const nodeInfo = this.navNodeManagerService + .getNodeContainerInfo(context); + + return nodeInfo.catalogId; + } + + private getDBObjectSchema(context: string) { + const nodeInfo = this.navNodeManagerService + .getNodeContainerInfo(context); + + return nodeInfo.schemaId; + } +} diff --git a/webapp/packages/core/src/manifest.ts b/webapp/packages/core/src/manifest.ts index 868006d856..c84f0e60e2 100644 --- a/webapp/packages/core/src/manifest.ts +++ b/webapp/packages/core/src/manifest.ts @@ -8,7 +8,8 @@ import { ConnectionDialogsService, ConnectionsManagerService, - MainMenuService, NavigationService, NavigationTreeService, NavNodeManagerService, DBObjectService, + MainMenuService, NavigationService, NavigationTreeService, + NavNodeManagerService, DBObjectService, NavNodeExtensionsService, ConnectionSchemaManagerService, NavigationTabsService, NavigationTreeContextMenuService, SettingsMenuService, LogViewerService, LogViewerMenuService, TopNavService, @@ -65,6 +66,7 @@ export const coreManifest: PluginManifest = { NavigationTreeContextMenuService, NavigationTreeService, NavNodeManagerService, + NavNodeExtensionsService, DBObjectService, NotificationService, SessionService, diff --git a/webapp/packages/sql-editor/src/SqlEditorBootstrap.ts b/webapp/packages/sql-editor/src/SqlEditorBootstrap.ts index 6393deb63c..c8afcf1b53 100644 --- a/webapp/packages/sql-editor/src/SqlEditorBootstrap.ts +++ b/webapp/packages/sql-editor/src/SqlEditorBootstrap.ts @@ -11,7 +11,7 @@ import { NavigationTreeContextMenuService, EObjectFeature, NodeManagerUtils, - NavNode, ConnectionsManagerService, + NavNode, ConnectionsManagerService, ConnectionSchemaManagerService, } from '@dbeaver/core/app'; import { injectable } from '@dbeaver/core/di'; import { ContextMenuService, IContextMenuItem, IMenuContext } from '@dbeaver/core/dialogs'; @@ -26,7 +26,8 @@ export class SqlEditorBootstrap { private contextMenuService: ContextMenuService, private connectionsManagerService: ConnectionsManagerService, private sqlEditorTabService: SqlEditorTabService, - private sqlEditorNavigatorService: SqlEditorNavigatorService + private sqlEditorNavigatorService: SqlEditorNavigatorService, + private connectionSchemaManagerService: ConnectionSchemaManagerService, ) {} async bootstrap() { @@ -37,7 +38,18 @@ export class SqlEditorBootstrap { id: 'sql-editor', title: 'SQL', order: 2, - onClick: () => this.sqlEditorNavigatorService.openNewEditor(), + onClick: () => { + console.log( + this.connectionSchemaManagerService.currentConnectionId, + this.connectionSchemaManagerService.currentObjectCatalogId, + this.connectionSchemaManagerService.currentObjectSchemaId + ); + this.sqlEditorNavigatorService.openNewEditor( + this.connectionSchemaManagerService.currentConnectionId, + this.connectionSchemaManagerService.currentObjectCatalogId, + this.connectionSchemaManagerService.currentObjectSchemaId, + ); + }, isDisabled: () => !this.connectionsManagerService.hasAnyConnection(), } ); diff --git a/webapp/packages/sql-editor/src/SqlEditorNavigatorService.ts b/webapp/packages/sql-editor/src/SqlEditorNavigatorService.ts index 19b3269214..db0b66ef2c 100644 --- a/webapp/packages/sql-editor/src/SqlEditorNavigatorService.ts +++ b/webapp/packages/sql-editor/src/SqlEditorNavigatorService.ts @@ -77,10 +77,12 @@ export class SqlEditorNavigatorService { registerTabHandler() { } - openNewEditor(connectionId?: string) { + openNewEditor(connectionId?: string, catalogId?: string, schemaId?: string) { this.navigator.navigateTo({ type: SQLEditorNavigationAction.create, connectionId, + catalogId, + schemaId, }); } @@ -125,7 +127,7 @@ export class SqlEditorNavigatorService { const tabInfo = await contexts.getContext(this.navigationTabsService.navigationTabContext); if (data.type === SQLEditorNavigationAction.create) { - const tabOptions = await this.createNewEditor(data.connectionId); + const tabOptions = await this.createNewEditor(data.connectionId, data.catalogId, data.schemaId); if (tabOptions) { tabInfo.openNewTab(tabOptions); } else { @@ -159,19 +161,20 @@ export class SqlEditorNavigatorService { } } - private async createNewEditor(connectionId?: string): Promise | null> { + private async createNewEditor( + connectionId?: string, + catalogId?: string, + schemaId?: string + ): Promise | null> { const order = this.getFreeEditorId(); - const connection = connectionId - ? this.connectionsManagerService.getConnectionById(connectionId) - : this.connectionsManagerService.connections[0]; - - if (!connection) { - return null; + if (!connectionId) { + connectionId = this.connectionsManagerService.connections[0].id; } - await this.sqlDialectInfoService.loadSqlDialectInfo(connection.id); - const context = await this.createSqlContext(connection.id); + await this.sqlDialectInfoService.loadSqlDialectInfo(connectionId); + + const context = await this.createSqlContext(connectionId, catalogId, schemaId); return { handlerId: sqlEditorTabHandlerKey, @@ -179,7 +182,7 @@ export class SqlEditorNavigatorService { query: '', order, contextId: context.contextId, - connectionId: connection.id, + connectionId, objectCatalogId: context.objectCatalogId, objectSchemaId: context.objectSchemaId, sqlExecutionState: new SqlExecutionState(),