Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
93a598b
dbeaver/pro#7772 adds truncated tooltips for context menus
sergeyteleshev Jan 6, 2026
285b6b7
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
sergeyteleshev Jan 6, 2026
e64ad4f
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
sergeyteleshev Jan 8, 2026
a7365de
dbeaver/pro#7772 adds " ... " for long labels
sergeyteleshev Jan 8, 2026
178dfe8
adds unit tests
sergeyteleshev Jan 8, 2026
8fa917c
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
sergeyteleshev Jan 8, 2026
e0e4162
Update webapp/packages/core-blocks/src/Menu/MenuItemElement.tsx
sergeyteleshev Jan 8, 2026
6f02892
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
sergeyteleshev Jan 8, 2026
9732955
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
dariamarutkina Jan 9, 2026
67ad19b
adds binding for delete row
sergeyteleshev Jan 9, 2026
c8455bd
removes tooltips
sergeyteleshev Jan 9, 2026
f423efe
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
sergeyteleshev Jan 9, 2026
b2eb320
fixes tree filters clipping
sergeyteleshev Jan 9, 2026
459dfab
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
sergeyteleshev Jan 12, 2026
cd3ee3d
clips only names, not whole labels
sergeyteleshev Jan 12, 2026
1e01d04
clips only names, not whole labels [2]
sergeyteleshev Jan 12, 2026
f3c996d
reverts getBindingLabels + fixes the os specific keys to show it first
sergeyteleshev Jan 12, 2026
34d1696
clips only names, not whole labels [3]
sergeyteleshev Jan 12, 2026
8743624
dbeaver/pro#7772 simplifies keys helpers logic
sergeyteleshev Jan 12, 2026
637f675
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
dariamarutkina Jan 13, 2026
a3c72c3
Merge branch 'devel' into 7772-cb-remove-tooltips-from-context-menu
dariamarutkina Jan 13, 2026
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
12 changes: 3 additions & 9 deletions webapp/packages/core-blocks/src/Menu/MenuItemElement.tsx
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -48,20 +48,14 @@ export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItem
loading = useStateDelay(loading, 300);

return (
<button {...rest} className={s(styles, { menuPanelItem: true }, rest.className)} title={tooltip ? translate(tooltip) : title}>
<button {...rest} className={s(styles, { menuPanelItem: true }, rest.className)} title={translate(tooltip)}>
<div className={s(styles, { menuItemMain: true })}>
<div className={s(styles, { menuItemIcon: true })}>
<Loader className={s(styles, { loader: true })} suspense small fullSize>
{typeof icon === 'string' ? <IconOrImage className={s(styles, { iconOrImage: true })} icon={icon} /> : icon}
</Loader>
</div>
{!onlyIcons ? (
<div className={s(styles, { menuItemText: true })} title={title}>
{title}
</div>
) : (
<div />
)}
{!onlyIcons ? <div className={s(styles, { menuItemText: true })}>{title}</div> : <div />}
<div className={s(styles, { menuItemBinding: true })} title={binding}>
{binding}
</div>
Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/core-blocks/src/index.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -10,22 +10,22 @@

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 './ExportImageDialog/ExportImageDialogLazy.js';
export * from './ExportImageDialog/ExportImageFormats.js';

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

Check failure on line 25 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 28 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 Down
6 changes: 3 additions & 3 deletions webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/*
* 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.
*/
import { observer } from 'mobx-react-lite';

import { Checkbox, getComputed, Radio, registry, useTranslate, type IMenuItemElementProps } from '@cloudbeaver/core-blocks';
import { getBindingLabel, type IMenuActionItem, type IMenuInfo } from '@cloudbeaver/core-view';
import { getBindingLabels, type IMenuActionItem, type IMenuInfo } from '@cloudbeaver/core-view';
import type { IContextMenuItemProps } from './IContextMenuItemProps.js';
import { MenuItem, MenuItemCheckbox, MenuItemRadio } from '@dbeaver/ui-kit';
import { useCallback } from 'react';
Expand Down Expand Up @@ -40,7 +40,7 @@ export const MenuActionElement = registry(
const loading = getComputed(() => item.action.isLoading());
let binding: string | undefined;
if (item.action.binding !== null) {
binding = getBindingLabel(item.action.binding.binding);
binding = getBindingLabels(item.action.binding.binding);
}

const label = translate(actionInfo.label);
Expand Down
7 changes: 6 additions & 1 deletion webapp/packages/core-view/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"build": "tsc -b",
"clean": "rimraf --glob lib",
"lint": "eslint ./src/ --ext .ts,.tsx",
"test": "dbeaver-test",
"validate-dependencies": "core-cli-validate-dependencies"
},
"dependencies": {
Expand All @@ -37,10 +38,14 @@
},
"devDependencies": {
"@cloudbeaver/core-cli": "workspace:*",
"@cloudbeaver/tests-runner": "workspace:*",
"@cloudbeaver/tsconfig": "workspace:*",
"@dbeaver/cli": "workspace:*",
"@dbeaver/react-tests": "workspace:*",
"@types/react": "^19",
"rimraf": "^6",
"typescript": "^5",
"typescript-plugin-css-modules": "^5"
"typescript-plugin-css-modules": "^5",
"vitest": "^4"
}
}
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-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.
Expand All @@ -9,5 +9,4 @@ import { createAction } from '../createAction.js';

