From 39ea1e2b7f2b808a9a4bb11010f418b34a1a6bba Mon Sep 17 00:00:00 2001 From: alex <48489896+devnaumov@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:43:27 +0200 Subject: [PATCH] dbeaver/pro#4742 add tree context menu provider (#3745) * dbeaver/pro#4742 add tree context menu provider * dbeaver/pro#4742 add useTreeMenu hook * dbeaver/pro#4742 revert hook renaming --------- Co-authored-by: Alexey Potsetsuev Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../src/Menu/useContextMenuPosition.ts | 21 ++++++-- webapp/packages/plugin-devtools/src/index.ts | 1 + .../src/TreeNew/Tree.tsx | 8 ++- .../TreeMenuContext/TreeMenuContext.ts | 23 ++++++++ .../TreeMenuContextProvider.tsx | 33 ++++++++++++ .../src/TreeNew/useTreeMenu.ts | 52 +++++++++++++++++++ .../plugin-navigation-tree/src/index.ts | 2 + 7 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContext.ts create mode 100644 webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContextProvider.tsx create mode 100644 webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeMenu.ts diff --git a/webapp/packages/core-blocks/src/Menu/useContextMenuPosition.ts b/webapp/packages/core-blocks/src/Menu/useContextMenuPosition.ts index d9e3c7cd4c..4dd97b5500 100644 --- a/webapp/packages/core-blocks/src/Menu/useContextMenuPosition.ts +++ b/webapp/packages/core-blocks/src/Menu/useContextMenuPosition.ts @@ -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. @@ -16,23 +16,34 @@ export interface IContextMenuPositionCoords { export interface IContextMenuPosition { position: IContextMenuPositionCoords | null; - handleContextMenuOpen: (event: React.MouseEvent) => void; + handleContextMenuOpen: (event: React.MouseEvent) => void; } export function useContextMenuPosition(): IContextMenuPosition { return useObservableRef( () => ({ position: null, - handleContextMenuOpen(event: React.MouseEvent) { + handleContextMenuOpen(event: React.MouseEvent) { if (!event.currentTarget.contains(event.target as Node)) { return; } + event.preventDefault(); event.stopPropagation(); + let x = event.clientX; + let y = event.clientY; + + if (x === 0 && y === 0) { + const rect = event.currentTarget.getBoundingClientRect(); + + x = rect.left + rect.width / 2; + y = rect.top + rect.height / 2; + } + this.position = { - x: event.clientX, - y: event.clientY, + x, + y, }; }, }), diff --git a/webapp/packages/plugin-devtools/src/index.ts b/webapp/packages/plugin-devtools/src/index.ts index 0345e62ba6..14415d66fb 100644 --- a/webapp/packages/plugin-devtools/src/index.ts +++ b/webapp/packages/plugin-devtools/src/index.ts @@ -13,3 +13,4 @@ export { devToolsPlugin }; export default devToolsPlugin; export * from './DevToolsService.js'; +export * from './menu/MENU_DEVTOOLS.js'; diff --git a/webapp/packages/plugin-navigation-tree/src/TreeNew/Tree.tsx b/webapp/packages/plugin-navigation-tree/src/TreeNew/Tree.tsx index e9c8e907f7..cda1025e8f 100644 --- a/webapp/packages/plugin-navigation-tree/src/TreeNew/Tree.tsx +++ b/webapp/packages/plugin-navigation-tree/src/TreeNew/Tree.tsx @@ -24,10 +24,13 @@ import { useTree } from './useTree.js'; import { useTreeDnD } from './useTreeDnD.js'; import type { ITreeSelection } from './useTreeSelection.js'; import { useTreeVirtualization } from './useTreeVirtualization.js'; +import { TreeMenuContextProvider } from './contexts/TreeMenuContext/TreeMenuContextProvider.js'; +import type { ITreeMenu } from './useTreeMenu.js'; export interface NavigationTreeNewProps { data: ITreeData; selection?: ITreeSelection; + menu?: ITreeMenu; nodeRenderers?: INodeRenderer[]; emptyPlaceholder?: NodeEmptyPlaceholderComponent; className?: string; @@ -40,6 +43,7 @@ export interface NavigationTreeNewProps { export const Tree = observer(function Tree({ data, selection, + menu, nodeRenderers, emptyPlaceholder, className, @@ -69,7 +73,9 @@ export const Tree = observer(function Tree({ - + + + diff --git a/webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContext.ts b/webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContext.ts new file mode 100644 index 0000000000..92b0107ffd --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContext.ts @@ -0,0 +1,23 @@ +/* + * 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 { createContext, use } from 'react'; + +import type { ITreeMenu } from '../../useTreeMenu.js'; + +export const TreeMenuContext = createContext(null); + +export function useTreeMenuContext() { + const context = use(TreeMenuContext); + + if (!context) { + throw new Error('useTreeMenuContext must be used within a TreeMenuContextProvider.'); + } + + return context; +} diff --git a/webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContextProvider.tsx b/webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContextProvider.tsx new file mode 100644 index 0000000000..5075edd770 --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/TreeNew/contexts/TreeMenuContext/TreeMenuContextProvider.tsx @@ -0,0 +1,33 @@ +/* + * 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 { observer } from 'mobx-react-lite'; +import type { PropsWithChildren } from 'react'; + +import { ContextMenu } from '@cloudbeaver/core-ui'; + +import type { ITreeMenu } from '../../useTreeMenu.js'; +import { TreeMenuContext } from './TreeMenuContext.js'; + +interface Props { + menu: ITreeMenu | null; +} + +export const TreeMenuContextProvider = observer>(function TreeMenuContextProvider({ menu, children }) { + return ( + + {children} + + {menu && ( +
+ +
+ )} +
+ ); +}); diff --git a/webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeMenu.ts b/webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeMenu.ts new file mode 100644 index 0000000000..e93296378f --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/TreeNew/useTreeMenu.ts @@ -0,0 +1,52 @@ +/* + * 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 { action } from 'mobx'; +import { useId } from 'react'; + +import { useContextMenuPosition, useObjectRef, useObservableRef, type IContextMenuPosition } from '@cloudbeaver/core-blocks'; +import type { IDataContext } from '@cloudbeaver/core-data-context'; +import { DATA_CONTEXT_NAV_NODE_ID } from '@cloudbeaver/core-navigation-tree'; +import type { IMenuData } from '@cloudbeaver/core-view'; + +export interface ITreeMenu { + menu: IMenuData; + position: IContextMenuPosition; + openMenu(event: React.MouseEvent, nodeId: string): void; +} + +interface ITreeMenuOptions { + menu: IMenuData; + setContext?: (context: IDataContext, id: string) => void; +} + +export function useTreeMenu(options: ITreeMenuOptions): Readonly { + options = useObjectRef(options); + + const id = useId(); + const position = useContextMenuPosition(); + + const state = useObservableRef( + () => ({ + openMenu(event: React.MouseEvent, nodeId: string) { + this.menu.context.deleteForId(this.id); + this.menu.context.set(DATA_CONTEXT_NAV_NODE_ID, nodeId, this.id); + + if (options.setContext) { + options.setContext(this.menu.context, this.id); + } + + this.position.handleContextMenuOpen(event); + }, + }), + { openMenu: action.bound }, + { id, menu: options.menu, position }, + ); + + return state; +} diff --git a/webapp/packages/plugin-navigation-tree/src/index.ts b/webapp/packages/plugin-navigation-tree/src/index.ts index f4eeb2494e..dcba79b1ac 100644 --- a/webapp/packages/plugin-navigation-tree/src/index.ts +++ b/webapp/packages/plugin-navigation-tree/src/index.ts @@ -66,6 +66,8 @@ export * from './NodesManager/NavNodeContextMenuService.js'; export * from './NodesManager/useDatabaseObjectInfo.js'; export * from './NodesManager/useNode.js'; export * from './NodesManager/useChildren.js'; +export * from './TreeNew/contexts/TreeMenuContext/TreeMenuContext.js'; +export * from './TreeNew/useTreeMenu.js'; export * from './NodesManager/NodeLinkLoader.js';