mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge pull request #1096 from dbeaver/feat/CB-2453/smart-projects-in-nav-tree
feat(plugin-navigation-tree): CB-2453 remove expand from projects in …
This commit is contained in:
+1
@@ -231,6 +231,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
</InputField>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InputField
|
||||
type={password ? 'password' : 'text'}
|
||||
|
||||
+6
-3
@@ -102,6 +102,7 @@ export const ElementsTree = observer<Props>(function ElementsTree({
|
||||
navNodeFilterCompare,
|
||||
filters = [],
|
||||
renderers = [],
|
||||
expandStateGetters,
|
||||
style,
|
||||
className,
|
||||
getChildren,
|
||||
@@ -161,7 +162,7 @@ export const ElementsTree = observer<Props>(function ElementsTree({
|
||||
|
||||
}, [folderExplorer]);
|
||||
|
||||
const children = useMapResource(ElementsTree, navTreeResource, root, {
|
||||
useMapResource(ElementsTree, navTreeResource, root, {
|
||||
onLoad: async resource => {
|
||||
let fullPath = folderExplorer.state.fullPath;
|
||||
const preload = await resource.preloadNodeParents(fullPath);
|
||||
@@ -199,6 +200,7 @@ export const ElementsTree = observer<Props>(function ElementsTree({
|
||||
localState,
|
||||
filters: [nameFilter, ...filters, limitFilter],
|
||||
renderers: [...renderers, elementsTreeLimitRenderer],
|
||||
expandStateGetters,
|
||||
getChildren,
|
||||
loadChildren,
|
||||
isGroup,
|
||||
@@ -279,7 +281,8 @@ export const ElementsTree = observer<Props>(function ElementsTree({
|
||||
}
|
||||
});
|
||||
|
||||
const hasChildren = (children.data?.length || 0) > 0;
|
||||
const children = tree.getNodeChildren(root);
|
||||
const hasChildren = children.length > 0;
|
||||
const loaderAvailable = !foldersTree || context.folderExplorer.root === root;
|
||||
|
||||
return styled(useStyles(TREE_NODE_STYLES, styles, style))(
|
||||
@@ -290,7 +293,7 @@ export const ElementsTree = observer<Props>(function ElementsTree({
|
||||
root={root}
|
||||
context={context}
|
||||
emptyPlaceholder={emptyPlaceholder}
|
||||
childrenState={children}
|
||||
childrenState={tree}
|
||||
hasChildren={hasChildren}
|
||||
>
|
||||
<ElementsTreeContext.Provider value={context}>
|
||||
|
||||
+3
-1
@@ -104,9 +104,11 @@ export const NavigationNodeControl: NavTreeControlComponent = observer<NavTreeCo
|
||||
treeNodeContext.select(event.ctrlKey || event.metaKey);
|
||||
}
|
||||
|
||||
const expandable = getComputed(() => treeContext?.tree.isNodeExpandable(node.id) ?? true);
|
||||
|
||||
return styled(TREE_NODE_STYLES, nodeIconStyle, styles)(
|
||||
<TreeNodeControl ref={ref} onClick={onClickHandler} {...use({ outdated, editing, dragging: dndElement })}>
|
||||
<TreeNodeExpand filterActive={treeContext?.tree.filtering} />
|
||||
{expandable && <TreeNodeExpand filterActive={treeContext?.tree.filtering} />}
|
||||
<TreeNodeIcon icon={icon} style={nodeIconStyle} {...use({ connected })}>
|
||||
<ConnectionMark connected={connected} />
|
||||
</TreeNodeIcon>
|
||||
|
||||
+52
-4
@@ -9,7 +9,7 @@
|
||||
import { action, computed, observable, runInAction } from 'mobx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { IFolderExplorerContext, useExecutor, useObjectRef, useObservableRef, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { IFolderExplorerContext, ILoadableState, useExecutor, useObjectRef, useObservableRef, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
@@ -23,6 +23,16 @@ import type { NavigationNodeRendererComponent } from './NavigationNodeComponent'
|
||||
|
||||
export type IElementsTreeCustomRenderer = (nodeId: string) => NavigationNodeRendererComponent | undefined;
|
||||
|
||||
export interface IElementsTreeNodeExpandedInfo {
|
||||
expanded: boolean;
|
||||
expandable?: boolean;
|
||||
}
|
||||
|
||||
export type IElementsTreeNodeExpandInfoGetter = (
|
||||
nodeId: string,
|
||||
state: ITreeNodeState
|
||||
) => IElementsTreeNodeExpandedInfo | null;
|
||||
|
||||
export type IElementsTreeFilter = (
|
||||
filter: string,
|
||||
node: NavNode,
|
||||
@@ -56,6 +66,7 @@ export interface IElementsTreeOptions {
|
||||
disabled?: boolean;
|
||||
filters?: IElementsTreeFilter[];
|
||||
renderers?: IElementsTreeCustomRenderer[];
|
||||
expandStateGetters?: IElementsTreeNodeExpandInfoGetter[];
|
||||
localState?: MetadataMap<string, ITreeNodeState>;
|
||||
getChildren: (id: string) => string[] | undefined;
|
||||
loadChildren: (id: string, manual: boolean) => Promise<boolean>;
|
||||
@@ -74,7 +85,7 @@ interface IOptions extends IElementsTreeOptions {
|
||||
folderExplorer: IFolderExplorerContext;
|
||||
}
|
||||
|
||||
export interface IElementsTree {
|
||||
export interface IElementsTree extends ILoadableState {
|
||||
actions: ISyncExecutor<IElementsTreeAction>;
|
||||
settings?: IElementsTreeSettings;
|
||||
baseRoot: string;
|
||||
@@ -89,6 +100,7 @@ export interface IElementsTree {
|
||||
|
||||
getNodeState: (nodeId: string) => ITreeNodeState;
|
||||
isNodeExpanded: (nodeId: string, ignoreFilter?: boolean) => boolean;
|
||||
isNodeExpandable: (nodeId: string) => boolean;
|
||||
getExpanded: () => string[];
|
||||
getSelected: () => string[];
|
||||
isNodeSelected: (nodeId: string) => boolean;
|
||||
@@ -294,6 +306,12 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
get filtering() {
|
||||
return this.filter !== '';
|
||||
},
|
||||
isLoading() {
|
||||
return this.loading;
|
||||
},
|
||||
isLoaded() {
|
||||
return true;
|
||||
},
|
||||
getNodeState(nodeId: string) {
|
||||
return this.state.get(nodeId);
|
||||
},
|
||||
@@ -307,8 +325,34 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
}
|
||||
|
||||
const nodeState = this.getNodeState(nodeId);
|
||||
const expanded = nodeState.expanded || nodeState.showInFilter;
|
||||
|
||||
return nodeState.expanded || nodeState.showInFilter;
|
||||
if (!expanded && options.expandStateGetters?.length) {
|
||||
return options
|
||||
.expandStateGetters
|
||||
.map(getExpandState => getExpandState(nodeId, nodeState))
|
||||
.filter(stateInfo => stateInfo !== null)
|
||||
.some(stateInfo => stateInfo?.expanded);
|
||||
}
|
||||
|
||||
return expanded;
|
||||
},
|
||||
isNodeExpandable(nodeId: string): boolean {
|
||||
if (nodeId === this.root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (options.expandStateGetters?.length) {
|
||||
const nodeState = this.getNodeState(nodeId);
|
||||
|
||||
return options
|
||||
.expandStateGetters
|
||||
.map(getExpandState => getExpandState(nodeId, nodeState))
|
||||
.filter(stateInfo => stateInfo !== null)
|
||||
.every(stateInfo => stateInfo?.expandable !== false);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
getExpanded(): string[] {
|
||||
return Array.from(this.state).filter(([key, state]) => state.expanded).map(([key]) => key);
|
||||
@@ -416,6 +460,10 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
}
|
||||
},
|
||||
async expand(node: NavNode, state: boolean) {
|
||||
if (!this.isNodeExpandable(node.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const treeNodeState = this.state.get(node.id);
|
||||
|
||||
try {
|
||||
@@ -499,7 +547,7 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
baseRoot: options.baseRoot,
|
||||
renderers,
|
||||
userData,
|
||||
});
|
||||
}, ['isLoading', 'isLoaded']);
|
||||
|
||||
function exitNodeFolder(nodeId: string) {
|
||||
runInAction(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import styled, { css } from 'reshadow';
|
||||
import { useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { Translate } from '@cloudbeaver/core-localization';
|
||||
import { NavNodeInfoResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
|
||||
import { NavNodeInfoResource, NavTreeResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
|
||||
import { usePermission, EPermission } from '@cloudbeaver/core-root';
|
||||
import { CaptureView } from '@cloudbeaver/core-view';
|
||||
|
||||
@@ -26,6 +26,8 @@ import type { IElementsTreeSettings } from './ElementsTree/useElementsTree';
|
||||
import { getNavigationTreeUserSettingsId } from './getNavigationTreeUserSettingsId';
|
||||
import { navigationTreeDuplicateFilter } from './navigationTreeDuplicateIdFilter';
|
||||
import { NavigationTreeService } from './NavigationTreeService';
|
||||
import { navigationTreeProjectFilter } from './ProjectsRenderer/navigationTreeProjectFilter';
|
||||
import { navigationTreeProjectsExpandStateGetter } from './ProjectsRenderer/navigationTreeProjectsExpandStateGetter';
|
||||
import { navigationTreeProjectsRendererRenderer } from './ProjectsRenderer/navigationTreeProjectsRendererRenderer';
|
||||
import { useNavigationTree } from './useNavigationTree';
|
||||
|
||||
@@ -74,6 +76,7 @@ const elementsTreeStyles = css`
|
||||
export const NavigationTree = observer(function NavigationTree() {
|
||||
const navTreeService = useService(NavigationTreeService);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const navTreeResource = useService(NavTreeResource);
|
||||
const navNodeViewService = useService(NavNodeViewService);
|
||||
|
||||
const root = ROOT_NODE_PATH;
|
||||
@@ -94,7 +97,15 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
const duplicateFilter = useMemo(() => navigationTreeDuplicateFilter(navNodeViewService), [navNodeViewService]);
|
||||
const projectsRendererRenderer = useMemo(
|
||||
() => navigationTreeProjectsRendererRenderer(navNodeInfoResource),
|
||||
[navNodeViewService]
|
||||
[navNodeInfoResource]
|
||||
);
|
||||
const projectsExpandStateGetter = useMemo(
|
||||
() => navigationTreeProjectsExpandStateGetter(navNodeInfoResource),
|
||||
[navNodeInfoResource]
|
||||
);
|
||||
const projectFilter = useMemo(
|
||||
() => navigationTreeProjectFilter(navNodeInfoResource, navTreeResource),
|
||||
[navNodeInfoResource, navTreeResource]
|
||||
);
|
||||
|
||||
if (!isEnabled) {
|
||||
@@ -106,8 +117,9 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
<ElementsTree
|
||||
root={root}
|
||||
localState={navTreeService.treeState}
|
||||
filters={[duplicateFilter, connectionGroupFilter]}
|
||||
filters={[duplicateFilter, connectionGroupFilter, projectFilter]}
|
||||
renderers={[projectsRendererRenderer, navigationTreeConnectionGroupRenderer]}
|
||||
expandStateGetters={[projectsExpandStateGetter]}
|
||||
emptyPlaceholder={() => styled(navigationTreeStyles)(
|
||||
<center>
|
||||
<message>
|
||||
|
||||
+1
-4
@@ -10,12 +10,11 @@ import { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useContext, useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { getComputed, TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeName, TREE_NODE_STYLES, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, TreeNodeContext, TreeNodeControl, TreeNodeName, TREE_NODE_STYLES, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { NavNodeInfoResource, type INodeActions } from '@cloudbeaver/core-navigation-tree';
|
||||
|
||||
import { ElementsTreeContext } from '../ElementsTree/ElementsTreeContext';
|
||||
import type { NavTreeControlComponent, NavTreeControlProps } from '../ElementsTree/NavigationNodeComponent';
|
||||
import { NavigationNodeEditor } from '../ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeEditor';
|
||||
import { TreeNodeMenu } from '../ElementsTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenu';
|
||||
@@ -62,7 +61,6 @@ export const NavigationNodeProjectControl: NavTreeControlComponent = observer<Na
|
||||
dndPlaceholder,
|
||||
}, ref) {
|
||||
const treeNodeContext = useContext(TreeNodeContext);
|
||||
const treeContext = useContext(ElementsTreeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const outdated = getComputed(() => navNodeInfoResource.isOutdated(node.id) && !treeNodeContext.loading);
|
||||
const selected = treeNodeContext.selected;
|
||||
@@ -86,7 +84,6 @@ export const NavigationNodeProjectControl: NavTreeControlComponent = observer<Na
|
||||
|
||||
return styled(TREE_NODE_STYLES, styles)(
|
||||
<TreeNodeControl ref={ref} onClick={onClickHandler} {...use({ outdated, editing, dragging: dndElement })}>
|
||||
<TreeNodeExpand filterActive={treeContext?.tree.filtering} />
|
||||
<TreeNodeName title={node.name}>
|
||||
{editing ? (
|
||||
<NavigationNodeEditor node={node} onClose={() => setEditing(false)} />
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 { NavNode, NavNodeInfoResource, NavTreeResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
|
||||
import { NAV_NODE_TYPE_PROJECT } from '@cloudbeaver/core-projects';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IElementsTreeFilter } from '../ElementsTree/useElementsTree';
|
||||
|
||||
export function navigationTreeProjectFilter(
|
||||
navNodeInfoResource: NavNodeInfoResource,
|
||||
navTreeResource: NavTreeResource,
|
||||
): IElementsTreeFilter {
|
||||
return (filter: string, node: NavNode, children: string[]) => {
|
||||
if (node.id !== ROOT_NODE_PATH) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const nodes = navNodeInfoResource
|
||||
.get(resourceKeyList(children))
|
||||
.filter<NavNode>((node => node !== undefined) as (node: NavNode | undefined) => node is NavNode)
|
||||
.filter(node => node.nodeType !== NAV_NODE_TYPE_PROJECT || navTreeResource.get(node.id)?.length)
|
||||
.map(node => node.id);
|
||||
|
||||
return nodes;
|
||||
};
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 type { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import { NAV_NODE_TYPE_PROJECT } from '@cloudbeaver/core-projects';
|
||||
|
||||
import type { IElementsTreeNodeExpandInfoGetter } from '../ElementsTree/useElementsTree';
|
||||
|
||||
|
||||
export function navigationTreeProjectsExpandStateGetter(
|
||||
navNodeInfoResource: NavNodeInfoResource
|
||||
): IElementsTreeNodeExpandInfoGetter {
|
||||
|
||||
return nodeId => {
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
if (node?.nodeType !== NAV_NODE_TYPE_PROJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
expanded: true,
|
||||
expandable: false,
|
||||
};
|
||||
};
|
||||
}
|
||||
+12
-1
@@ -7,19 +7,29 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { TreeNodeNestedMessage, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks';
|
||||
import { Translate } from '@cloudbeaver/core-localization';
|
||||
import type { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import { NAV_NODE_TYPE_PROJECT } from '@cloudbeaver/core-projects';
|
||||
|
||||
import { useChildren } from '../../NodesManager/useChildren';
|
||||
import { useNode } from '../../NodesManager/useNode';
|
||||
import type { NavigationNodeRendererComponent } from '../ElementsTree/NavigationNodeComponent';
|
||||
import { NavigationNodeRenderer } from '../ElementsTree/NavigationTreeNode/NavigationNodeRenderer';
|
||||
import type { IElementsTreeCustomRenderer } from '../ElementsTree/useElementsTree';
|
||||
import { NavigationNodeProjectControl } from './NavigationNodeProjectControl';
|
||||
|
||||
const nestedStyles = css`
|
||||
TreeNode {
|
||||
margin-top: 8px;
|
||||
}
|
||||
NavigationNodeNested {
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export function navigationTreeProjectsRendererRenderer(
|
||||
navNodeInfoResource: NavNodeInfoResource
|
||||
): IElementsTreeCustomRenderer {
|
||||
@@ -61,6 +71,7 @@ const ProjectRenderer: NavigationNodeRendererComponent = observer(function Manag
|
||||
dragging={dragging}
|
||||
className={className}
|
||||
control={NavigationNodeProjectControl}
|
||||
style={nestedStyles}
|
||||
component={component}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user