diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index efc2f517a51..4dacef8d244 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -44,6 +44,7 @@ export default [ ['ui_error_message', 'Error:'], ['ui_error_close', 'Close'], ['ui_clear', 'Clear'], + ['ui_file_name', 'File name'], ['ui_remove', 'Remove'], ['ui_close', 'Close'], ['ui_open', 'Open'], diff --git a/webapp/packages/core-localization/src/locales/fr.ts b/webapp/packages/core-localization/src/locales/fr.ts index 64e87931457..55f8f708ccc 100644 --- a/webapp/packages/core-localization/src/locales/fr.ts +++ b/webapp/packages/core-localization/src/locales/fr.ts @@ -42,6 +42,7 @@ export default [ ['ui_error_message', 'Erreur :'], ['ui_error_close', 'Fermer'], ['ui_clear', 'Effacer'], + ['ui_file_name', 'Nom du fichier'], ['ui_remove', 'Supprimer'], ['ui_close', 'Fermer'], ['ui_open', 'Ouvrir'], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index ee66b61fc4f..61ebdf0e056 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -42,6 +42,7 @@ export default [ ['ui_error_message', 'Errore:'], ['ui_error_close', 'Chiudi'], ['ui_clear', 'Clear'], + ['ui_file_name', 'Nome del file'], ['ui_remove', 'Remove'], ['ui_close', 'Chiudi'], ['ui_open', 'Open'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 617f81f1191..0fea0e6c32d 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -41,6 +41,7 @@ export default [ ['ui_error_message', 'Ошибка:'], ['ui_error_close', 'Закрыть'], ['ui_clear', 'Очистить'], + ['ui_file_name', 'Имя файла'], ['ui_remove', 'Убрать'], ['ui_close', 'Закрыть'], ['ui_open', 'Открыть'], diff --git a/webapp/packages/core-localization/src/locales/vi.ts b/webapp/packages/core-localization/src/locales/vi.ts index b096edd5acf..3cf2da8ca2b 100644 --- a/webapp/packages/core-localization/src/locales/vi.ts +++ b/webapp/packages/core-localization/src/locales/vi.ts @@ -44,6 +44,7 @@ export default [ ['ui_error_message', 'Lỗi:'], ['ui_error_close', 'Đóng'], ['ui_clear', 'Xóa'], + ['ui_file_name', 'Tên tệp'], ['ui_remove', 'Xóa'], ['ui_close', 'Đóng'], ['ui_open', 'Mở'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index 942eb2bf951..61928e424d9 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -42,6 +42,7 @@ export default [ ['ui_error_message', '错误:'], ['ui_error_close', '关闭'], ['ui_clear', '清除'], + ['ui_file_name', '文件名'], ['ui_remove', '移除'], ['ui_close', '关闭'], ['ui_open', '打开'], diff --git a/webapp/packages/core-ui/src/Tabs/TabPanelList.tsx b/webapp/packages/core-ui/src/Tabs/TabPanelList.tsx index 7099c61dc6d..0363578ac55 100644 --- a/webapp/packages/core-ui/src/Tabs/TabPanelList.tsx +++ b/webapp/packages/core-ui/src/Tabs/TabPanelList.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -15,9 +15,10 @@ import { TabsContext } from './TabsContext.js'; export interface TabPanelListProps { contents?: boolean; + className?: string; } -export const TabPanelList = observer>(function TabPanelList({ contents, children }) { +export const TabPanelList = observer>(function TabPanelList({ contents, children, className }) { const state = useContext(TabsContext); if (!state) { @@ -41,7 +42,7 @@ export const TabPanelList = observer> .map( generateTabElement( (tabInfo, key) => ( - + {renderPanel(tabInfo, key)} ), diff --git a/webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeData.ts b/webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeData.ts index 38044011180..33d2a230624 100644 --- a/webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeData.ts +++ b/webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeData.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -122,7 +122,7 @@ export function useTreeData(options: IOptions): ITreeData { const nodeId = nodes.shift()!; const state = this.state.getState(nodeId); - if (!state.expanded) { + if (!state.expanded && nodeId !== this.rootId) { continue; } diff --git a/webapp/packages/plugin-script-export/package.json b/webapp/packages/plugin-script-export/package.json new file mode 100644 index 00000000000..373ddf01636 --- /dev/null +++ b/webapp/packages/plugin-script-export/package.json @@ -0,0 +1,47 @@ +{ + "name": "@cloudbeaver/plugin-script-export", + "type": "module", + "sideEffects": [ + "./lib/module.js", + "./lib/index.js", + "src/**/*.css", + "src/**/*.scss", + "public/**/*" + ], + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "exports": { + ".": "./lib/index.js", + "./module": "./lib/module.js" + }, + "scripts": { + "build": "tsc -b", + "clean": "rimraf --glob lib", + "lint": "eslint ./src/ --ext .ts,.tsx", + "test": "dbeaver-test", + "validate-dependencies": "core-cli-validate-dependencies" + }, + "dependencies": { + "@cloudbeaver/core-blocks": "workspace:*", + "@cloudbeaver/core-di": "workspace:*", + "@cloudbeaver/core-dialogs": "workspace:*", + "@cloudbeaver/core-localization": "workspace:*", + "@cloudbeaver/core-ui": "workspace:*", + "mobx": "^6", + "mobx-react-lite": "^4", + "react": "^19", + "react-dom": "^19", + "tslib": "^2" + }, + "devDependencies": { + "@cloudbeaver/core-cli": "workspace:*", + "@cloudbeaver/tsconfig": "workspace:*", + "@dbeaver/cli": "workspace:*", + "@types/react": "^19", + "@types/react-dom": "^19", + "rimraf": "^6", + "tslib": "^2", + "typescript": "^5" + } +} diff --git a/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialog.tsx b/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialog.tsx new file mode 100644 index 00000000000..67e083ddd5f --- /dev/null +++ b/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialog.tsx @@ -0,0 +1,77 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { useMemo, useState } from 'react'; + +import { + Button, + CommonDialogBody, + CommonDialogFooter, + CommonDialogHeader, + CommonDialogWrapper, + Fill, + Form, + Translate, + useForm, +} from '@cloudbeaver/core-blocks'; +import type { DialogComponent } from '@cloudbeaver/core-dialogs'; +import { useService } from '@cloudbeaver/core-di'; + +import { ScriptExportService, type IScriptExportTabProps } from '../ScriptExportService.js'; +import { TabList, TabPanelList, TabsState } from '@cloudbeaver/core-ui'; +import { ExportScriptDialogContext, type IExportScriptDialogContext } from './ExportScriptDialogContext.js'; + +export const ExportScriptDialog: DialogComponent = observer(function ExportScriptDialog({ + payload, + resolveDialog, + rejectDialog, + className, +}) { + const scriptExportService = useService(ScriptExportService); + const [selectedTabId, setSelectedTabId] = useState(undefined); + const form = useForm(); + + const context = useMemo( + () => ({ + resolveDialog, + rejectDialog, + }), + [resolveDialog, rejectDialog], + ); + + return ( + +
+ + + setSelectedTabId(tab.tabId)} + {...payload} + > + + + + + + + + + + + +
+
+ ); +}); diff --git a/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialogContext.ts b/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialogContext.ts new file mode 100644 index 00000000000..298f0534661 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialogContext.ts @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { createContext } from 'react'; + +export interface IExportScriptDialogContext { + resolveDialog: () => void; + rejectDialog: () => void; +} + +export const ExportScriptDialogContext = createContext(null); diff --git a/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialogLazy.ts b/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialogLazy.ts new file mode 100644 index 00000000000..25d141e8009 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/ExportScriptDialog/ExportScriptDialogLazy.ts @@ -0,0 +1,11 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 } from '@cloudbeaver/core-blocks'; + +export const ExportScriptDialog = importLazyComponent(() => import('./ExportScriptDialog.js').then(module => module.ExportScriptDialog)); diff --git a/webapp/packages/plugin-script-export/src/LocaleService.ts b/webapp/packages/plugin-script-export/src/LocaleService.ts new file mode 100644 index 00000000000..f0b78f32e81 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/LocaleService.ts @@ -0,0 +1,37 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { LocalizationService } from '@cloudbeaver/core-localization'; + +@injectable(() => [LocalizationService]) +export class LocaleService extends Bootstrap { + constructor(private readonly localizationService: LocalizationService) { + super(); + + this.localizationService.addProvider(this.provider.bind(this)); + } + + private async provider(locale: string) { + switch (locale) { + case 'ru': + return (await import('./locales/ru.js')).default; + case 'it': + return (await import('./locales/it.js')).default; + case 'zh': + return (await import('./locales/zh.js')).default; + case 'fr': + return (await import('./locales/fr.js')).default; + case 'vi': + return (await import('./locales/vi.js')).default; + case 'en': + default: + return (await import('./locales/en.js')).default; + } + } +} diff --git a/webapp/packages/plugin-script-export/src/ScriptExportService.ts b/webapp/packages/plugin-script-export/src/ScriptExportService.ts new file mode 100644 index 00000000000..64bec433441 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/ScriptExportService.ts @@ -0,0 +1,32 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 '@cloudbeaver/core-di'; +import { TabsContainer } from '@cloudbeaver/core-ui'; +import { CommonDialogService } from '@cloudbeaver/core-dialogs'; +import { ExportScriptDialog } from './ExportScriptDialog/ExportScriptDialogLazy.js'; + +export interface IScriptExportTabProps { + script: string; + fileName: string; + editorId: string; + projectId?: string; + connectionId?: string; +} + +@injectable(() => [CommonDialogService]) +export class ScriptExportService { + readonly tabsContainer: TabsContainer; + + constructor(private readonly commonDialogService: CommonDialogService) { + this.tabsContainer = new TabsContainer('script_export_tabs'); + } + + openExportDialog(props: IScriptExportTabProps) { + return this.commonDialogService.open(ExportScriptDialog, props); + } +} diff --git a/webapp/packages/plugin-script-export/src/index.ts b/webapp/packages/plugin-script-export/src/index.ts new file mode 100644 index 00000000000..00f28d7e20e --- /dev/null +++ b/webapp/packages/plugin-script-export/src/index.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export * from './ScriptExportService.js'; +export * from './ExportScriptDialog/ExportScriptDialogLazy.js'; +export * from './ExportScriptDialog/ExportScriptDialogContext.js'; + +import './module.js'; diff --git a/webapp/packages/plugin-script-export/src/locales/en.ts b/webapp/packages/plugin-script-export/src/locales/en.ts new file mode 100644 index 00000000000..14572066da4 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/locales/en.ts @@ -0,0 +1 @@ +export default [['plugin_script_export_dialog_title', 'Export SQL Script']]; diff --git a/webapp/packages/plugin-script-export/src/locales/fr.ts b/webapp/packages/plugin-script-export/src/locales/fr.ts new file mode 100644 index 00000000000..e5cf6ddcf28 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/locales/fr.ts @@ -0,0 +1 @@ +export default [['plugin_script_export_dialog_title', 'Exporter le script SQL']]; diff --git a/webapp/packages/plugin-script-export/src/locales/it.ts b/webapp/packages/plugin-script-export/src/locales/it.ts new file mode 100644 index 00000000000..3c1577e0b0c --- /dev/null +++ b/webapp/packages/plugin-script-export/src/locales/it.ts @@ -0,0 +1 @@ +export default [['plugin_script_export_dialog_title', 'Esporta script SQL']]; diff --git a/webapp/packages/plugin-script-export/src/locales/ru.ts b/webapp/packages/plugin-script-export/src/locales/ru.ts new file mode 100644 index 00000000000..32860a9a722 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/locales/ru.ts @@ -0,0 +1 @@ +export default [['plugin_script_export_dialog_title', 'Экспорт SQL-скрипта']]; diff --git a/webapp/packages/plugin-script-export/src/locales/vi.ts b/webapp/packages/plugin-script-export/src/locales/vi.ts new file mode 100644 index 00000000000..274e9153b9b --- /dev/null +++ b/webapp/packages/plugin-script-export/src/locales/vi.ts @@ -0,0 +1 @@ +export default [['plugin_script_export_dialog_title', 'Xuất tập lệnh SQL']]; diff --git a/webapp/packages/plugin-script-export/src/locales/zh.ts b/webapp/packages/plugin-script-export/src/locales/zh.ts new file mode 100644 index 00000000000..78116618f2a --- /dev/null +++ b/webapp/packages/plugin-script-export/src/locales/zh.ts @@ -0,0 +1 @@ +export default [['plugin_script_export_dialog_title', '导出 SQL 脚本']]; diff --git a/webapp/packages/plugin-script-export/src/module.ts b/webapp/packages/plugin-script-export/src/module.ts new file mode 100644 index 00000000000..5fe4eae2746 --- /dev/null +++ b/webapp/packages/plugin-script-export/src/module.ts @@ -0,0 +1,19 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { Bootstrap, ModuleRegistry } from '@cloudbeaver/core-di'; +import { LocaleService } from './LocaleService.js'; +import { ScriptExportService } from './ScriptExportService.js'; + +export default ModuleRegistry.add({ + name: '@cloudbeaver/plugin-script-export', + + configure: serviceCollection => { + serviceCollection.addSingleton(Bootstrap, LocaleService).addSingleton(ScriptExportService); + }, +}); diff --git a/webapp/packages/plugin-script-export/tsconfig.json b/webapp/packages/plugin-script-export/tsconfig.json new file mode 100644 index 00000000000..0d12cd0a9be --- /dev/null +++ b/webapp/packages/plugin-script-export/tsconfig.json @@ -0,0 +1,46 @@ +{ + "extends": "@cloudbeaver/tsconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "tsBuildInfoFile": "lib/tsconfig.tsbuildinfo", + "composite": true + }, + "references": [ + { + "path": "../../common-typescript/@dbeaver/cli" + }, + { + "path": "../core-blocks" + }, + { + "path": "../core-cli" + }, + { + "path": "../core-di" + }, + { + "path": "../core-di/tsconfig.json" + }, + { + "path": "../core-dialogs" + }, + { + "path": "../core-localization" + }, + { + "path": "../core-ui" + } + ], + "include": [ + "__custom_mocks__/**/*", + "src/**/*", + "src/**/*.json", + "src/**/*.css", + "src/**/*.scss" + ], + "exclude": [ + "**/node_modules", + "lib/**/*" + ] +} diff --git a/webapp/packages/plugin-set-common/package.json b/webapp/packages/plugin-set-common/package.json index df2262d75e0..34159bb69fc 100644 --- a/webapp/packages/plugin-set-common/package.json +++ b/webapp/packages/plugin-set-common/package.json @@ -102,6 +102,7 @@ "@cloudbeaver/plugin-resource-manager-administration": "workspace:*", "@cloudbeaver/plugin-resource-manager-scripts": "workspace:*", "@cloudbeaver/plugin-root": "workspace:*", + "@cloudbeaver/plugin-script-export": "workspace:*", "@cloudbeaver/plugin-session-expiration": "workspace:*", "@cloudbeaver/plugin-settings-administration": "workspace:*", "@cloudbeaver/plugin-settings-default-administration": "workspace:*", diff --git a/webapp/packages/plugin-set-common/src/index.ts b/webapp/packages/plugin-set-common/src/index.ts index b493f11bb3a..67582133f79 100644 --- a/webapp/packages/plugin-set-common/src/index.ts +++ b/webapp/packages/plugin-set-common/src/index.ts @@ -111,6 +111,7 @@ import pluginSqlAsyncTaskConfirmation from '@cloudbeaver/plugin-sql-async-task-c import pluginDataViewerConditionalFormatting from '@cloudbeaver/plugin-data-viewer-conditional-formatting/module'; import pluginConnectionView from '@cloudbeaver/plugin-connection-view/module'; import pluginConnectionPreferences from '@cloudbeaver/plugin-connection-preferences/module'; +import pluginScriptExport from '@cloudbeaver/plugin-script-export/module'; const core = [ coreRouting, // important, should be first because the router starts in load phase first after all plugins register phase @@ -171,6 +172,7 @@ export const commonSet = [ pluginGisViewer, pluginDdlViewer, pluginObjectViewer, + pluginScriptExport, pluginSqlEditor, pluginSqlEditorNavigationTab, pluginSqlEditorScreen, diff --git a/webapp/packages/plugin-set-common/tsconfig.json b/webapp/packages/plugin-set-common/tsconfig.json index 2c341834f0f..61dabc96cc0 100644 --- a/webapp/packages/plugin-set-common/tsconfig.json +++ b/webapp/packages/plugin-set-common/tsconfig.json @@ -250,6 +250,9 @@ { "path": "../plugin-root" }, + { + "path": "../plugin-script-export" + }, { "path": "../plugin-session-expiration" }, diff --git a/webapp/packages/plugin-sql-editor/package.json b/webapp/packages/plugin-sql-editor/package.json index 653b9e42f34..d564ae123e0 100644 --- a/webapp/packages/plugin-sql-editor/package.json +++ b/webapp/packages/plugin-sql-editor/package.json @@ -45,6 +45,7 @@ "@cloudbeaver/plugin-codemirror6": "workspace:*", "@cloudbeaver/plugin-data-viewer": "workspace:*", "@cloudbeaver/plugin-navigation-tabs": "workspace:*", + "@cloudbeaver/plugin-script-export": "workspace:*", "@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*", "@dbeaver/js-helpers": "workspace:^", "mobx": "^6", diff --git a/webapp/packages/plugin-sql-editor/src/LocalExport/LocalExportPanel.tsx b/webapp/packages/plugin-sql-editor/src/LocalExport/LocalExportPanel.tsx new file mode 100644 index 00000000000..7fac98e8fbd --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/LocalExport/LocalExportPanel.tsx @@ -0,0 +1,38 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { InputField, Translate, useForm } from '@cloudbeaver/core-blocks'; +import { ExportScriptDialogContext, type IScriptExportTabProps } from '@cloudbeaver/plugin-script-export'; +import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; +import { downloadSql } from '../downloadSql.js'; +import { withTimestamp } from '@cloudbeaver/core-utils'; +import { useState, useContext } from 'react'; + +export const LocalExportPanel: TabContainerPanelComponent = observer(function LocalExportPanel({ + script, + fileName: initialFileName, +}) { + const dialogContext = useContext(ExportScriptDialogContext); + const [fileName, setFileName] = useState(initialFileName); + + useForm({ + onSubmit() { + const fileNameWithTimestamp = withTimestamp(fileName); + downloadSql(fileNameWithTimestamp, script); + + dialogContext?.resolveDialog(); + }, + }); + + return ( + + + + ); +}); diff --git a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts index 1b95c6db3ed..8bb8c4976b3 100644 --- a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts +++ b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -8,7 +8,7 @@ import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { WindowEventsService } from '@cloudbeaver/core-root'; -import { download, getTextFileReadingProcess, throttle, withTimestamp } from '@cloudbeaver/core-utils'; +import { getTextFileReadingProcess, throttle, withTimestamp } from '@cloudbeaver/core-utils'; import { ACTION_DOWNLOAD, ACTION_REDO, @@ -28,7 +28,9 @@ import { ConnectionInfoResource, createConnectionParam, type Connection } from ' import { promptForFiles } from '@cloudbeaver/core-browser'; import { NotificationService } from '@cloudbeaver/core-events'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; -import { ConfirmationDialog } from '@cloudbeaver/core-blocks'; +import { ConfirmationDialog, importLazyComponent } from '@cloudbeaver/core-blocks'; + +import { ScriptExportService } from '@cloudbeaver/plugin-script-export'; import { ACTION_SQL_EDITOR_EXECUTE } from './actions/ACTION_SQL_EDITOR_EXECUTE.js'; import { ACTION_SQL_EDITOR_EXECUTE_NEW } from './actions/ACTION_SQL_EDITOR_EXECUTE_NEW.js'; @@ -50,6 +52,7 @@ import { SQL_EDITOR_ACTIONS_MENU } from './SqlEditor/SQL_EDITOR_ACTIONS_MENU.js' import { getSqlEditorName } from './getSqlEditorName.js'; import type { ISqlEditorTabState } from './ISqlEditorTabState.js'; import { SqlEditorSettingsService } from './SqlEditorSettingsService.js'; +import { downloadSql } from './downloadSql.js'; const SYNC_DELAY = 5 * 60 * 1000; @@ -60,6 +63,9 @@ const EXECUTIONS_ACTIONS = [ ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN, ]; +const LOCAL_EXPORT_TAB_ID = 'sql-editor-local-export-tab'; +const LocalExportPanel = importLazyComponent(() => import('./LocalExport/LocalExportPanel.js').then(module => module.LocalExportPanel)); + @injectable(() => [ MenuService, ActionService, @@ -70,6 +76,7 @@ const EXECUTIONS_ACTIONS = [ SqlEditorSettingsService, NotificationService, CommonDialogService, + ScriptExportService, ]) export class MenuBootstrap extends Bootstrap { constructor( @@ -82,6 +89,7 @@ export class MenuBootstrap extends Bootstrap { private readonly sqlEditorSettingsService: SqlEditorSettingsService, private readonly notificationService: NotificationService, private readonly commonDialogService: CommonDialogService, + private readonly scriptExportService: ScriptExportService, ) { super(); } @@ -359,6 +367,13 @@ export class MenuBootstrap extends Bootstrap { // ...items, // ], // }); + + this.scriptExportService.tabsContainer.add({ + key: LOCAL_EXPORT_TAB_ID, + name: 'plugin_sql_editor_export_local_tab', + order: 1, + panel: () => LocalExportPanel, + }); } private sqlEditorActionHandler(context: IDataContextProvider, action: IAction): void { @@ -411,7 +426,7 @@ export class MenuBootstrap extends Bootstrap { } } - private downloadSql(state: ISqlEditorTabState) { + private async downloadSql(state: ISqlEditorTabState) { const dataSource = this.sqlDataSourceService.get(state.editorId); if (!dataSource) { @@ -427,11 +442,23 @@ export class MenuBootstrap extends Bootstrap { const name = getSqlEditorName(state, dataSource, connection); const script = dataSource.script; + const hasOnlyLocalExport = + this.scriptExportService.tabsContainer.has(LOCAL_EXPORT_TAB_ID) && this.scriptExportService.tabsContainer.getDisplayed().length === 1; + + if (hasOnlyLocalExport) { + const fileName = withTimestamp(name); + + downloadSql(fileName, script); + return; + } - const blob = new Blob([script], { - type: 'application/sql', + await this.scriptExportService.openExportDialog({ + script, + fileName: name, + editorId: state.editorId, + projectId: executionContext?.projectId, + connectionId: executionContext?.connectionId, }); - download(blob, `${withTimestamp(name)}.sql`); } private async uploadSql(state: ISqlEditorTabState) { diff --git a/webapp/packages/plugin-sql-editor/src/downloadSql.ts b/webapp/packages/plugin-sql-editor/src/downloadSql.ts new file mode 100644 index 00000000000..b9ffa82748d --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/downloadSql.ts @@ -0,0 +1,34 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { download } from '@cloudbeaver/core-utils'; + +function sanitizeFilename(filename: string): string { + // Remove or replace invalid filesystem characters + return filename + .replace(/[/\\:*?"<>|]/g, '_') + .replace(/\s+/g, ' ') + .trim() + .slice(0, 255); // Max filename length for most filesystems +} + +export function downloadSql(name: string, script: string): void { + if (!name || name.trim().length === 0) { + throw new Error('Filename cannot be empty'); + } + + if (!script) { + throw new Error('Script content cannot be empty'); + } + + const sanitizedName = sanitizeFilename(name); + const blob = new Blob([script], { + type: 'application/sql', + }); + download(blob, `${sanitizedName}.sql`); +} diff --git a/webapp/packages/plugin-sql-editor/src/index.ts b/webapp/packages/plugin-sql-editor/src/index.ts index fc2ea4dd5ec..c114945951a 100644 --- a/webapp/packages/plugin-sql-editor/src/index.ts +++ b/webapp/packages/plugin-sql-editor/src/index.ts @@ -55,5 +55,5 @@ export * from './SqlEditorSettingsService.js'; export * from './SqlEditorView.js'; export * from './SqlResultTabs/SqlQueryService.js'; export * from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewService.js'; +export * from './downloadSql.js'; export type { ISqlExecutionPlanViewProps } from './SqlResultTabs/ExecutionPlan/ISqlExecutionPlanViewProps.js'; - diff --git a/webapp/packages/plugin-sql-editor/src/locales/en.ts b/webapp/packages/plugin-sql-editor/src/locales/en.ts index 5a7128a55ad..c011e45bafe 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/en.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/en.ts @@ -58,4 +58,6 @@ export default [ ['plugin_sql_editor_settings_highlight_white_space', 'Highlight white space characters'], ['plugin_sql_editor_settings_highlight_white_space_description', 'Highlight spaces, tabs and other white space characters in the SQL editor'], ['plugin_sql_editor_bind_parameters_dialog_alert_title', 'String values must be quoted'], + ['plugin_sql_editor_export_local_tab', 'to OS (local)'], + ['plugin_sql_editor_script_exported', 'Script exported successfully'], ]; diff --git a/webapp/packages/plugin-sql-editor/src/locales/fr.ts b/webapp/packages/plugin-sql-editor/src/locales/fr.ts index 8712de062b6..4147b378388 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/fr.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/fr.ts @@ -62,4 +62,6 @@ export default [ "Mettre en surbrillance les espaces, les tabulations et les autres caractères d'espace blanc dans l'éditeur SQL", ], ['plugin_sql_editor_bind_parameters_dialog_alert_title', 'Les valeurs de type chaîne doivent être entre guillemets'], + ['plugin_sql_editor_export_local_tab', 'vers OS (local)'], + ['plugin_sql_editor_script_exported', 'Script exporté avec succès'], ]; diff --git a/webapp/packages/plugin-sql-editor/src/locales/it.ts b/webapp/packages/plugin-sql-editor/src/locales/it.ts index 3d9d8e7f89a..129cf764110 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/it.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/it.ts @@ -54,4 +54,6 @@ export default [ "Mettere in evidenza spazi, tabulazioni e altri caratteri di spazio bianco nell'editor SQL", ], ['plugin_sql_editor_bind_parameters_dialog_alert_title', 'I valori stringa devono essere racchiusi tra virgolette'], + ['plugin_sql_editor_export_local_tab', 'nel sistema operativo (locale)'], + ['plugin_sql_editor_script_exported', 'Script esportato con successo'], ]; diff --git a/webapp/packages/plugin-sql-editor/src/locales/ru.ts b/webapp/packages/plugin-sql-editor/src/locales/ru.ts index ac296e672fa..47ba8142e05 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/ru.ts @@ -55,4 +55,6 @@ export default [ ['plugin_sql_editor_settings_highlight_white_space', 'Подсветить пробелы'], ['plugin_sql_editor_settings_highlight_white_space_description', 'Подсветить пробелы, табуляции и другие пробельные символы в редакторе SQL'], ['plugin_sql_editor_bind_parameters_dialog_alert_title', 'Строковые значения должны быть заключены в кавычки'], + ['plugin_sql_editor_export_local_tab', 'в ОС (локально)'], + ['plugin_sql_editor_script_exported', 'Скрипт успешно экспортирован'], ]; diff --git a/webapp/packages/plugin-sql-editor/src/locales/vi.ts b/webapp/packages/plugin-sql-editor/src/locales/vi.ts index 3701609ceea..52326d21ee3 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/vi.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/vi.ts @@ -57,4 +57,6 @@ export default [ 'Đánh dấu khoảng trắng, tab và các ký tự khoảng trắng khác trong trình soạn thảo SQL', ], ['plugin_sql_editor_bind_parameters_dialog_alert_title', 'Giá trị chuỗi phải được đặt trong dấu ngoặc kép'], + ['plugin_sql_editor_export_local_tab', 'tới hệ điều hành (cục bộ)'], + ['plugin_sql_editor_script_exported', 'Đã xuất kịch bản thành công'], ]; diff --git a/webapp/packages/plugin-sql-editor/src/locales/zh.ts b/webapp/packages/plugin-sql-editor/src/locales/zh.ts index 4cec96c9c31..6e1c7b8075e 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/zh.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/zh.ts @@ -55,4 +55,6 @@ export default [ ['plugin_sql_editor_settings_highlight_white_space', '高亮显示空白字符'], ['plugin_sql_editor_settings_highlight_white_space_description', '高亮显示SQL编辑器中的空格、制表符和其他空白字符'], ['plugin_sql_editor_bind_parameters_dialog_alert_title', '字符串值必须用引号括起来'], + ['plugin_sql_editor_export_local_tab', '到操作系统(本地)'], + ['plugin_sql_editor_script_exported', '脚本导出成功'], ]; diff --git a/webapp/packages/plugin-sql-editor/tsconfig.json b/webapp/packages/plugin-sql-editor/tsconfig.json index 80d38f2c50c..ab341966b65 100644 --- a/webapp/packages/plugin-sql-editor/tsconfig.json +++ b/webapp/packages/plugin-sql-editor/tsconfig.json @@ -85,6 +85,9 @@ { "path": "../plugin-navigation-tabs" }, + { + "path": "../plugin-script-export" + }, { "path": "../plugin-sql-editor-codemirror" } diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 40299355f0e..51cf14844a6 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -3826,6 +3826,30 @@ __metadata: languageName: unknown linkType: soft +"@cloudbeaver/plugin-script-export@workspace:*, @cloudbeaver/plugin-script-export@workspace:packages/plugin-script-export": + version: 0.0.0-use.local + resolution: "@cloudbeaver/plugin-script-export@workspace:packages/plugin-script-export" + dependencies: + "@cloudbeaver/core-blocks": "workspace:*" + "@cloudbeaver/core-cli": "workspace:*" + "@cloudbeaver/core-di": "workspace:*" + "@cloudbeaver/core-dialogs": "workspace:*" + "@cloudbeaver/core-localization": "workspace:*" + "@cloudbeaver/core-ui": "workspace:*" + "@cloudbeaver/tsconfig": "workspace:*" + "@dbeaver/cli": "workspace:*" + "@types/react": "npm:^19" + "@types/react-dom": "npm:^19" + mobx: "npm:^6" + mobx-react-lite: "npm:^4" + react: "npm:^19" + react-dom: "npm:^19" + rimraf: "npm:^6" + tslib: "npm:^2" + typescript: "npm:^5" + languageName: unknown + linkType: soft + "@cloudbeaver/plugin-session-expiration@workspace:*, @cloudbeaver/plugin-session-expiration@workspace:packages/plugin-session-expiration": version: 0.0.0-use.local resolution: "@cloudbeaver/plugin-session-expiration@workspace:packages/plugin-session-expiration" @@ -3938,6 +3962,7 @@ __metadata: "@cloudbeaver/plugin-resource-manager-administration": "workspace:*" "@cloudbeaver/plugin-resource-manager-scripts": "workspace:*" "@cloudbeaver/plugin-root": "workspace:*" + "@cloudbeaver/plugin-script-export": "workspace:*" "@cloudbeaver/plugin-session-expiration": "workspace:*" "@cloudbeaver/plugin-settings-administration": "workspace:*" "@cloudbeaver/plugin-settings-default-administration": "workspace:*" @@ -4287,6 +4312,7 @@ __metadata: "@cloudbeaver/plugin-codemirror6": "workspace:*" "@cloudbeaver/plugin-data-viewer": "workspace:*" "@cloudbeaver/plugin-navigation-tabs": "workspace:*" + "@cloudbeaver/plugin-script-export": "workspace:*" "@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" "@dbeaver/cli": "workspace:*"