mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
247 lines
7.7 KiB
TypeScript
247 lines
7.7 KiB
TypeScript
/*
|
|
* 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 {
|
|
NavigationTabsService,
|
|
ITab,
|
|
ConnectionsManagerService,
|
|
INavigator,
|
|
NavigationService,
|
|
IContextProvider,
|
|
ITabOptions,
|
|
NodesManagerService,
|
|
INodeNavigationData,
|
|
NavigationType,
|
|
} from '@dbeaver/core/app';
|
|
import { injectable } from '@dbeaver/core/di';
|
|
import { NotificationService } from '@dbeaver/core/eventsLog';
|
|
import { GraphQLService } from '@dbeaver/core/sdk';
|
|
import { IExecutionContext } from '@dbeaver/data-viewer-plugin';
|
|
|
|
import { ISqlEditorTabState } from './ISqlEditorTabState';
|
|
import { SqlDialectInfoService } from './SqlDialectInfoService';
|
|
import { sqlEditorTabHandlerKey } from './sqlEditorTabHandlerKey';
|
|
import { SqlExecutionState } from './SqlExecutionState';
|
|
|
|
enum SQLEditorNavigationAction {
|
|
create,
|
|
select,
|
|
close
|
|
}
|
|
|
|
export interface SQLEditorActionContext {
|
|
type: SQLEditorNavigationAction;
|
|
}
|
|
|
|
export interface SQLCreateAction extends SQLEditorActionContext {
|
|
type: SQLEditorNavigationAction.create;
|
|
|
|
connectionId?: string;
|
|
catalogId?: string;
|
|
schemaId?: string;
|
|
}
|
|
|
|
export interface SQLEditorAction extends SQLEditorActionContext {
|
|
type: SQLEditorNavigationAction.close | SQLEditorNavigationAction.select;
|
|
|
|
editorId: string;
|
|
resultId: string;
|
|
}
|
|
|
|
@injectable()
|
|
export class SqlEditorNavigatorService {
|
|
|
|
private readonly navigator: INavigator<SQLCreateAction | SQLEditorAction>;
|
|
constructor(private navigationTabsService: NavigationTabsService,
|
|
private connectionsManagerService: ConnectionsManagerService,
|
|
private notificationService: NotificationService,
|
|
private gql: GraphQLService,
|
|
private sqlDialectInfoService: SqlDialectInfoService,
|
|
private nodesManagerService: NodesManagerService,
|
|
private navigationService: NavigationService) {
|
|
|
|
this.navigator = this.navigationService.createNavigator<SQLCreateAction | SQLEditorAction>(
|
|
null,
|
|
this.navigateHandler.bind(this)
|
|
);
|
|
this.nodesManagerService.navigator.addHandler(this.nodeNavigationHandler.bind(this));
|
|
}
|
|
|
|
registerTabHandler() {
|
|
}
|
|
|
|
openNewEditor(connectionId?: string) {
|
|
this.navigator.navigateTo({
|
|
type: SQLEditorNavigationAction.create,
|
|
connectionId,
|
|
});
|
|
}
|
|
|
|
openEditorResult(editorId: string, resultId: string) {
|
|
this.navigator.navigateTo({
|
|
type: SQLEditorNavigationAction.select,
|
|
editorId,
|
|
resultId,
|
|
});
|
|
}
|
|
|
|
closeEditorResult(editorId: string, resultId: string) {
|
|
this.navigator.navigateTo({
|
|
type: SQLEditorNavigationAction.close,
|
|
editorId,
|
|
resultId,
|
|
});
|
|
}
|
|
|
|
private async nodeNavigationHandler(contexts: IContextProvider<INodeNavigationData>) {
|
|
try {
|
|
const nodeInfo = await contexts.getContext(this.nodesManagerService.navigationNodeContext);
|
|
|
|
if (nodeInfo.type === NavigationType.closeConnection) {
|
|
for (const tab of this.navigationTabsService.findTabs(
|
|
isSQLEditorTab(tab => tab.handlerState.connectionId.includes(nodeInfo.nodeId))
|
|
)) {
|
|
await this.navigationTabsService.closeTab(tab.id);
|
|
}
|
|
return;
|
|
}
|
|
} catch (exception) {
|
|
this.notificationService.logException(exception, 'Error in Object Viewer while processing action with database node');
|
|
}
|
|
}
|
|
|
|
private async navigateHandler(
|
|
contexts: IContextProvider<SQLCreateAction | SQLEditorAction>,
|
|
data: SQLCreateAction | SQLEditorAction
|
|
) {
|
|
try {
|
|
const tabInfo = await contexts.getContext(this.navigationTabsService.navigationTabContext);
|
|
|
|
if (data.type === SQLEditorNavigationAction.create) {
|
|
const tabOptions = await this.createNewEditor(data.connectionId);
|
|
if (tabOptions) {
|
|
tabInfo.openNewTab(tabOptions);
|
|
} else {
|
|
this.notificationService.logError({
|
|
title: `Failed to create editor for ${data.connectionId} connection`,
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
const tab = this.navigationTabsService.findTab(isSQLEditorTab(tab => tab.id === data.editorId));
|
|
if (!tab) {
|
|
return;
|
|
}
|
|
|
|
if (data.type === SQLEditorNavigationAction.select) {
|
|
tab.handlerState.currentResultTabId = data.resultId;
|
|
} else if (data.type === SQLEditorNavigationAction.close) {
|
|
tab.handlerState.resultTabs.splice(
|
|
tab.handlerState.resultTabs.findIndex(result => result.resultTabId === data.resultId),
|
|
1
|
|
);
|
|
|
|
if (tab.handlerState.currentResultTabId === data.resultId) {
|
|
tab.handlerState.currentResultTabId = tab.handlerState.resultTabs[0]?.resultTabId || '';
|
|
}
|
|
}
|
|
this.navigationTabsService.selectTab(tab.id);
|
|
} catch (exception) {
|
|
this.notificationService.logException(exception, 'Error in SQL Editor while processing action with editor');
|
|
}
|
|
}
|
|
|
|
private async createNewEditor(connectionId?: string): Promise<ITabOptions<ISqlEditorTabState> | null> {
|
|
const order = this.getFreeEditorId();
|
|
|
|
const connection = connectionId
|
|
? this.connectionsManagerService.getConnectionById(connectionId)
|
|
: this.connectionsManagerService.connections[0];
|
|
|
|
if (!connection) {
|
|
return null;
|
|
}
|
|
await this.sqlDialectInfoService.loadSqlDialectInfo(connection.id);
|
|
|
|
const context = await this.createSqlContext(connection.id);
|
|
|
|
return {
|
|
handlerId: sqlEditorTabHandlerKey,
|
|
handlerState: {
|
|
query: '',
|
|
order,
|
|
contextId: context.contextId,
|
|
connectionId: connection.id,
|
|
objectCatalogId: context.objectCatalogId,
|
|
objectSchemaId: context.objectSchemaId,
|
|
sqlExecutionState: new SqlExecutionState(),
|
|
resultTabs: [],
|
|
},
|
|
};
|
|
}
|
|
|
|
private getFreeEditorId() {
|
|
const editorTabs = this.navigationTabsService.findTabs(isSQLEditorTab);
|
|
const ordered = Array.from(editorTabs).map(tab => tab.handlerState.order);
|
|
return findMinimalFree(ordered, 1);
|
|
}
|
|
|
|
/**
|
|
* Returns context id, context catalog and schema
|
|
* When try create context without catalog or schema the context is created with default catalog and schema
|
|
* and response contains its ids.
|
|
* If in the response there are no catalog or schema it means that database has no catalogs or schemas at all.
|
|
*/
|
|
private async createSqlContext(
|
|
connectionId: string,
|
|
defaultCatalog?: string,
|
|
defaultSchema?: string
|
|
): Promise<IExecutionContext> {
|
|
|
|
const response = await this.gql.gql.sqlContextCreate({
|
|
connectionId,
|
|
defaultCatalog,
|
|
defaultSchema,
|
|
});
|
|
return {
|
|
contextId: response.context.id,
|
|
connectionId,
|
|
objectCatalogId: response.context.defaultCatalog,
|
|
objectSchemaId: response.context.defaultSchema,
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
function findMinimalFree(array: number[], base: number): number {
|
|
return array
|
|
.sort((a, b) => b - a)
|
|
.reduceRight((prev, cur) => (prev === cur ? prev + 1 : prev), base);
|
|
}
|
|
|
|
export function isSQLEditorTab(tab: ITab): tab is ITab<ISqlEditorTabState>;
|
|
export function isSQLEditorTab(
|
|
predicate: (tab: ITab<ISqlEditorTabState>) => boolean
|
|
): (tab: ITab) => tab is ITab<ISqlEditorTabState>
|
|
export function isSQLEditorTab(
|
|
tab: ITab | ((tab: ITab<ISqlEditorTabState>) => boolean)
|
|
): boolean | ((tab: ITab) => tab is ITab<ISqlEditorTabState>) {
|
|
if (typeof tab === 'function') {
|
|
const predicate = tab;
|
|
return (tab: ITab): tab is ITab<ISqlEditorTabState> => {
|
|
const sqlEditorTab = tab.handlerId === sqlEditorTabHandlerKey;
|
|
if (!predicate || !sqlEditorTab) {
|
|
return sqlEditorTab;
|
|
}
|
|
return predicate(tab);
|
|
};
|
|
}
|
|
return tab.handlerId === sqlEditorTabHandlerKey;
|
|
}
|