mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
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 <wrouds@gmail.com> Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Alexey Potsetsuev
Evgenia
parent
da7d4eef19
commit
39ea1e2b7f
@@ -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<HTMLDivElement>) => void;
|
||||
handleContextMenuOpen: (event: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export function useContextMenuPosition(): IContextMenuPosition {
|
||||
return useObservableRef<IContextMenuPosition>(
|
||||
() => ({
|
||||
position: null,
|
||||
handleContextMenuOpen(event: React.MouseEvent<HTMLDivElement>) {
|
||||
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,
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -13,3 +13,4 @@ export { devToolsPlugin };
|
||||
export default devToolsPlugin;
|
||||
|
||||
export * from './DevToolsService.js';
|
||||
export * from './menu/MENU_DEVTOOLS.js';
|
||||
|
||||
@@ -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<NavigationTreeNewProps>(function Tree({
|
||||
data,
|
||||
selection,
|
||||
menu,
|
||||
nodeRenderers,
|
||||
emptyPlaceholder,
|
||||
className,
|
||||
@@ -69,7 +73,9 @@ export const Tree = observer<NavigationTreeNewProps>(function Tree({
|
||||
<TreeVirtualizationContext.Provider value={mountOptimization}>
|
||||
<TreeContext.Provider value={tree}>
|
||||
<TreeDnDContext.Provider value={treeDnD}>
|
||||
<NodeChildren nodeId={data.rootId} offsetHeight={0} emptyPlaceholder={emptyPlaceholder} root />
|
||||
<TreeMenuContextProvider menu={menu ?? null}>
|
||||
<NodeChildren nodeId={data.rootId} offsetHeight={0} emptyPlaceholder={emptyPlaceholder} root />
|
||||
</TreeMenuContextProvider>
|
||||
</TreeDnDContext.Provider>
|
||||
</TreeContext.Provider>
|
||||
</TreeVirtualizationContext.Provider>
|
||||
|
||||
+23
@@ -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<ITreeMenu | null>(null);
|
||||
|
||||
export function useTreeMenuContext() {
|
||||
const context = use(TreeMenuContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useTreeMenuContext must be used within a TreeMenuContextProvider.');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
+33
@@ -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<PropsWithChildren<Props>>(function TreeMenuContextProvider({ menu, children }) {
|
||||
return (
|
||||
<TreeMenuContext value={menu}>
|
||||
{children}
|
||||
|
||||
{menu && (
|
||||
<div className="tw:absolute tw:invisible">
|
||||
<ContextMenu menu={menu.menu} contextMenuPosition={menu.position} modal />
|
||||
</div>
|
||||
)}
|
||||
</TreeMenuContext>
|
||||
);
|
||||
});
|
||||
@@ -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<ITreeMenu> {
|
||||
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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user