export const ACTION_EXPORT = createAction('export', {
label: 'ui_export',
tooltip: 'ui_export',
});
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -32,6 +32,7 @@ const FORMAT_SHORTCUT_KEYS_MAP: Record<string, string> = {
};
const SOURCE_DIVIDER_REGEXP = /\+/gi;
const APPLIED_DIVIDER = ' + ';
const BINDINGS_DIVIDER = ' | ';

function transformKeys(keyBinding: IKeyBinding): string[] {
return getCommonAndOSSpecificKeys(keyBinding).map(shortcut =>
Expand Down Expand Up @@ -62,6 +63,6 @@ function formatKeyToDisplayKey(code: string): string {
}
}

export function getBindingLabel(binding: IKeyBinding): string | undefined {
return transformKeys(binding)[0];
export function getBindingLabels(binding: IKeyBinding): string | undefined {
return transformKeys(binding).join(BINDINGS_DIVIDER);
}
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-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.
Expand Down Expand Up @@ -28,7 +28,7 @@ export function getCommonAndOSSpecificKeys(keyBinding: IKeyBinding | undefined):
}
}

return keys;
return Array.from(new Set(keys));
}

export function getOSSpecificKeys(keyBinding: IKeyBinding): string | string[] | undefined {
Expand Down
104 changes: 104 additions & 0 deletions webapp/packages/core-view/src/Menu/getMenuLabelClipped.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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 { describe, expect, it } from 'vitest';

import { getMenuLabelClipped } from './getMenuLabelClipped.js';

describe('getMenuLabelClipped', () => {
it('should return original label and no tooltip when label is shorter than limiter', () => {
const result = getMenuLabelClipped('Short label', 8, 30);
expect(result.clippedLabel).toBe('Short label');
expect(result.tooltip).toBeUndefined();
});

it('should clip label and return tooltip when label exceeds limiter', () => {
const longLabel = 'This is a very long label that exceeds the limiter';
const result = getMenuLabelClipped(longLabel, 8, 30);
expect(result.clippedLabel).not.toBe(longLabel);
expect(result.clippedLabel).toContain(' ... ');
expect(result.tooltip).toBe(longLabel);
});

it('should use default sideLength of 8 when not provided', () => {
const longLabel = 'This is a very long label that exceeds the limiter';
const result = getMenuLabelClipped(longLabel, undefined, 30);
expect(result.clippedLabel).toMatch(/^.{8} \.\.\. .{8}$/);
expect(result.tooltip).toBe(longLabel);
});

it('should use default limiter of 30 when not provided', () => {
const label = '12345678901234567890123456789'; // exactly 29 characters
const result = getMenuLabelClipped(label, 8);
expect(result.clippedLabel).toBe(label);
expect(result.tooltip).toBeUndefined();
});

it('should use custom sideLength', () => {
const longLabel = 'This is a very long label that exceeds the limiter';
const result = getMenuLabelClipped(longLabel, 5, 30);
expect(result.clippedLabel).toMatch(/^.{5} \.\.\. .{5}$/);
expect(result.tooltip).toBe(longLabel);
});

it('should use custom limiter', () => {
const label = '1234567890123456789'; // exactly 19 characters
const result = getMenuLabelClipped(label, 8, 20);
expect(result.clippedLabel).toBe(label);
expect(result.tooltip).toBeUndefined();
});

it('should handle label exactly at limiter length', () => {
const label = '123456789012345678901234567890'; // exactly 30 characters
const result = getMenuLabelClipped(label, 8, 30);
expect(result.clippedLabel).toBe('12345678 ... 34567890');
expect(result.tooltip).toBe(label);
});

it('should handle label one character longer than limiter', () => {
const label = '1234567890123456789012345678901'; // 31 characters
const result = getMenuLabelClipped(label, 8, 30);
expect(result.clippedLabel).not.toBe(label);
expect(result.clippedLabel).toContain(' ... ');
expect(result.tooltip).toBe(label);
});

it('should handle very long labels', () => {
const veryLongLabel = 'A'.repeat(100);
const result = getMenuLabelClipped(veryLongLabel, 8, 30);
expect(result.clippedLabel.length).toBeLessThan(veryLongLabel.length);
expect(result.clippedLabel).toContain(' ... ');
expect(result.tooltip).toBe(veryLongLabel);
});

it('should handle empty string', () => {
const result = getMenuLabelClipped('', 8, 30);
expect(result.clippedLabel).toBe('');
expect(result.tooltip).toBeUndefined();
});

it('should handle sideLength of 0', () => {
const longLabel = 'This is a very long label that exceeds the limiter';
const result = getMenuLabelClipped(longLabel, 0, 30);
expect(result.clippedLabel).toContain(' ... ');
expect(result.tooltip).toBe(longLabel);
});

it('should handle very small sideLength', () => {
const longLabel = 'This is a very long label that exceeds the limiter';
const result = getMenuLabelClipped(longLabel, 2, 30);
expect(result.clippedLabel).toMatch(/^.{2} \.\.\. .{2}$/);
expect(result.tooltip).toBe(longLabel);
});

it('should handle large sideLength', () => {
const longLabel = 'This is a very long label that exceeds the limiter';
const result = getMenuLabelClipped(longLabel, 15, 30);
expect(result.clippedLabel).toMatch(/^.{15} \.\.\. .{15}$/);
expect(result.tooltip).toBe(longLabel);
});
});
19 changes: 19 additions & 0 deletions webapp/packages/core-view/src/Menu/getMenuLabelClipped.ts
Original file line number Diff line number Diff line change
@@ -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 { replaceMiddle } from '@cloudbeaver/core-utils';

export function getMenuLabelClipped(
label: string,
sideLength: number = 8,
limiter: number = 30,
): { clippedLabel: string; tooltip: string | undefined } {
const clippedLabel = replaceMiddle(label, ' ... ', sideLength, limiter);
const tooltip = clippedLabel !== label ? label : undefined;

return { clippedLabel, tooltip };
}
3 changes: 2 additions & 1 deletion webapp/packages/core-view/src/index.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -76,6 +76,7 @@ export * from './Menu/IMenu.js';
export * from './Menu/IMenuInfo.js';
export * from './Menu/menuExtractItems.js';
export * from './Menu/menuItemsPlaceAfter.js';
export * from './Menu/getMenuLabelClipped.js';
export * from './Menu/MenuService.js';
export * from './Menu/useMenu.js';
export * from './Menu/useMenuContext.js';
Expand Down
9 changes: 9 additions & 0 deletions webapp/packages/core-view/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"composite": true
},
"references": [
{
"path": "../../common-react/@dbeaver/react-tests"
},
{
"path": "../../common-typescript/@dbeaver/cli"
},
{
"path": "../../common-typescript/@dbeaver/js-helpers"
},
Expand All @@ -30,6 +36,9 @@
},
{
"path": "../core-utils"
},
{
"path": "../tests-runner"
}
],
"include": [
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -9,5 +9,4 @@ import { createAction } from '@cloudbeaver/core-view';

export const ACTION_TREE_CREATE_CONNECTION = createAction('create-tree-connection', {
label: 'plugin_connections_connection_create_menu_title',
tooltip: 'plugin_connections_connection_create_menu_title',
});
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -9,6 +9,5 @@ import { createAction } from '@cloudbeaver/core-view';

export const ACTION_DATA_GRID_FILTERS_RESET_OR_SORTING = createAction('data-grid-filters-or-sorting-reset', {
label: 'data_grid_table_delete_filters_and_orders',
tooltip: 'data_grid_table_delete_filters_and_orders',
icon: 'erase',
});
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -9,7 +9,7 @@ import { injectable } from '@cloudbeaver/core-di';
import {
ACTION_EDIT,
ActionService,
getBindingLabel,
getBindingLabels,
KEY_BINDING_ADD,
KEY_BINDING_DUPLICATE,
MenuService,
Expand All @@ -30,6 +30,7 @@ import {
IDatabaseDataViewAction,
isBooleanValuePresentationAvailable,
isResultSetDataSource,
KEY_BINDING_DELETE_ROW,
ResultSetDataContentAction,
type IDatabaseValueHolder,
type IGridDataKey,
Expand Down Expand Up @@ -206,21 +207,29 @@ export class DataGridContextMenuCellEditingService {
return {
...action.info,
label: 'data_grid_table_editing_row_add',
tooltip: t('data_grid_table_editing_row_add') + ' (' + getBindingLabel(KEY_BINDING_ADD) + ')',
tooltip: t('data_grid_table_editing_row_add') + ' (' + getBindingLabels(KEY_BINDING_ADD) + ')',
};
}
if (action === ACTION_DATA_GRID_EDITING_DUPLICATE_ROW) {
return {
...action.info,
label: 'data_grid_table_editing_row_add_copy',
tooltip: t('data_grid_table_editing_row_add_copy') + ' (' + getBindingLabel(KEY_BINDING_DUPLICATE) + ')',
tooltip: t('data_grid_table_editing_row_add_copy') + ' (' + getBindingLabels(KEY_BINDING_DUPLICATE) + ')',
};
}

if (action === ACTION_EDIT) {
return { ...action.info, label: t('data_grid_table_editing_open_inline_editor'), icon: 'edit' };
}

if (action === ACTION_DATA_GRID_EDITING_DELETE_ROW) {
return {
...action.info,
label: t('data_grid_table_editing_row_delete'),
tooltip: t('data_grid_table_editing_row_delete') + ' (' + getBindingLabels(KEY_BINDING_DELETE_ROW) + ')',
};
}

return action.info;
}
}
Loading
Loading