Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion webapp/common-typescript/@dbeaver/js-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"vitest": "^3"
},
"dependencies": {
"async-mutex": "^0"
"async-mutex": "^0",
"p-debounce": "^4.0.0",
"p-memoize": "^8.0.0",
"quick-lru": "^7.1.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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.
*/

export { default as debouncePromise } from 'p-debounce';
10 changes: 10 additions & 0 deletions webapp/common-typescript/@dbeaver/js-helpers/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* 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.
*/

export * from './debouncePromise.js';
export * from './isDefined.js';
export * from './isNotNullDefined.js';
export * from './memoizeLast.js';
export * from './mutex.js';
19 changes: 19 additions & 0 deletions webapp/common-typescript/@dbeaver/js-helpers/src/memoizeLast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 pMemoize from 'p-memoize';
import QuickLRU from 'quick-lru';

const cacheKey = (args: unknown[]) => JSON.stringify(args);

export function memoizeLast<T extends (...a: any[]) => Promise<any>>(fn: T) {

Check warning on line 14 in webapp/common-typescript/@dbeaver/js-helpers/src/memoizeLast.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
return pMemoize(fn, {
cache: new QuickLRU({ maxSize: 5 }),
cacheKey,
});
}
1 change: 1 addition & 0 deletions webapp/packages/core-blocks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@

export * from './AuthenticationProviderLoader.js';
export * from './useAuthenticationAction.js';
export * from './CommonDialog/CommonDialog/CommonDialogBody.js';

Check failure on line 13 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogFooter.js';

Check failure on line 14 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogHeader.js';

Check failure on line 15 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogWrapper.js';

Check failure on line 16 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/ConfirmationDialog.js';

Check failure on line 17 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export { default as ConfirmationDialogStyles } from './CommonDialog/ConfirmationDialog.module.css';
export * from './CommonDialog/ConfirmationDialogDelete.js';

Check failure on line 19 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/RenameDialog.js';

Check failure on line 20 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/DialogsPortal.js';

Check failure on line 21 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()

export * from './ErrorDetailsDialog/ErrorDetailsDialog.js';

Check failure on line 23 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()

export * from './ComponentsRegistry/CRegistryLoader.js';
export * from './ComponentsRegistry/registry.js';

Check failure on line 26 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './ComponentsRegistry/CRegistryList.js';
export * from './ComponentsRegistry/IComponentsTreeNodeValidator.js';
export * from './ComponentsRegistry/useParentProps.js';
Expand All @@ -34,6 +34,7 @@
export * from './ErrorBoundary.js';
export * from './Icon.js';
export * from './useHotkeys.js';
export * from './useSync.js';

export * from './ItemList/ItemList.js';
export * from './ItemList/ItemListSearch.js';
Expand Down
59 changes: 59 additions & 0 deletions webapp/packages/core-blocks/src/useSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { mutex } from '@dbeaver/js-helpers';
import { useEffect, useState } from 'react';
import { useObjectRef } from './useObjectRef.js';

interface ISyncHook {
markOutdated(): void;
}

export function useSync(callback: () => void | Promise<void>, canSync = true): ISyncHook {
const [syncMutex] = useState(() => new mutex.Mutex());
const [outdated, setOutdated] = useState(false);
const [delayedOutdated, setDelayedOutdated] = useState(false);

function markOutdated() {
if (syncMutex.isLocked()) {
setDelayedOutdated(true);
} else {
setOutdated(true);
}
}

function markUpdated() {
if (delayedOutdated) {
setDelayedOutdated(false);
setOutdated(true);
} else {
setOutdated(false);
}
}

const data = useObjectRef(() => ({
markOutdated,
markUpdated,
}));

useEffect(() => {
if (outdated && !syncMutex.isLocked() && canSync) {
syncMutex
.runExclusive(async () => {
try {
await callback();
} finally {
data.markUpdated();
}
})
.catch(error => console.error(error));
}
});

return data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
type ISqlEditorTabState,
SQL_EDITOR_TAB_STATE_SCHEMA,
SqlDataSourceService,
SqlEditorModelService,
SqlEditorService,
SqlResultTabsService,
} from '@cloudbeaver/plugin-sql-editor';
Expand All @@ -67,6 +68,7 @@
ConnectionsManagerService,
ContainerResource,
CommonDialogService,
SqlEditorModelService,
])
export class SqlEditorTabService extends Bootstrap {
get sqlEditorTabs(): ITab<ISqlEditorTabState>[] {
Expand All @@ -89,6 +91,7 @@
private readonly connectionsManagerService: ConnectionsManagerService,
private readonly containerResource: ContainerResource,
private readonly commonDialogService: CommonDialogService,
private readonly sqlEditorModelService: SqlEditorModelService,
) {
super();

Expand Down Expand Up @@ -305,6 +308,7 @@

private async handleTabRestore(tab: ITab<ISqlEditorTabState>): Promise<boolean> {
if (!SQL_EDITOR_TAB_STATE_SCHEMA.safeParse(tab.handlerState).success) {
await this.sqlEditorModelService.destroy(tab.handlerState.editorId);
await this.sqlDataSourceService.destroy(tab.handlerState.editorId);
return false;
}
Expand Down Expand Up @@ -400,7 +404,7 @@
return true;
}

async setConnectionId(tab: ITab<ISqlEditorTabState>, connectionKey: IConnectionInfoParams, catalogId?: string, schemaId?: string) {

Check warning on line 407 in webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
const state = await this.sqlEditorService.setConnection(tab.handlerState, connectionKey, catalogId, schemaId);

if (state) {
Expand Down Expand Up @@ -531,12 +535,14 @@
}

private async handleTabUnload(editorTab: ITab<ISqlEditorTabState>) {
await this.sqlEditorModelService.unload(editorTab.handlerState.editorId);
await this.sqlDataSourceService.unload(editorTab.handlerState.editorId);

this.sqlResultTabsService.removeResultTabs(editorTab.handlerState);
}

private async handleTabCloseSilent(editorTab: ITab<ISqlEditorTabState>) {
await this.sqlEditorModelService.destroySilent(editorTab.handlerState.editorId);
const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);

if (dataSource?.executionContext) {
Expand All @@ -548,6 +554,8 @@
}

private async handleTabClose(editorTab: ITab<ISqlEditorTabState>) {
await this.sqlEditorModelService.destroy(editorTab.handlerState.editorId);

const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);

if (dataSource?.executionContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
});

function applyIncoming() {
data.dataSource?.applyIncoming();
data.model.dataSource?.applyIncoming();
}

function keepCurrent() {
data.dataSource?.keepCurrent();
data.model.dataSource?.keepCurrent();
}

return (
Expand All @@ -99,8 +99,8 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
ref={setEditorRef}
getValue={() => data.value}
cursor={{
anchor: data.cursor.anchor,
head: data.cursor.head,
anchor: data.model.cursor.anchor,
head: data.model.cursor.head,
}}
incomingValue={data.incomingValue}
extensions={extensions}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
Expand All @@ -20,7 +20,7 @@ export class SQLCodeEditorPanelService {
key: 'sql-editor',
icon: '/icons/sql_script_sm.svg',
name: 'sql_editor_script_editor',
isHidden: (_, props) => props?.data.dataSource?.hasFeature(ESqlDataSourceFeatures.script) !== true,
isHidden: (_, props) => props?.data.model.dataSource?.hasFeature(ESqlDataSourceFeatures.script) !== true,
panel: () => SQLCodeEditorPanel,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@

export const ON_QUERY_CHANGE_SOURCE = 'QueryChange';

export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {

Check warning on line 25 in webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/useSQLCodeEditorPanel.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
const state: State = useObservableRef(
() => ({
highlightActiveQuery() {
this.editor.clearActiveQueryHighlight();

const segment = this.data.activeSegment;
const segment = this.data.model.cursorSegment;

if (segment) {
this.editor.highlightActiveQuery(segment.begin, segment.end);
Expand All @@ -46,13 +46,13 @@
{ editor, data },
);

const updateHighlight = useCallback(

Check warning on line 49 in webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/useSQLCodeEditorPanel.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead
throttle(() => state.highlightActiveQuery(), 1000),
[state],
);

useExecutor({
executor: data.onUpdate,
executor: data.model.onUpdate,
handlers: [updateHighlight],
});

Expand Down
1 change: 1 addition & 0 deletions webapp/packages/plugin-sql-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@cloudbeaver/plugin-codemirror6": "workspace:*",
"@cloudbeaver/plugin-data-viewer": "workspace:*",
"@cloudbeaver/plugin-navigation-tabs": "workspace:*",
"@dbeaver/js-helpers": "workspace:^",
"mobx": "^6",
"mobx-react-lite": "^4",
"react": "^19",
Expand Down
41 changes: 8 additions & 33 deletions webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,6 @@ export class MenuBootstrap extends Bootstrap {
id: 'sql-editor-actions-more',
actions: [ACTION_DOWNLOAD, ACTION_UPLOAD],
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA, DATA_CONTEXT_SQL_EDITOR_STATE],
isHidden: context => {
const data = context.get(DATA_CONTEXT_SQL_EDITOR_DATA)!;

return data.activeSegmentMode.activeSegmentMode;
},
isDisabled: (context, action) => {
const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE)!;

Expand Down Expand Up @@ -217,10 +212,7 @@ export class MenuBootstrap extends Bootstrap {
this.menuService.addCreator({
menus: [SQL_EDITOR_ACTIONS_MENU],
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA, DATA_CONTEXT_SQL_EDITOR_STATE],
getItems: (context, items) => [
...items,
...EXECUTIONS_ACTIONS,
],
getItems: (context, items) => [...items, ...EXECUTIONS_ACTIONS],
});

this.keyBindingService.addKeyBindingHandler({
Expand All @@ -242,12 +234,7 @@ export class MenuBootstrap extends Bootstrap {

this.actionService.addHandler({
id: 'sql-editor-actions',
actions: [
...EXECUTIONS_ACTIONS,
ACTION_SQL_EDITOR_FORMAT,
ACTION_REDO,
ACTION_UNDO,
],
actions: [...EXECUTIONS_ACTIONS, ACTION_SQL_EDITOR_FORMAT, ACTION_REDO, ACTION_UNDO],
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA],
isActionApplicable: (contexts, action): boolean => {
const sqlEditorData = contexts.get(DATA_CONTEXT_SQL_EDITOR_DATA)!;
Expand All @@ -256,25 +243,21 @@ export class MenuBootstrap extends Bootstrap {
return false;
}

if (
!sqlEditorData.isExecutionAllowed &&
EXECUTIONS_ACTIONS.includes(action)
) {
if (!sqlEditorData.isExecutionAllowed && EXECUTIONS_ACTIONS.includes(action)) {
return false;
}

if (action === ACTION_SQL_EDITOR_FORMAT) {
return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.script) && !sqlEditorData.activeSegmentMode.activeSegmentMode;
return !!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.script);
}

if (action === ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN) {
return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
!!sqlEditorData.dialect?.supportsExplainExecutionPlan;
return !!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query) && !!sqlEditorData.dialect?.supportsExplainExecutionPlan;
}

// TODO we have to add check for output action ?
if (
!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
[ACTION_SQL_EDITOR_EXECUTE, ACTION_SQL_EDITOR_EXECUTE_NEW, ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN].includes(action)
) {
return false;
Expand Down Expand Up @@ -402,24 +385,16 @@ export class MenuBootstrap extends Bootstrap {
data.executeQueryNewTab();
break;
case ACTION_SQL_EDITOR_EXECUTE_SCRIPT:
if (data.activeSegmentMode.activeSegmentMode) {
return;
}

data.executeScript();
break;
case ACTION_SQL_EDITOR_FORMAT:
if (data.activeSegmentMode.activeSegmentMode) {
return;
}

data.formatScript();
break;
case ACTION_UNDO:
data.dataSource?.history.undo();
data.model.dataSource?.history.undo();
break;
case ACTION_REDO:
data.dataSource?.history.redo();
data.model.dataSource?.history.redo();
break;
case ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN:
data.showExecutionPlan();
Expand Down
Loading
Loading