mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
* dbeaver/pro#7465 refactor: migrate context menu to ariakit * refactor: remove unused modal prop from ContextMenu and clean up schema name handling * chore: add memoization to the base context menu components * fix: menu issues --------- Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
325 lines
11 KiB
TypeScript
325 lines
11 KiB
TypeScript
/*
|
|
* CloudBeaver - Cloud Database Manager
|
|
* Copyright (C) 2020-2025 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 { importLazyComponent, RenameDialog } from '@cloudbeaver/core-blocks';
|
|
import {
|
|
type Connection,
|
|
ConnectionInfoResource,
|
|
createConnectionParam,
|
|
DATA_CONTEXT_CONNECTION,
|
|
type IConnectionInfoParams,
|
|
isConnectionProvider,
|
|
isObjectCatalogProvider,
|
|
isObjectSchemaProvider,
|
|
} from '@cloudbeaver/core-connections';
|
|
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
|
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
|
import type { IExecutorHandler } from '@cloudbeaver/core-executor';
|
|
import { ExtensionUtils } from '@cloudbeaver/core-extensions';
|
|
import { LocalizationService } from '@cloudbeaver/core-localization';
|
|
import { DATA_CONTEXT_NAV_NODE, EObjectFeature, NodeManagerUtils } from '@cloudbeaver/core-navigation-tree';
|
|
import { type ISessionAction, sessionActionContext, SessionActionService } from '@cloudbeaver/core-root';
|
|
import { ACTION_OPEN, ACTION_RENAME, ActionService, menuExtractItems, MenuService, ViewService } from '@cloudbeaver/core-view';
|
|
import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections';
|
|
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
|
import {
|
|
DATA_CONTEXT_SQL_EDITOR_STATE,
|
|
ESqlDataSourceFeatures,
|
|
getSqlEditorName,
|
|
LocalStorageSqlDataSource,
|
|
SqlDataSourceService,
|
|
SqlEditorService,
|
|
SqlEditorSettingsService,
|
|
} from '@cloudbeaver/plugin-sql-editor';
|
|
import { MENU_APP_ACTIONS } from '@cloudbeaver/plugin-top-app-bar';
|
|
|
|
import { ACTION_SQL_EDITOR_NEW } from './ACTION_SQL_EDITOR_NEW.js';
|
|
import { ACTION_SQL_EDITOR_OPEN } from './ACTION_SQL_EDITOR_OPEN.js';
|
|
import { DATA_CONTEXT_SQL_EDITOR_TAB } from './DATA_CONTEXT_SQL_EDITOR_TAB.js';
|
|
import { isSessionActionOpenSQLEditor } from './sessionActionOpenSQLEditor.js';
|
|
import { SQL_EDITOR_SOURCE_ACTION } from './SQL_EDITOR_SOURCE_ACTION.js';
|
|
import { SqlEditorNavigatorService } from './SqlEditorNavigatorService.js';
|
|
import { SqlEditorTabService } from './SqlEditorTabService.js';
|
|
|
|
const WelcomeNewSqlEditor = importLazyComponent(() => import('./WelcomeNewSqlEditor.js').then(m => m.WelcomeNewSqlEditor));
|
|
|
|
interface IActiveConnectionContext {
|
|
connectionKey?: IConnectionInfoParams;
|
|
catalogId?: string;
|
|
schemaId?: string;
|
|
}
|
|
|
|
@injectable(() => [
|
|
SqlEditorNavigatorService,
|
|
NavigationTabsService,
|
|
ViewService,
|
|
ActionService,
|
|
MenuService,
|
|
SessionActionService,
|
|
CommonDialogService,
|
|
SqlEditorTabService,
|
|
SqlDataSourceService,
|
|
ConnectionInfoResource,
|
|
SqlEditorService,
|
|
LocalizationService,
|
|
SqlEditorSettingsService,
|
|
])
|
|
export class SqlEditorBootstrap extends Bootstrap {
|
|
constructor(
|
|
private readonly sqlEditorNavigatorService: SqlEditorNavigatorService,
|
|
private readonly navigationTabsService: NavigationTabsService,
|
|
private readonly viewService: ViewService,
|
|
private readonly actionService: ActionService,
|
|
private readonly menuService: MenuService,
|
|
private readonly sessionActionService: SessionActionService,
|
|
private readonly commonDialogService: CommonDialogService,
|
|
private readonly sqlEditorTabService: SqlEditorTabService,
|
|
private readonly sqlDataSourceService: SqlDataSourceService,
|
|
private readonly connectionInfoResource: ConnectionInfoResource,
|
|
private readonly sqlEditorService: SqlEditorService,
|
|
private readonly localizationService: LocalizationService,
|
|
private readonly sqlEditorSettingsService: SqlEditorSettingsService,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
override register(): void {
|
|
this.navigationTabsService.welcomeContainer.add(WelcomeNewSqlEditor, undefined, () => this.sqlEditorSettingsService.disabled);
|
|
this.registerTopAppBarItem();
|
|
|
|
this.menuService.addCreator({
|
|
contexts: [DATA_CONTEXT_SQL_EDITOR_STATE, DATA_CONTEXT_SQL_EDITOR_TAB],
|
|
getItems: (context, items) => [...items, ACTION_RENAME],
|
|
orderItems: (context, items) => {
|
|
items.unshift(...menuExtractItems(items, [ACTION_RENAME]));
|
|
return items;
|
|
},
|
|
});
|
|
|
|
this.menuService.addCreator({
|
|
root: true,
|
|
contexts: [DATA_CONTEXT_CONNECTION],
|
|
isApplicable: context => {
|
|
const node = context.get(DATA_CONTEXT_NAV_NODE);
|
|
|
|
if (node && !node.objectFeatures.includes(EObjectFeature.dataSource)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
getItems: (context, items) => [...items, ACTION_SQL_EDITOR_OPEN],
|
|
orderItems: (context, items) => {
|
|
items.unshift(...menuExtractItems(items, [ACTION_OPEN, ACTION_SQL_EDITOR_OPEN]));
|
|
return items;
|
|
},
|
|
});
|
|
|
|
this.actionService.addHandler({
|
|
id: 'sql-editor',
|
|
actions: [ACTION_RENAME, ACTION_SQL_EDITOR_OPEN],
|
|
isActionApplicable: (context, action) => {
|
|
switch (action) {
|
|
case ACTION_RENAME: {
|
|
const editorState = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
|
|
|
|
if (!editorState) {
|
|
return false;
|
|
}
|
|
|
|
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
|
|
|
return dataSource?.hasFeature(ESqlDataSourceFeatures.setName) ?? false;
|
|
}
|
|
case ACTION_SQL_EDITOR_OPEN:
|
|
return context.has(DATA_CONTEXT_CONNECTION);
|
|
}
|
|
return false;
|
|
},
|
|
handler: async (context, action) => {
|
|
switch (action) {
|
|
case ACTION_RENAME: {
|
|
const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE)!;
|
|
const dataSource = this.sqlDataSourceService.get(state.editorId);
|
|
const executionContext = dataSource?.executionContext;
|
|
|
|
if (!dataSource) {
|
|
return;
|
|
}
|
|
|
|
let connection: Connection | undefined;
|
|
|
|
if (executionContext) {
|
|
connection = this.connectionInfoResource.get(createConnectionParam(executionContext.projectId, executionContext.connectionId));
|
|
}
|
|
|
|
const name = getSqlEditorName(state, dataSource, connection);
|
|
const regexp = /^(.*?)(\.\w+)$/gi.exec(name);
|
|
|
|
const { status, result } = await this.commonDialogService.open(RenameDialog, {
|
|
name: regexp?.[1] ?? name,
|
|
objectName: name,
|
|
icon: dataSource.icon,
|
|
validation: name =>
|
|
!this.sqlEditorTabService.sqlEditorTabs.some(
|
|
tab => tab.handlerState.order !== state.order && this.sqlEditorService.getName(tab.handlerState) === name.trim(),
|
|
) && dataSource.canRename(name),
|
|
});
|
|
|
|
if (status === DialogueStateResult.Resolved && result !== undefined) {
|
|
dataSource.setName((result ?? '').trim());
|
|
}
|
|
break;
|
|
}
|
|
case ACTION_SQL_EDITOR_OPEN: {
|
|
const connectionKey = context.get(DATA_CONTEXT_CONNECTION)!;
|
|
|
|
this.sqlEditorNavigatorService.openNewEditor({
|
|
dataSourceKey: LocalStorageSqlDataSource.key,
|
|
connectionKey,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
this.navigationTabsService.onInit.addHandler(state => {
|
|
if (state) {
|
|
this.sessionActionService.onAction.addHandler(this.handleAction);
|
|
} else {
|
|
this.sessionActionService.onAction.removeHandler(this.handleAction);
|
|
}
|
|
});
|
|
}
|
|
|
|
private registerTopAppBarItem() {
|
|
this.menuService.addCreator({
|
|
menus: [MENU_APP_ACTIONS],
|
|
getItems: (context, items) => [...items, ACTION_SQL_EDITOR_NEW],
|
|
orderItems: (context, items) => {
|
|
const actionsOpen = menuExtractItems(items, [ACTION_SQL_EDITOR_NEW]);
|
|
|
|
let placeIndex = items.indexOf(ACTION_SQL_EDITOR_NEW);
|
|
const connectionsIndex = items.indexOf(MENU_CONNECTIONS);
|
|
|
|
if (connectionsIndex !== -1) {
|
|
placeIndex = connectionsIndex + 1;
|
|
}
|
|
|
|
items.splice(placeIndex, 0, ...actionsOpen);
|
|
|
|
return items;
|
|
},
|
|
});
|
|
|
|
this.actionService.addHandler({
|
|
id: 'sql-editor-new',
|
|
actions: [ACTION_SQL_EDITOR_NEW],
|
|
isLabelVisible: () => false,
|
|
getActionInfo: (context, action) => {
|
|
const connectionContext = this.getActiveConnectionContext();
|
|
const schemaAndCatalog = NodeManagerUtils.concatSchemaAndCatalog(connectionContext.catalogId, connectionContext.schemaId);
|
|
|
|
let tooltip = action.info.tooltip;
|
|
|
|
if (connectionContext.connectionKey) {
|
|
const connectionInfo = this.connectionInfoResource.get(connectionContext.connectionKey);
|
|
|
|
if (connectionInfo) {
|
|
tooltip = this.localizationService.translate(
|
|
'plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip_context',
|
|
'plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip',
|
|
{
|
|
connection: `${connectionInfo.name}${schemaAndCatalog ? ' ' + schemaAndCatalog : ''}`,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
return {
|
|
...action.info,
|
|
label: '',
|
|
tooltip,
|
|
};
|
|
},
|
|
isHidden: (context, action) => {
|
|
if (action === ACTION_SQL_EDITOR_NEW) {
|
|
return this.sqlEditorSettingsService.disabled;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
handler: async (context, action) => {
|
|
switch (action) {
|
|
case ACTION_SQL_EDITOR_NEW: {
|
|
await this.openSQLEditor();
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
private getActiveConnectionContext(): IActiveConnectionContext {
|
|
let connectionKey: IConnectionInfoParams | undefined;
|
|
let catalogId: string | undefined;
|
|
let schemaId: string | undefined;
|
|
|
|
for (const activeView of this.viewService.activeViews) {
|
|
ExtensionUtils.from(activeView.extensions)
|
|
.on(isConnectionProvider, extension => {
|
|
connectionKey = extension(activeView.context);
|
|
})
|
|
.on(isObjectCatalogProvider, extension => {
|
|
catalogId = extension(activeView.context);
|
|
})
|
|
.on(isObjectSchemaProvider, extension => {
|
|
schemaId = extension(activeView.context);
|
|
});
|
|
|
|
if (connectionKey) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
connectionKey,
|
|
catalogId,
|
|
schemaId,
|
|
};
|
|
}
|
|
|
|
async openSQLEditor(): Promise<void> {
|
|
const connectionContext = this.getActiveConnectionContext();
|
|
|
|
await this.sqlEditorNavigatorService.openNewEditor({
|
|
dataSourceKey: LocalStorageSqlDataSource.key,
|
|
connectionKey: connectionContext.connectionKey,
|
|
catalogId: connectionContext.catalogId,
|
|
schemaId: connectionContext.schemaId,
|
|
});
|
|
}
|
|
|
|
private readonly handleAction: IExecutorHandler<ISessionAction | null> = (data, contexts) => {
|
|
const processInfo = contexts.getContext(sessionActionContext);
|
|
|
|
if (isSessionActionOpenSQLEditor(data)) {
|
|
try {
|
|
this.sqlEditorNavigatorService.openNewEditor({
|
|
dataSourceKey: LocalStorageSqlDataSource.key,
|
|
name: data['editor-name'],
|
|
connectionKey: createConnectionParam(data['project-id'], data['connection-id']),
|
|
source: SQL_EDITOR_SOURCE_ACTION,
|
|
});
|
|
} finally {
|
|
processInfo.process();
|
|
}
|
|
}
|
|
};
|
|
}
|