mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-3300 feat: dataset editing (#1614)
* CB-3300 feat: dataset editing * CB-3300 chore: remove unused styles --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
This commit is contained in:
@@ -11,7 +11,7 @@ import { computed, makeObservable } from 'mobx';
|
||||
import { UserDataService, UserInfoResource } from '@cloudbeaver/core-authentication';
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import { Executor, ExecutorInterrupter, IExecutor, ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { DataSynchronizationService, ServerEventId } from '@cloudbeaver/core-root';
|
||||
import { DataSynchronizationService, ServerConfigResource, ServerEventId } from '@cloudbeaver/core-root';
|
||||
import { CachedMapAllKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
import { NavigationService } from '@cloudbeaver/core-ui';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
@@ -46,7 +46,7 @@ export class ProjectsService extends Dependency {
|
||||
get activeProjects(): ProjectInfo[] {
|
||||
let activeProjects: ProjectInfo[] = [];
|
||||
|
||||
if (activeProjects.length === 0 && this.activeProjectIds.length > 0) {
|
||||
if (this.activeProjectIds.length > 0) {
|
||||
activeProjects = this.projectInfoResource
|
||||
.get(resourceKeyList(this.activeProjectIds))
|
||||
.filter(Boolean) as ProjectInfo[];
|
||||
@@ -79,6 +79,10 @@ export class ProjectsService extends Dependency {
|
||||
}
|
||||
|
||||
get activeProjectIds(): string[] {
|
||||
if (!this.serverConfigResource.distributed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.userProjectsSettings.activeProjectIds;
|
||||
}
|
||||
|
||||
@@ -92,6 +96,7 @@ export class ProjectsService extends Dependency {
|
||||
readonly getActiveProjectTask: ISyncExecutor;
|
||||
|
||||
constructor(
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly projectInfoResource: ProjectInfoResource,
|
||||
private readonly userInfoResource: UserInfoResource,
|
||||
private readonly userDataService: UserDataService,
|
||||
|
||||
@@ -42,7 +42,7 @@ export const Tab = observer<TabProps>(function Tab({
|
||||
const tab = useTab(tabId, onOpen, onClose, onClick);
|
||||
const info = tab.getInfo();
|
||||
|
||||
const canClose = getComputed(() => !!onClose || tab.state.closable);
|
||||
const canClose = getComputed(() => !!onClose || (tab.closable && tab.state.closable));
|
||||
|
||||
return styled(useStyles(BASE_TAB_STYLES, BASE_TAB_ACTION_STYLES, style))(
|
||||
<TabContext.Provider value={tabContext}>
|
||||
|
||||
@@ -52,6 +52,9 @@ export function useTab(
|
||||
get selected() {
|
||||
return this.state.state.selectedId === this.tabId;
|
||||
},
|
||||
get closable() {
|
||||
return this.state.canClose(this.tabId);
|
||||
},
|
||||
getInfo() {
|
||||
return this.state.getTabInfo(this.tabId);
|
||||
},
|
||||
@@ -68,6 +71,7 @@ export function useTab(
|
||||
},
|
||||
}), {
|
||||
selected: computed,
|
||||
closable: computed,
|
||||
state: observable.ref,
|
||||
tabId: observable.ref,
|
||||
}, {
|
||||
|
||||
@@ -87,8 +87,14 @@ export class TabsBootstrap extends Bootstrap {
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
const tab = context.tryGet(DATA_CONTEXT_TAB_ID);
|
||||
const state = context.tryGet(DATA_CONTEXT_TABS_CONTEXT);
|
||||
return !!state?.enabledBaseActions && context.get(DATA_CONTEXT_MENU) === MENU_TAB;
|
||||
return (
|
||||
!!tab
|
||||
&& !!state?.enabledBaseActions
|
||||
&& context.get(DATA_CONTEXT_MENU) === MENU_TAB
|
||||
&& state.canClose(tab)
|
||||
);
|
||||
},
|
||||
getItems: (context, items) => [
|
||||
...items,
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ITabsContext<T = Record<string, any>> {
|
||||
closable: boolean;
|
||||
tabList?: string[];
|
||||
enabledBaseActions?: boolean;
|
||||
canClose: (tabId: string) => boolean;
|
||||
getTabInfo: (tabId: string) => ITabInfo<T> | undefined;
|
||||
getTabState: <T>(tabId: string, valueGetter?: MetadataValueGetter<string, T>) => T;
|
||||
getLocalState: <T>(tabId: string, valueGetter?: MetadataValueGetter<string, T>) => T;
|
||||
|
||||
@@ -32,6 +32,7 @@ type Props<T = Record<string, any>> = ExtractContainerProps<T> & React.PropsWith
|
||||
autoSelect?: boolean;
|
||||
tabList?: string[];
|
||||
enabledBaseActions?: boolean;
|
||||
canClose?: (tab: ITabData<T>) => boolean;
|
||||
onChange?: (tab: ITabData<T>) => void;
|
||||
onClose?: (tab: ITabData<T>) => void;
|
||||
}>;
|
||||
@@ -50,6 +51,7 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
enabledBaseActions,
|
||||
onChange: onOpen,
|
||||
onClose,
|
||||
canClose,
|
||||
...rest
|
||||
}: Props<T>): React.ReactElement | null {
|
||||
const props = useMemo(() => rest as any as T, [...Object.values(rest)]);
|
||||
@@ -87,6 +89,7 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
const dynamic = useObjectRef(() => ({
|
||||
selectedId: selectedId || currentTabId,
|
||||
}), {
|
||||
canClose,
|
||||
open: onOpen,
|
||||
close: onClose,
|
||||
props,
|
||||
@@ -176,8 +179,14 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
}, [!isNull(state.selectedId) && !isUndefined(state.selectedId)]);
|
||||
|
||||
const value = useObservableRef<ITabsContext<T>>(() => ({
|
||||
canClose(tabId) {
|
||||
return dynamic.canClose?.({
|
||||
tabId,
|
||||
props: dynamic.props,
|
||||
}) ?? true;
|
||||
},
|
||||
getTabInfo(tabId: string) {
|
||||
return dynamic.container?.getTabInfo(tabId);
|
||||
return dynamic.container?.getTabInfo(tabId);
|
||||
},
|
||||
getTabState(tabId: string, valueGetter?: MetadataValueGetter<string, any>) {
|
||||
return dynamic.container?.getTabState(
|
||||
@@ -200,6 +209,10 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
});
|
||||
},
|
||||
async close(tabId: string) {
|
||||
if (!this.canClose(tabId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await closeExecutor.execute({
|
||||
tabId,
|
||||
props: dynamic.props,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { createAction } from '../createAction';
|
||||
|
||||
export const ACTION_EDIT = createAction('edit', {
|
||||
label: 'core_view_action_edit_label',
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './Action/Actions/ACTION_COLLAPSE_ALL';
|
||||
export * from './Action/Actions/ACTION_DELETE';
|
||||
export * from './Action/Actions/ACTION_EDIT';
|
||||
export * from './Action/Actions/ACTION_EXPORT';
|
||||
export * from './Action/Actions/ACTION_FILTER';
|
||||
export * from './Action/Actions/ACTION_LAYOUT';
|
||||
|
||||
@@ -3,4 +3,5 @@ export default [
|
||||
['core_view_zoom_in', 'Zoom In'],
|
||||
['core_view_zoom_out', 'Zoom out'],
|
||||
['core_view_action_new_folder', 'New folder'],
|
||||
['core_view_action_edit_label', 'Edit'],
|
||||
];
|
||||
|
||||
@@ -3,4 +3,5 @@ export default [
|
||||
['core_view_zoom_in', 'Zoom In'],
|
||||
['core_view_zoom_out', 'Zoom out'],
|
||||
['core_view_action_new_folder', 'New folder'],
|
||||
['core_view_action_edit_label', 'Edit'],
|
||||
];
|
||||
|
||||
@@ -3,4 +3,5 @@ export default [
|
||||
['core_view_zoom_in', 'Приблизить'],
|
||||
['core_view_zoom_out', 'Отдалить'],
|
||||
['core_view_action_new_folder', 'Создать папку'],
|
||||
['core_view_action_edit_label', 'Изменить'],
|
||||
];
|
||||
|
||||
@@ -19,7 +19,7 @@ import { ActionService, ACTION_SAVE, DATA_CONTEXT_MENU, MenuService } from '@clo
|
||||
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
||||
import { NavResourceNodeService, ResourceManagerService, getResourceKeyFromNodeId } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { ResourceManagerScriptsService, SaveScriptDialog, SCRIPTS_TYPE_ID } from '@cloudbeaver/plugin-resource-manager-scripts';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE, ESqlDataSourceFeatures, getSqlEditorName, ISqlDataSource, SqlDataSourceService, SqlEditorSettingsService, SQL_EDITOR_ACTIONS_MENU } from '@cloudbeaver/plugin-sql-editor';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE, ESqlDataSourceFeatures, getSqlEditorName, ISqlDataSource, SqlDataSourceService, SqlEditorSettingsService, MemorySqlDataSource, LocalStorageSqlDataSource, SQL_EDITOR_TOOLS_MENU } from '@cloudbeaver/plugin-sql-editor';
|
||||
import { isSQLEditorTab, SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab';
|
||||
|
||||
import { ResourceSqlDataSource } from './ResourceSqlDataSource';
|
||||
@@ -66,7 +66,12 @@ export class PluginBootstrap extends Bootstrap {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(this.sqlDataSourceService.get(state.editorId) instanceof ResourceSqlDataSource);
|
||||
const dataSource = this.sqlDataSourceService.get(state.editorId);
|
||||
|
||||
return (
|
||||
dataSource instanceof MemorySqlDataSource
|
||||
|| dataSource instanceof LocalStorageSqlDataSource
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -182,7 +187,7 @@ export class PluginBootstrap extends Bootstrap {
|
||||
|
||||
return (
|
||||
this.resourceManagerService.enabled
|
||||
&& context.get(DATA_CONTEXT_MENU) === SQL_EDITOR_ACTIONS_MENU
|
||||
&& context.get(DATA_CONTEXT_MENU) === SQL_EDITOR_TOOLS_MENU
|
||||
&& !!dataSource?.hasFeature(ESqlDataSourceFeatures.script)
|
||||
);
|
||||
},
|
||||
|
||||
+12
-5
@@ -89,14 +89,18 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
|
||||
get features():ESqlDataSourceFeatures[] {
|
||||
if (this.isReadonly()) {
|
||||
return [ESqlDataSourceFeatures.script];
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.query, ESqlDataSourceFeatures.executable];
|
||||
}
|
||||
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.setName];
|
||||
return [
|
||||
ESqlDataSourceFeatures.script,
|
||||
ESqlDataSourceFeatures.query,
|
||||
ESqlDataSourceFeatures.executable,
|
||||
ESqlDataSourceFeatures.setName,
|
||||
];
|
||||
}
|
||||
|
||||
private _script: string;
|
||||
private saved: boolean;
|
||||
private actions?: IResourceActions;
|
||||
private info?: IResourceInfo;
|
||||
private lastAction?: () => Promise<void>;
|
||||
@@ -114,7 +118,6 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
super();
|
||||
this.state = state;
|
||||
this._script = '';
|
||||
this.saved = true;
|
||||
this.loaded = false;
|
||||
this.resourceUseKeyId = null;
|
||||
this.scheduler = new TaskScheduler(() => true);
|
||||
@@ -207,7 +210,6 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
|
||||
this._script = script;
|
||||
super.setScript(script);
|
||||
this.saved = false;
|
||||
|
||||
if (previous !== script) {
|
||||
this.debouncedWrite();
|
||||
@@ -347,6 +349,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
this.markUpdated();
|
||||
this.loaded = true;
|
||||
super.setScript(this.script);
|
||||
this.saved = true;
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
@@ -377,6 +380,10 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
});
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
await this.write();
|
||||
}
|
||||
|
||||
private async saveProperties() {
|
||||
await this.scheduler.schedule(undefined, async () => {
|
||||
if (!this.actions || !this.resourceKey) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
objectSchemaSetter,
|
||||
} from '@cloudbeaver/core-connections';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { Executor, ExecutorInterrupter, IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { objectNavNodeProvider, NodeManagerUtils, NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
@@ -61,7 +62,8 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
private readonly navNodeInfoResource: NavNodeInfoResource,
|
||||
private readonly sqlDataSourceService: SqlDataSourceService,
|
||||
private readonly connectionsManagerService: ConnectionsManagerService,
|
||||
private readonly containerResource: ContainerResource
|
||||
private readonly containerResource: ContainerResource,
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -498,10 +500,32 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
private async handleCanTabClose(editorTab: ITab<ISqlEditorTabState>) {
|
||||
const canCloseTabs = await this.sqlResultTabsService.canCloseResultTabs(editorTab.handlerState);
|
||||
|
||||
if (canCloseTabs) {
|
||||
const contexts = await this.onCanClose.execute(editorTab);
|
||||
if (ExecutorInterrupter.isInterrupted(contexts)) {
|
||||
if (!canCloseTabs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contexts = await this.onCanClose.execute(editorTab);
|
||||
if (ExecutorInterrupter.isInterrupted(contexts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);
|
||||
|
||||
if (dataSource?.isSaved() === false) {
|
||||
const result = await this.commonDialogService.open(ConfirmationDialog, {
|
||||
title: 'plugin_sql_editor_navigation_tab_data_source_save_confirmation_title',
|
||||
subTitle: dataSource.name ?? undefined,
|
||||
message: 'plugin_sql_editor_navigation_tab_data_source_save_confirmation_message',
|
||||
confirmActionText: 'ui_yes',
|
||||
extraStatus: 'no',
|
||||
});
|
||||
|
||||
if (result === DialogueStateResult.Rejected) {
|
||||
return false;
|
||||
} else if (result === DialogueStateResult.Resolved) {
|
||||
await dataSource.save();
|
||||
} else {
|
||||
await dataSource.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,4 +2,6 @@ export default [
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new', 'SQL'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip', 'Open SQL Editor'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip_context', 'Open SQL Editor for {arg:connection}'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_title', 'Save changes'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_message', 'Do you want to save changes?'],
|
||||
];
|
||||
|
||||
@@ -2,4 +2,6 @@ export default [
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new', 'SQL'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip', 'Open SQL Editor'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip_context', 'Open SQL Editor for {arg:connection}'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_title', 'Save changes'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_message', 'Do you want to save changes?'],
|
||||
];
|
||||
|
||||
@@ -2,4 +2,6 @@ export default [
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new', 'SQL'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip', 'Открыть SQL Редактор'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip_context', 'Открыть SQL Редактор для {arg:connection}'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_title', 'Сохранить изменения'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_message', 'Вы хотите сохранить изменения?'],
|
||||
];
|
||||
|
||||
@@ -2,4 +2,6 @@ export default [
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new', 'SQL'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip', 'Open SQL Editor'],
|
||||
['plugin_sql_editor_navigation_tab_action_sql_editor_new_tooltip_context', 'Open SQL Editor for {arg:connection}'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_title', 'Save changes'],
|
||||
['plugin_sql_editor_navigation_tab_data_source_save_confirmation_message', 'Do you want to save changes?'],
|
||||
];
|
||||
|
||||
@@ -22,6 +22,7 @@ import { KEY_BINDING_SQL_EDITOR_EXECUTE_SCRIPT } from './actions/bindings/KEY_BI
|
||||
import { KEY_BINDING_SQL_EDITOR_FORMAT } from './actions/bindings/KEY_BINDING_SQL_EDITOR_FORMAT';
|
||||
import { KEY_BINDING_SQL_EDITOR_SHOW_EXECUTION_PLAN } from './actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_EXECUTION_PLAN';
|
||||
import { ScriptPreviewService } from './ScriptPreview/ScriptPreviewService';
|
||||
import { ESqlDataSourceFeatures } from './SqlDataSource/ESqlDataSourceFeatures';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './SqlEditor/DATA_CONTEXT_SQL_EDITOR_DATA';
|
||||
|
||||
@injectable()
|
||||
@@ -53,6 +54,29 @@ export class MenuBootstrap extends Bootstrap {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.executable)
|
||||
&& [
|
||||
ACTION_SQL_EDITOR_EXECUTE,
|
||||
ACTION_SQL_EDITOR_EXECUTE_NEW,
|
||||
ACTION_SQL_EDITOR_EXECUTE_SCRIPT,
|
||||
ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN,
|
||||
].includes(action)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query)
|
||||
&& [
|
||||
ACTION_SQL_EDITOR_EXECUTE,
|
||||
ACTION_SQL_EDITOR_EXECUTE_NEW,
|
||||
ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN,
|
||||
].includes(action)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
[
|
||||
ACTION_SQL_EDITOR_EXECUTE,
|
||||
@@ -86,7 +110,13 @@ export class MenuBootstrap extends Bootstrap {
|
||||
this.keyBindingService.addKeyBindingHandler({
|
||||
id: 'sql-editor-execute-script',
|
||||
binding: KEY_BINDING_SQL_EDITOR_EXECUTE_SCRIPT,
|
||||
isBindingApplicable: (contexts, action) => action === ACTION_SQL_EDITOR_EXECUTE_SCRIPT,
|
||||
isBindingApplicable: (contexts, action) => {
|
||||
const sqlEditorData = contexts.tryGet(DATA_CONTEXT_SQL_EDITOR_DATA);
|
||||
return (
|
||||
action === ACTION_SQL_EDITOR_EXECUTE_SCRIPT
|
||||
&& sqlEditorData?.dataSource?.hasFeature(ESqlDataSourceFeatures.executable) === true
|
||||
);
|
||||
},
|
||||
handler: this.sqlEditorActionHandler.bind(this),
|
||||
});
|
||||
|
||||
|
||||
@@ -40,9 +40,10 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
}
|
||||
|
||||
get features(): ESqlDataSourceFeatures[] {
|
||||
return [ESqlDataSourceFeatures.script];
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.query, ESqlDataSourceFeatures.executable];
|
||||
}
|
||||
|
||||
protected saved: boolean;
|
||||
readonly history: ISqlDataSourceHistory;
|
||||
readonly onUpdate: ISyncExecutor;
|
||||
readonly onSetScript: ISyncExecutor<ISetScriptData>;
|
||||
@@ -57,6 +58,7 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
this.message = undefined;
|
||||
this.outdated = true;
|
||||
this.editing = true;
|
||||
this.saved = true;
|
||||
this.history = new SqlDataSourceHistory();
|
||||
this.onUpdate = new SyncExecutor();
|
||||
this.onSetScript = new SyncExecutor();
|
||||
@@ -83,17 +85,24 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
}
|
||||
|
||||
setScript(script: string, source?: string): void {
|
||||
this.saved = false;
|
||||
this.onSetScript.execute({ script, source });
|
||||
}
|
||||
|
||||
abstract canRename(name: string | null): boolean;
|
||||
|
||||
setName(name: string | null): void {
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
|
||||
setExecutionContext(executionContext?: IConnectionExecutionContextInfo | undefined): void {
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
|
||||
isSaved(): boolean {
|
||||
return this.saved;
|
||||
}
|
||||
|
||||
isError(): boolean {
|
||||
return isContainsException(this.exception);
|
||||
}
|
||||
@@ -146,6 +155,10 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
|
||||
save(): Promise<void> | void { }
|
||||
load(): Promise<void> | void { }
|
||||
reset(): Promise<void> | void {
|
||||
this.saved = true;
|
||||
}
|
||||
dispose(): void | Promise<void> { }
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
export enum ESqlDataSourceFeatures {
|
||||
script,
|
||||
query,
|
||||
executable,
|
||||
setName,
|
||||
setProject
|
||||
}
|
||||
@@ -40,6 +40,7 @@ export interface ISqlDataSource extends ILoadableState {
|
||||
readonly features: ESqlDataSourceFeatures[];
|
||||
readonly history: ISqlDataSourceHistory;
|
||||
|
||||
isSaved(): boolean;
|
||||
isReadonly(): boolean;
|
||||
isEditing(): boolean;
|
||||
isOutdated(): boolean;
|
||||
@@ -52,6 +53,8 @@ export interface ISqlDataSource extends ILoadableState {
|
||||
setScript(script: string, source?: string): void;
|
||||
setEditing(state: boolean): void;
|
||||
setExecutionContext(executionContext?: IConnectionExecutionContextInfo): void;
|
||||
save(): Promise<void> | void;
|
||||
load(): Promise<void> | void;
|
||||
reset(): Promise<void> | void;
|
||||
dispose(): Promise<void> | void;
|
||||
}
|
||||
+10
-1
@@ -30,7 +30,12 @@ export class LocalStorageSqlDataSource extends BaseSqlDataSource {
|
||||
}
|
||||
|
||||
get features(): ESqlDataSourceFeatures[] {
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.setName];
|
||||
return [
|
||||
ESqlDataSourceFeatures.script,
|
||||
ESqlDataSourceFeatures.query,
|
||||
ESqlDataSourceFeatures.executable,
|
||||
ESqlDataSourceFeatures.setName,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly state: ILocalStorageSqlDataSourceState;
|
||||
@@ -47,6 +52,10 @@ export class LocalStorageSqlDataSource extends BaseSqlDataSource {
|
||||
});
|
||||
}
|
||||
|
||||
isSaved(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,12 @@ export class MemorySqlDataSource extends BaseSqlDataSource {
|
||||
}
|
||||
|
||||
get features(): ESqlDataSourceFeatures[] {
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.setName];
|
||||
return [
|
||||
ESqlDataSourceFeatures.script,
|
||||
ESqlDataSourceFeatures.query,
|
||||
ESqlDataSourceFeatures.executable,
|
||||
ESqlDataSourceFeatures.setName,
|
||||
];
|
||||
}
|
||||
|
||||
private _name: string | null;
|
||||
@@ -55,6 +60,10 @@ export class MemorySqlDataSource extends BaseSqlDataSource {
|
||||
});
|
||||
}
|
||||
|
||||
isSaved(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -44,9 +44,7 @@ export const SQL_EDITOR_ACTIONS_MENU_STYLES = css`
|
||||
}
|
||||
|
||||
& menu-bar-item-label {
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
display: none;
|
||||
}
|
||||
|
||||
& IconOrImage + menu-bar-item-label, & Loader + menu-bar-item-label {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const SQL_EDITOR_TOOLS_MENU = createMenu('sql-editor-tools', '');
|
||||
@@ -20,6 +20,7 @@ import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures'
|
||||
import { ISqlEditorModeProps, SqlEditorModeService } from '../SqlEditorModeService';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './DATA_CONTEXT_SQL_EDITOR_DATA';
|
||||
import type { ISqlEditorProps } from './ISqlEditorProps';
|
||||
import { SqlEditorActionsMenu } from './SqlEditorActionsMenu';
|
||||
import { SqlEditorTools } from './SqlEditorTools';
|
||||
import { useSqlEditor } from './useSqlEditor';
|
||||
|
||||
@@ -53,7 +54,7 @@ const styles = css`
|
||||
user-select: none;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,10 +125,16 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
const displayedEditors = getComputed(() => sqlEditorModeService.tabsContainer.getDisplayed({ state, data }).length);
|
||||
|
||||
useEffect(() => {
|
||||
split.fixate('maximize', displayedEditors === 0);
|
||||
if (displayedEditors === 0) {
|
||||
split.fixate('maximize', true);
|
||||
} else if (split.state.disable) {
|
||||
split.fixate('resize', false);
|
||||
split.state.setSize(-1);
|
||||
}
|
||||
});
|
||||
|
||||
const isScript = data.dataSource?.hasFeature(ESqlDataSourceFeatures.script);
|
||||
const isQuery = data.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = data.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
return styled(styles, BASE_TAB_STYLES, VERTICAL_ROTATED_TAB_STYLES, tabStyles)(
|
||||
<TabsState
|
||||
@@ -142,22 +149,26 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
<sql-editor className={className}>
|
||||
<container>
|
||||
<actions onMouseDown={preventFocusHandler}>
|
||||
{isScript && (
|
||||
{isExecutable && (
|
||||
<>
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_button_tooltip')}
|
||||
onClick={data.executeQuery}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_exec.svg" />
|
||||
</button>
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_new_tab_button_tooltip')}
|
||||
onClick={data.executeQueryNewTab}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_exec_new.svg" />
|
||||
</button>
|
||||
{isQuery && (
|
||||
<>
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_button_tooltip')}
|
||||
onClick={data.executeQuery}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_exec.svg" />
|
||||
</button>
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_new_tab_button_tooltip')}
|
||||
onClick={data.executeQueryNewTab}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_exec_new.svg" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_script_button_tooltip')}
|
||||
@@ -166,7 +177,7 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
>
|
||||
<StaticImage icon="/icons/sql_script_exec.svg" />
|
||||
</button>
|
||||
{data.dialect?.supportsExplainExecutionPlan && (
|
||||
{isQuery && data.dialect?.supportsExplainExecutionPlan && (
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_execution_plan_button_tooltip')}
|
||||
@@ -177,6 +188,7 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<SqlEditorActionsMenu state={state} />
|
||||
</actions>
|
||||
<SqlEditorTools data={data} state={state} style={styles} />
|
||||
</container>
|
||||
|
||||
@@ -28,6 +28,6 @@ export const SqlEditorActionsMenu = observer<Props>(function SqlEditorActionsMen
|
||||
menu.context.set(DATA_CONTEXT_SQL_EDITOR_STATE, state);
|
||||
|
||||
return (
|
||||
<MenuBar style={[SQL_EDITOR_ACTIONS_MENU_STYLES, style]} menu={menu} />
|
||||
<MenuBar style={[SQL_EDITOR_ACTIONS_MENU_STYLES, ...[style].flat(2)]} menu={menu} />
|
||||
);
|
||||
});
|
||||
@@ -15,7 +15,7 @@ import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures';
|
||||
import type { ISQLEditorData } from './ISQLEditorData';
|
||||
import { SqlEditorActionsMenu } from './SqlEditorActionsMenu';
|
||||
import { SqlEditorToolsMenu } from './SqlEditorToolsMenu';
|
||||
import { useTools } from './useTools';
|
||||
|
||||
const styles = css`
|
||||
@@ -73,19 +73,17 @@ export const SqlEditorTools = observer<Props>(function SqlEditorTools({
|
||||
|
||||
return styled(useStyles(style, styles))(
|
||||
<tools className={className} onMouseDown={preventFocusHandler}>
|
||||
<SqlEditorActionsMenu state={state} />
|
||||
<SqlEditorToolsMenu state={state} />
|
||||
{isScript && (
|
||||
<>
|
||||
{!data.readonly && (
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_format_button_tooltip')}
|
||||
hidden={isActiveSegmentMode}
|
||||
onClick={data.formatScript}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_format_sm.svg" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
disabled={disabled || data.readonly}
|
||||
title={translate('sql_editor_sql_format_button_tooltip')}
|
||||
hidden={isActiveSegmentMode}
|
||||
onClick={data.formatScript}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_format_sm.svg" />
|
||||
</button>
|
||||
<button
|
||||
disabled={scriptEmpty}
|
||||
title={translate('sql_editor_download_script_tooltip')}
|
||||
@@ -94,14 +92,15 @@ export const SqlEditorTools = observer<Props>(function SqlEditorTools({
|
||||
>
|
||||
<StaticImage icon='/icons/export.svg' />
|
||||
</button>
|
||||
{!isActiveSegmentMode && !data.readonly && (
|
||||
{!isActiveSegmentMode && (
|
||||
<UploadArea
|
||||
accept='.sql'
|
||||
title={translate('sql_editor_upload_script_tooltip')}
|
||||
disabled={data.readonly}
|
||||
reset
|
||||
onChange={handleScriptUpload}
|
||||
>
|
||||
<upload>
|
||||
<upload disabled={data.readonly}>
|
||||
<StaticImage icon='/icons/import.svg' />
|
||||
</upload>
|
||||
</UploadArea>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { MenuBar } from '@cloudbeaver/core-ui';
|
||||
import { IDataContext, useMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../DATA_CONTEXT_SQL_EDITOR_STATE';
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { SQL_EDITOR_ACTIONS_MENU_STYLES } from './SQL_EDITOR_ACTIONS_MENU_STYLES';
|
||||
import { SQL_EDITOR_TOOLS_MENU } from './SQL_EDITOR_TOOLS_MENU';
|
||||
|
||||
interface Props {
|
||||
state: ISqlEditorTabState;
|
||||
context?: IDataContext;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
export const SqlEditorToolsMenu = observer<Props>(function SqlEditorToolsMenu({ state, context, style }) {
|
||||
const menu = useMenu({ menu: SQL_EDITOR_TOOLS_MENU, context });
|
||||
menu.context.set(DATA_CONTEXT_SQL_EDITOR_STATE, state);
|
||||
|
||||
return (
|
||||
<MenuBar style={[SQL_EDITOR_ACTIONS_MENU_STYLES, ...[style].flat(2)]} menu={menu} />
|
||||
);
|
||||
});
|
||||
@@ -268,6 +268,12 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
async executeQuery(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isQuery || !isExecutable) {
|
||||
return;
|
||||
}
|
||||
const query = this.getSubQuery();
|
||||
|
||||
await this.executeQueryAction(
|
||||
@@ -296,6 +302,12 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
async executeQueryNewTab(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isQuery || !isExecutable) {
|
||||
return;
|
||||
}
|
||||
const query = this.getSubQuery();
|
||||
|
||||
await this.executeQueryAction(
|
||||
@@ -309,7 +321,10 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
async showExecutionPlan(): Promise<void> {
|
||||
if (!this.dialect?.supportsExplainExecutionPlan) {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isQuery || !isExecutable || !this.dialect?.supportsExplainExecutionPlan) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -329,7 +344,9 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
async executeScript(): Promise<void> {
|
||||
if (this.isDisabled || this.isScriptEmpty) {
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isExecutable || this.isDisabled || this.isScriptEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ const styles = css`
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
TabList:not([|script]) tab-outer:only-child {
|
||||
TabList:not([|executable]) tab-outer:only-child {
|
||||
display: none;
|
||||
}
|
||||
TextPlaceholder {
|
||||
@@ -91,11 +91,22 @@ export const SqlResultTabs = observer<Props>(function SqlDataResult({ state, onT
|
||||
}
|
||||
}
|
||||
|
||||
function handleCanClose(tab: ITabData): boolean {
|
||||
const resultTab = state.resultTabs.find(tabState => tabState.tabId === tab.tabId);
|
||||
|
||||
if (resultTab) {
|
||||
const group = state.resultGroups.find(group => group.groupId === resultTab.groupId)!;
|
||||
|
||||
return dataSource?.databaseModels.some(model => model.id === group.modelId) !== true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!state.tabs.length) {
|
||||
return styled(style)(<TextPlaceholder>{translate(dataSource?.emptyPlaceholder ?? 'sql_editor_placeholder')}</TextPlaceholder>);
|
||||
}
|
||||
|
||||
const script = dataSource?.hasFeature(ESqlDataSourceFeatures.script);
|
||||
const executable = dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
const tabList = orderedTabs.map(tab => tab.id);
|
||||
|
||||
return styled(style)(
|
||||
@@ -103,11 +114,12 @@ export const SqlResultTabs = observer<Props>(function SqlDataResult({ state, onT
|
||||
<TabsState
|
||||
currentTabId={state.currentTabId}
|
||||
tabList={tabList}
|
||||
canClose={handleCanClose}
|
||||
enabledBaseActions
|
||||
onChange={handleSelect}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<TabList aria-label='SQL Results' {...use({ script })} style={styles}>
|
||||
<TabList aria-label='SQL Results' {...use({ executable })} style={styles}>
|
||||
{orderedTabs.map(result => (
|
||||
<SqlResultTab
|
||||
key={result.id}
|
||||
|
||||
@@ -13,5 +13,6 @@ export const ACTION_SQL_EDITOR_EXECUTE_SCRIPT = createAction(
|
||||
{
|
||||
icon: '/icons/sql_script_exec.svg',
|
||||
label: 'sql_editor_sql_execution_script_button_tooltip',
|
||||
tooltip: 'sql_editor_sql_execution_script_button_tooltip',
|
||||
}
|
||||
);
|
||||
|
||||
@@ -20,6 +20,8 @@ export * from './SqlDataSource/MemorySqlDataSource';
|
||||
export * from './SqlEditor/SQLCodeEditor/SQLCodeEditorLoader';
|
||||
export * from './SqlEditor/ISQLEditorData';
|
||||
export * from './SqlEditor/DATA_CONTEXT_SQL_EDITOR_DATA';
|
||||
export * from './SqlEditor/SQL_EDITOR_ACTIONS_MENU';
|
||||
export * from './SqlEditor/SQL_EDITOR_TOOLS_MENU';
|
||||
export * from './SqlEditor/SQLEditorModeContext';
|
||||
export * from './SqlResultTabs/DATA_CONTEXT_SQL_EDITOR_RESULT_ID';
|
||||
export * from './SqlResultTabs/SqlResultTabsService';
|
||||
@@ -32,7 +34,6 @@ export * from './SqlEditor';
|
||||
export * from './SqlEditorModeService';
|
||||
export * from './SqlEditorService';
|
||||
export * from './SqlEditorSettingsService';
|
||||
export * from './SqlEditor/SQL_EDITOR_ACTIONS_MENU';
|
||||
export * from './SqlGenerators/SqlGeneratorsResource';
|
||||
export * from './SqlGenerators/SqlGeneratorsBootstrap';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user