From 8aed3130a5f1d13cf84a1b2f8dbcea7b116fa6de Mon Sep 17 00:00:00 2001 From: Wroud Date: Thu, 13 May 2021 19:53:26 +0300 Subject: [PATCH] refactor: CB-874 navigation tree --- .../src/NavigationTree/ElementsTree.tsx | 70 +++--- .../src/NavigationTree/NavigationTree.tsx | 6 +- .../NavigationTreeNode/NavigationNode.tsx | 7 +- .../NavigationNode/NavigationNodeControl.tsx | 4 +- .../NavigationNode/NavigationNodeNested.tsx | 46 ++-- .../TreeNodeMenu/treeNodeMenuStyles.ts | 2 +- .../NavigationTreeNode/useNavigationNode.ts | 67 ++--- .../NavigationTree/NavigationTreeService.ts | 41 ++- .../src/NavigationTree/TreeContext.ts | 10 +- .../NavigationTree/elementsTreeNameFilter.ts | 35 +++ .../src/NavigationTree/useElementsTree.ts | 238 ++++++++++++++++++ .../src/NavigationTree/useNavigationTree.ts | 37 ++- webapp/packages/core-app/src/index.ts | 1 + .../NodesManager/NavNodeInfoResource.ts | 2 +- .../NodesManager/NavNodeManagerService.ts | 49 +--- .../shared/NodesManager/NavTreeResource.ts | 19 ++ .../src/shared/NodesManager/useNode.ts | 41 ++- .../src/Tree/TreeNode/TreeNode.tsx | 61 ++--- .../src/Tree/TreeNode/TreeNodeContext.ts | 2 +- .../src/Tree/TreeNode/TreeNodeControl.tsx | 5 +- .../src/Tree/TreeNode/TreeNodeExpand.tsx | 5 +- .../src/Tree/TreeNode/TreeNodeFilter.tsx | 7 +- .../src/Tree/TreeNode/TreeNodeSelect.tsx | 29 +-- 23 files changed, 503 insertions(+), 281 deletions(-) create mode 100644 webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts create mode 100644 webapp/packages/core-app/src/NavigationTree/useElementsTree.ts diff --git a/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx b/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx index f3f6303164..1ddd4ff9aa 100644 --- a/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx +++ b/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx @@ -7,18 +7,22 @@ */ import { observer } from 'mobx-react-lite'; -import { useCallback, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import styled, { css } from 'reshadow'; import { Loader, useMapResource } from '@cloudbeaver/core-blocks'; -import { MetadataMap } from '@cloudbeaver/core-utils'; +import { useService } from '@cloudbeaver/core-di'; +import type { MetadataMap } from '@cloudbeaver/core-utils'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; -import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource'; +import { NavNodeInfoResource, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource'; import { NavTreeResource } from '../shared/NodesManager/NavTreeResource'; import { useChildren } from '../shared/useChildren'; +import { elementsTreeNameFilter } from './elementsTreeNameFilter'; +import { NavigationNodeNested } from './NavigationTreeNode/NavigationNode/NavigationNodeNested'; import { NavigationNodeElement } from './NavigationTreeNode/NavigationNodeElement'; import { ITreeContext, TreeContext } from './TreeContext'; +import { IElementsTreeFilter, ITreeNodeState, useElementsTree } from './useElementsTree'; const styles = css` tree { @@ -34,10 +38,6 @@ const styles = css` } `; -export interface ITreeNodeState { - filter: string; -} - interface Props { root?: string; selectionTree?: boolean; @@ -47,61 +47,63 @@ interface Props { }>; emptyPlaceholder: React.FC; className?: string; + filters?: IElementsTreeFilter[]; + customSelect?: (node: NavNode, multiple: boolean) => void; + isGroup?: (node: NavNode) => boolean; + onExpand?: (node: NavNode, state: boolean) => Promise | void; onOpen?: (node: NavNode) => Promise | void; - onSelect?: (node: NavNode, multiple: boolean) => void; - isSelected?: (node: NavNode) => boolean; + onSelect?: (node: NavNode, state: boolean) => void; onFilter?: (node: NavNode, value: string) => void; } export const ElementsTree: React.FC = observer(function ElementsTree({ - root, + root = ROOT_NODE_PATH, control, localState, selectionTree = false, emptyPlaceholder, + filters, className, + isGroup, + customSelect, + onExpand, onOpen, onSelect, - isSelected, onFilter, }) { const nodeChildren = useChildren(root); const Placeholder = emptyPlaceholder; - useMapResource(NavTreeResource, root || ROOT_NODE_PATH); - const [localTreeNodesState] = useState(() => new MetadataMap(() => ({ filter: '' }))); + const navNodeInfoResource = useService(NavNodeInfoResource); - const treeNodesState = localState || localTreeNodesState; + useMapResource(NavTreeResource, root); - const getTreeNodeState = useCallback((node: NavNode) => - treeNodesState.get(node.id), [treeNodesState]); + const nameFilter = useMemo(() => elementsTreeNameFilter(navNodeInfoResource), [navNodeInfoResource]); - const onFilterHandler = useCallback((node: NavNode, value: string) => { - const treeNodeState = treeNodesState.get(node.id); - treeNodeState.filter = value; - - if (onFilter) { - onFilter(node, value); - } - }, [treeNodesState, onFilter]); + const tree = useElementsTree({ + root, + localState, + filters: [nameFilter, ...(filters || [])], + isGroup, + onFilter, + customSelect, + onExpand, + onSelect, + }); const context = useMemo( () => ({ - treeNodesState, - getTreeNodeState, + tree, selectionTree, control, onOpen, - onSelect, - isSelected, - onFilter: onFilterHandler, }), - [control, selectionTree, onOpen, onSelect, isSelected, onFilterHandler, treeNodesState, getTreeNodeState] + [control, selectionTree, onOpen] ); if (!nodeChildren.children || nodeChildren.children.length === 0) { if (nodeChildren.isLoading()) { return styled(styles)( -
+
); @@ -112,10 +114,8 @@ export const ElementsTree: React.FC = observer(function ElementsTree({ return styled(styles)( - - {nodeChildren.children.map(id => ( - - ))} + + diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx index d2658b9c8d..36a1d38eee 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx @@ -58,7 +58,7 @@ export const NavigationTree = observer(function NavigationTree() { const [onFocus, onBlur] = useActiveView(navTreeService.getView); const [ref] = useFocus({ onFocus, onBlur }); const isEnabled = usePermission(EPermission.public); - const { isSelected, handleOpen, handleSelect } = useNavigationTree(); + const { handleOpen, handleSelect } = useNavigationTree(); if (!isEnabled) { return null; @@ -68,6 +68,7 @@ export const NavigationTree = observer(function NavigationTree() { styled(navigationTreeStyles)(
@@ -76,9 +77,8 @@ export const NavigationTree = observer(function NavigationTree() {
)} - isSelected={isSelected} + customSelect={handleSelect} onOpen={handleOpen} - onSelect={handleSelect} />
); diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx index 3f067d3f53..4908a2aff0 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx @@ -7,9 +7,8 @@ */ import { observer } from 'mobx-react-lite'; -import { useContext } from 'react'; -import { TreeNode, TreeNodeContext } from '@cloudbeaver/core-blocks'; +import { TreeNode } from '@cloudbeaver/core-blocks'; import type { NavNode } from '../../shared/NodesManager/EntityTypes'; import { NavigationNodeControl } from './NavigationNode/NavigationNodeControl'; @@ -27,8 +26,6 @@ export const NavigationNode: React.FC = observer(function NavigationNode( node, component, }) { - const context = useContext(TreeNodeContext); - const { control, selected, @@ -57,7 +54,7 @@ export const NavigationNode: React.FC = observer(function NavigationNode( onFilter={handleFilter} > - + {expanded && } ); }); diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx index 9016180952..b155161f3f 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx @@ -79,10 +79,10 @@ export const NavigationNodeControl: React.FC = observer(function Navigati - {connected && } + {connected && } {node.name} - + diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.tsx index 2749281ea7..8e5348322e 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.tsx @@ -11,57 +11,49 @@ import { observer } from 'mobx-react-lite'; import { useContext, useMemo } from 'react'; import styled from 'reshadow'; -import { TreeNodeContext, TreeNodeNested, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { resourceKeyList } from '@cloudbeaver/core-sdk'; +import { TreeNodeNested, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; import { useStyles } from '@cloudbeaver/core-theming'; -import { NavNodeInfoResource } from '../../../shared/NodesManager/NavNodeInfoResource'; -import { useChildren } from '../../../shared/useChildren'; +import { TreeContext } from '../../TreeContext'; interface Props { nodeId: string; component: React.FC<{ nodeId: string; }>; -} - -function isDefined(val: T | undefined | null): val is T { - return val !== undefined && val !== null; + root?: boolean; } export const NavigationNodeNested: React.FC = observer(function NavigationNodeNested({ nodeId, component, + root, }) { const styles = useStyles(TREE_NODE_STYLES); - const context = useContext(TreeNodeContext); - const navNodeInfo = useService(NavNodeInfoResource); - const childrenInfo = useChildren(nodeId); + const treeContext = useContext(TreeContext); - const children = useMemo(() => computed(() => { - if (!childrenInfo?.children) { - return []; - } + const children = useMemo( + () => computed(() => treeContext?.tree.getNodeChildren(nodeId) || []), + [nodeId, treeContext?.tree] + ).get(); - const childrenEntities = navNodeInfo.get(resourceKeyList(childrenInfo.children)).filter(isDefined); - - if (!context?.filterValue) { - return childrenEntities; - } - - return childrenEntities.filter(child => child.name?.toLowerCase().includes(context.filterValue.toLowerCase())); - }), [childrenInfo.children, context?.filterValue, navNodeInfo]).get(); - - if (!children.length || !context?.expanded) { + if (children.length === 0) { return null; } const NavigationNode = component; + if (root) { + return styled(styles)( + <> + {children.map(child => )} + + ); + } + return styled(styles)( - {children.map(child => )} + {children.map(child => )} ); }); diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/treeNodeMenuStyles.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/treeNodeMenuStyles.ts index d536d2827e..31bc93f280 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/treeNodeMenuStyles.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/treeNodeMenuStyles.ts @@ -12,7 +12,7 @@ export const treeNodeMenuStyles = css` MenuTrigger { margin-left: 16px; padding: 0; - height: 20px; + height: 16px; &:before { display: none; diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts index 5dc903fd17..928f4c7075 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts @@ -8,6 +8,7 @@ import { useContext, useEffect, useState } from 'react'; +import { useObjectRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import type { NavNode } from '../../shared/NodesManager/EntityTypes'; @@ -27,29 +28,29 @@ interface INavigationNode { leaf: boolean; handleExpand: () => void; handleOpen: () => void; - handleSelect: (isMultiple?: boolean) => void; + handleSelect: (isMultiple?: boolean, nested?: boolean) => void; handleFilter: (value: string) => void; filterValue: string; } export function useNavigationNode(node: NavNode): INavigationNode { - const context = useContext(TreeContext); + const contextRef = useObjectRef({ + context: useContext(TreeContext), + }); const navigationTreeService = useService(NavigationTreeService); const [processing, setProcessing] = useState(false); - const { isLoading, isOutdated } = useNode(node.id); + const { isLoading } = useNode(node.id); const children = useChildren(node.id); const loading = isLoading() || children.isLoading() || processing; - const isExpanded = navigationTreeService.isNodeExpanded(node.id); - let leaf = isLeaf(node) || (children.children?.length === 0 && !children.isOutdated()); + const state = contextRef.context?.tree.getNodeState(node.id); + const isExpanded = state?.expanded || false; + let leaf = isLeaf(node); let expanded = isExpanded && !leaf; if ( node.objectFeatures.includes(EObjectFeature.dataSource) - && ( - !node.objectFeatures.includes(EObjectFeature.dataSourceConnected) - || (!children.children && isOutdated()) - ) + && !node.objectFeatures.includes(EObjectFeature.dataSourceConnected) ) { leaf = false; expanded = false; @@ -62,61 +63,43 @@ export function useNavigationNode(node: NavNode): INavigationNode { clearTimeout(timeout); setProcessing(false); if (!state) { - navigationTreeService.expandNode(node.id, false); + contextRef.context?.tree.expand(node, false); return; } } - navigationTreeService.expandNode(node.id, !expanded); + contextRef.context?.tree.expand(node, !expanded); }; const handleOpen = async () => { setProcessing(true); try { - await context?.onOpen?.(node); + await contextRef.context?.onOpen?.(node); } finally { setProcessing(false); } }; - const handleSelect = (multiple = false) => { - context?.onSelect?.(node, multiple); + const handleSelect = (multiple = false, nested = false) => { + contextRef.context?.tree.select(node, multiple, nested); }; const handleFilter = (value: string) => { - context?.onFilter?.(node, value); + contextRef.context?.tree.filter(node, value); }; - // TODO: probably should be refactored - useEffect(() => { - if (expanded && children.isOutdated() && !children.isLoading() && children.isLoaded() && !isOutdated()) { - setProcessing(true); - navigationTreeService - .loadNestedNodes(node.id) - .then(state => { - setProcessing(false); - if (!state) { - navigationTreeService.expandNode(node.id, false); - } - }); - } - }, [expanded, children.isOutdated(), children.isLoading(), children.isLoaded(), isOutdated(), node]); - useEffect(() => () => { - if (!context?.selectionTree && node && context?.isSelected?.(node)) { - context.onSelect?.(node, true); + if (!contextRef.context?.selectionTree) { + const state = contextRef.context?.tree.getNodeState(node.id); + + if (state?.selected) { + contextRef.context?.tree.select(node, true, false); + } } - }, [context, node.id]); - - useEffect(() => () => { - // TODO: seems like selection & expand should be specific for separate tree definitions - navigationTreeService.expandNode(node.id, false); - - context?.treeNodesState?.delete(node.id); }, [node.id]); return { - control: context?.control, - selected: context?.isSelected?.(node) || false, + control: contextRef.context?.control, + selected: state?.selected || false, loading, expanded, leaf, @@ -124,7 +107,7 @@ export function useNavigationNode(node: NavNode): INavigationNode { handleOpen, handleSelect, handleFilter, - filterValue: context?.treeNodesState?.get(node.id).filter || '', + filterValue: state?.filter || '', }; } diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts index 33167f4549..ae2b1620a1 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts @@ -12,16 +12,16 @@ import { ConnectionAuthService } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { Executor, IExecutor } from '@cloudbeaver/core-executor'; -import { ResourceKeyUtils } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; import type { IActiveView } from '@cloudbeaver/core-view'; import { EObjectFeature } from '../shared/NodesManager/EObjectFeature'; import { NavNodeExtensionsService } from '../shared/NodesManager/NavNodeExtensionsService'; -import { NavNodeInfoResource, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource'; +import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource'; import { NavNodeManagerService } from '../shared/NodesManager/NavNodeManagerService'; import { NavTreeResource } from '../shared/NodesManager/NavTreeResource'; import { NodeManagerUtils } from '../shared/NodesManager/NodeManagerUtils'; +import type { ITreeNodeState } from './useElementsTree'; export interface INavigationNodeMetadata { selected: boolean; @@ -35,7 +35,7 @@ export interface INavigationNodeSelectionData { @injectable() export class NavigationTreeService { - readonly navigationTreeMetadata: MetadataMap; + readonly treeState: MetadataMap; readonly nodeSelectionTask: IExecutor; constructor( @@ -43,30 +43,19 @@ export class NavigationTreeService { private notificationService: NotificationService, private connectionAuthService: ConnectionAuthService, private navNodeExtensionsService: NavNodeExtensionsService, - private navTreeResource: NavTreeResource, - private navNodeInfoResource: NavNodeInfoResource + private navTreeResource: NavTreeResource ) { makeObservable(this, { unselectAll: action, }); + this.treeState = new MetadataMap(() => ({ + filter: '', + expanded: false, + selected: false, + })); this.nodeSelectionTask = new Executor(); - this.navigationTreeMetadata = new MetadataMap(() => ({ - selected: false, - expanded: false, - })); this.getView = this.getView.bind(this); - - this.navNodeInfoResource.onItemDelete.addHandler(key => { - ResourceKeyUtils.forEach(key, key => { - this.navigationTreeMetadata.delete(key); - }); - }); - this.navTreeResource.onItemDelete.addHandler(key => { - ResourceKeyUtils.forEach(key, key => { - this.expandNode(key, false); - }); - }); } getChildren(id: string): string[] | undefined { @@ -95,7 +84,7 @@ export class NavigationTreeService { await this.unselectAll(); } - const metadata = this.navigationTreeMetadata.get(id); + const metadata = this.treeState.get(id); metadata.selected = !metadata.selected; await this.nodeSelectionTask.execute({ @@ -105,20 +94,20 @@ export class NavigationTreeService { } isNodeExpanded(navNodeId: string): boolean { - return this.navigationTreeMetadata.get(navNodeId).expanded; + return this.treeState.get(navNodeId).expanded; } isNodeSelected(navNodeId: string): boolean { - return this.navigationTreeMetadata.get(navNodeId).selected; + return this.treeState.get(navNodeId).selected; } expandNode(navNodeId: string, state: boolean): void { - const metadata = this.navigationTreeMetadata.get(navNodeId); + const metadata = this.treeState.get(navNodeId); metadata.expanded = state; } getView(): IActiveView | null { - const element = Array.from(this.navigationTreeMetadata).find(([key, metadata]) => metadata.selected); + const element = Array.from(this.treeState).find(([key, metadata]) => metadata.selected); if (!element) { return null; @@ -131,7 +120,7 @@ export class NavigationTreeService { } private async unselectAll() { - for (const [id, metadata] of this.navigationTreeMetadata) { + for (const [id, metadata] of this.treeState) { metadata.selected = false; await this.nodeSelectionTask.execute({ id, diff --git a/webapp/packages/core-app/src/NavigationTree/TreeContext.ts b/webapp/packages/core-app/src/NavigationTree/TreeContext.ts index feda4b1ee3..31e9d5ecc4 100644 --- a/webapp/packages/core-app/src/NavigationTree/TreeContext.ts +++ b/webapp/packages/core-app/src/NavigationTree/TreeContext.ts @@ -8,22 +8,16 @@ import { createContext } from 'react'; -import type { MetadataMap } from '@cloudbeaver/core-utils'; - import type { NavNode } from '../shared/NodesManager/EntityTypes'; -import type { ITreeNodeState } from './ElementsTree'; +import type { IElementsTree } from './useElementsTree'; export interface ITreeContext { - treeNodesState?: MetadataMap; - getTreeNodeState?: (node: NavNode) => ITreeNodeState; + tree: IElementsTree; selectionTree: boolean; control?: React.FC<{ node: NavNode; }>; onOpen?: (node: NavNode) => Promise | void; - onSelect?: (node: NavNode, multiple: boolean) => void; - isSelected?: (node: NavNode) => boolean; - onFilter?: (node: NavNode, value: string) => void; } export const TreeContext = createContext(null); diff --git a/webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts b/webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts new file mode 100644 index 0000000000..9d1672d885 --- /dev/null +++ b/webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts @@ -0,0 +1,35 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { resourceKeyList } from '@cloudbeaver/core-sdk'; +import type { MetadataMap } from '@cloudbeaver/core-utils'; + +import type { NavNode } from '../shared/NodesManager/EntityTypes'; +import type { NavNodeInfoResource } from '../shared/NodesManager/NavNodeInfoResource'; +import type { IElementsTreeFilter, ITreeNodeState } from './useElementsTree'; + +function isDefined(val: T | undefined | null): val is T { + return val !== undefined && val !== null; +} + +export function elementsTreeNameFilter(resource: NavNodeInfoResource): IElementsTreeFilter { + return (node: NavNode, children: string[], state: MetadataMap) => { + const nodeState = state.get(node.id); + + if (nodeState.filter === '') { + return children; + } + + const nodes = resource + .get(resourceKeyList(children)) + .filter(isDefined) + .filter(child => child.name?.toLowerCase().includes(nodeState.filter.toLowerCase())); + + return nodes.map(node => node.id); + }; +} diff --git a/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts b/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts new file mode 100644 index 0000000000..1bb747278a --- /dev/null +++ b/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts @@ -0,0 +1,238 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { useState } from 'react'; + +import { useExecutor, useObjectRef } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { ResourceKeyUtils } from '@cloudbeaver/core-sdk'; +import { MetadataMap } from '@cloudbeaver/core-utils'; + +import type { NavNode } from '../shared/NodesManager/EntityTypes'; +import { NavNodeInfoResource } from '../shared/NodesManager/NavNodeInfoResource'; +import { NavTreeResource } from '../shared/NodesManager/NavTreeResource'; +import { NavigationTreeService } from './NavigationTreeService'; + +export type IElementsTreeFilter = ( + node: NavNode, + children: string[], + state: MetadataMap +) => string[]; + +export interface ITreeNodeState { + filter: string; + selected: boolean; + expanded: boolean; +} + +interface IOptions { + root: string; + localState?: MetadataMap; + filters?: IElementsTreeFilter[]; + customSelect?: (node: NavNode, multiple: boolean, nested: boolean) => void; + isGroup?: (node: NavNode) => boolean; + onExpand?: (node: NavNode, state: boolean) => void; + onSelect?: (node: NavNode, state: boolean) => void; + onFilter?: (node: NavNode, value: string) => void; +} + +export interface IElementsTree { + state: MetadataMap; + getNodeState: (nodeId: string) => ITreeNodeState; + getNodeChildren: (nodeId: string) => string[]; + filter: (node: NavNode, value: string) => void; + select: (node: NavNode, multiple: boolean, nested: boolean) => void; + expand: (node: NavNode, state: boolean) => void; +} + +export function useElementsTree(options: IOptions): IElementsTree { + const navNodeInfoResource = useService(NavNodeInfoResource); + const navTreeService = useService(NavigationTreeService); + const navTreeResource = useService(NavTreeResource); + + const [localTreeNodesState] = useState(() => new MetadataMap(() => ({ + filter: '', + selected: false, + expanded: false, + }))); + + const state = options.localState || localTreeNodesState; + + function getNodeChildren(nodeId: string): string[] { + const node = navNodeInfoResource.get(nodeId); + + if (!node) { + return []; // Maybe filter should accept nodeId, so we be able to apply filters to empty node + } + + return (options.filters || []) + .reduce( + (children, filter) => filter(node, children, state), + navTreeService.getChildren(node.id) || [] + ); + } + + function getNestedChildren(nodeId: string): string [] { + const nestedChildren: string[] = []; + const prevChildren = getNodeChildren(nodeId); + nestedChildren.push(...prevChildren); + + while (prevChildren.length) { + const nodeKey = prevChildren.shift()!; + const children = getNodeChildren(nodeKey); + prevChildren.push(...children); + nestedChildren.push(...children); + } + + return nestedChildren; + } + + function clearSelection(nodeId: string) { + const node = navNodeInfoResource.get(nodeId); + + const ignore = node && options.isGroup?.(node) + ? getNestedChildren(nodeId) + : []; + + for (const [id, nodeState] of state) { + if (nodeState.selected && id !== nodeId && !ignore.includes(id)) { + nodeState.selected = false; + + if (options.onSelect) { + const node = navNodeInfoResource.get(id); + + if (node) { + options.onSelect(node, false); + } + } + } + } + } + + function setSelection(nodeId: string, selected: boolean): void { + const node = navNodeInfoResource.get(nodeId); + + if (!node) { + return; + } + + const treeNodeState = state.get(nodeId); + + if (treeNodeState.selected === selected) { + return; + } + + if (options.isGroup?.(node)) { + const children = getNodeChildren(nodeId); + + for (const child of children) { + setSelection(child, selected); + } + } + + treeNodeState.selected = selected; + options.onSelect?.(node, selected); + } + + const elementsTree = useObjectRef({ + state, + getNodeState(nodeId: string) { + return this.state.get(nodeId); + }, + getNodeChildren, + filter(node: NavNode, value: string) { + const treeNodeState = this.state.get(node.id); + treeNodeState.filter = value; + + options.onFilter?.(node, value); + }, + expand(node: NavNode, state: boolean) { + const treeNodeState = this.state.get(node.id); + treeNodeState.expanded = state; + + options.onExpand?.(node, state); + }, + select(node: NavNode, multiple: boolean, nested: boolean) { + if (options.customSelect) { + options.customSelect(node, multiple, nested); + return; + } + + const treeNodeState = this.state.get(node.id); + + if (!multiple) { + clearSelection(node.id); + + if (treeNodeState.selected) { + return; + } + } + + setSelection(node.id, !treeNodeState.selected); + }, + }); + + async function refreshNode(nodeId: string) { + let children = [nodeId]; + + while (children.length > 0) { + const nextChildren: string[] = []; + + for (const child of children) { + const nodeState = state.get(child); + if (!nodeState.expanded && child !== options.root) { + continue; + } + + const loaded = await navTreeService.loadNestedNodes(child); + + if (!loaded) { + const node = navNodeInfoResource.get(child); + + if (node) { + elementsTree.expand(node, false); + } + continue; + } + + nextChildren.push(...(navTreeResource.get(child) || [])); + } + + children = nextChildren; + } + } + + useExecutor({ + executor: navTreeResource.onNodeRefresh, + handlers: [refreshNode], + }); + + useExecutor({ + executor: navNodeInfoResource.onItemDelete, + handlers: [function deleteNodeState(key) { + ResourceKeyUtils.forEach(key, key => { + state.delete(key); + }); + }], + }); + + useExecutor({ + executor: navTreeResource.onItemDelete, + handlers: [function collapseDeletedTree(key) { + ResourceKeyUtils.forEach(key, key => { + const node = navNodeInfoResource.get(key); + + if (node) { + elementsTree.expand(node, false); + } + }); + }], + }); + + return elementsTree; +} diff --git a/webapp/packages/core-app/src/NavigationTree/useNavigationTree.ts b/webapp/packages/core-app/src/NavigationTree/useNavigationTree.ts index 8883ec0a03..143d51a113 100644 --- a/webapp/packages/core-app/src/NavigationTree/useNavigationTree.ts +++ b/webapp/packages/core-app/src/NavigationTree/useNavigationTree.ts @@ -6,39 +6,30 @@ * you may not use this file except in compliance with the License. */ -import { useCallback } from 'react'; - +import { useObjectRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; import { NavigationTreeService } from './NavigationTreeService'; interface INavigationTree { - isSelected: (node: NavNode) => boolean; + navigationTreeService: NavigationTreeService; handleOpen: (node: NavNode) => Promise; - handleSelect: (node: NavNode, isMultiple: boolean) => void; + handleSelect: (node: NavNode, state: boolean) => void; } +const bindActions: Array = ['handleOpen', 'handleSelect']; + export function useNavigationTree(): INavigationTree { const navigationTreeService = useService(NavigationTreeService); - const handleOpen = useCallback( - (node: NavNode) => navigationTreeService.navToNode(node.id, node.parentId), - [navigationTreeService] - ); - - const handleSelect = useCallback( - (node: NavNode, multiple: boolean) => navigationTreeService.selectNode(node.id, multiple), - [navigationTreeService]); - - const isSelected = useCallback( - (node: NavNode) => navigationTreeService.isNodeSelected(node.id), - [navigationTreeService] - ); - - return { - isSelected, - handleOpen, - handleSelect, - }; + return useObjectRef({ + navigationTreeService, + handleOpen(node: NavNode) { + return this.navigationTreeService.navToNode(node.id, node.parentId); + }, + handleSelect(node: NavNode, state: boolean) { + return this.navigationTreeService.selectNode(node.id, state); + }, + }, { navigationTreeService }, undefined, bindActions); } diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index c2ca8bead4..a17e14f187 100644 --- a/webapp/packages/core-app/src/index.ts +++ b/webapp/packages/core-app/src/index.ts @@ -27,6 +27,7 @@ export * from './NavigationTree/ElementsTree'; export * from './NavigationTree/TreeContext'; export * from './NavigationTree/NavigationTreeService'; export * from './NavigationTree/NavigationTreeContextMenuService'; +export * from './NavigationTree/useElementsTree'; export * from './AppLocaleService'; // Models diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts index 682d0db38c..428dbea403 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts @@ -60,7 +60,7 @@ export class NavNodeInfoResource extends CachedMapResource { }); } - protected async loader(key: ResourceKey) { + protected async loader(key: ResourceKey): Promise> { if (isResourceKeyList(key)) { const values: NavNode[] = []; for (const nodePath of key.list) { diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts index 776a33db5c..606d5fbb0c 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts @@ -16,7 +16,7 @@ import { PermissionsService, EPermission, ServerService } from '@cloudbeaver/core-root'; import { - GraphQLService, resourceKeyList, ResourceKey, ResourceKeyUtils + resourceKeyList, ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; import { NavigationService } from '@cloudbeaver/core-ui'; @@ -92,7 +92,6 @@ export class NavNodeManagerService extends Bootstrap { readonly navigator: IExecutor; constructor( - private graphQLService: GraphQLService, private permissionsService: PermissionsService, readonly connectionInfo: ConnectionInfoResource, readonly navTree: NavTreeResource, @@ -116,8 +115,6 @@ export class NavNodeManagerService extends Bootstrap { } register(): void { - // this.sessionDataResource.onDataUpdate.addHandler(this.refreshRoot.bind(this)); - // this.connectionInfo.onSessionUpdate.addHandler(this.connectionRefreshHandler.bind(this)); this.connectionInfo.onItemAdd.addHandler(this.connectionUpdateHandler.bind(this)); this.connectionInfo.onItemDelete.addHandler(this.connectionRemoveHandler.bind(this)); this.connectionInfo.onConnectionCreate.addHandler(this.connectionCreateHandler.bind(this)); @@ -135,15 +132,7 @@ export class NavNodeManagerService extends Bootstrap { } async refreshTree(navNodeId: string): Promise { - await this.graphQLService.sdk.navRefreshNode({ - nodePath: navNodeId, - }); - this.markTreeOutdated(navNodeId); - await this.navTree.refresh(navNodeId); - } - - markTreeOutdated(navNodeId: ResourceKey): void { - this.navTree.markOutdated(resourceKeyList(this.navTree.getNestedChildren(navNodeId))); + await this.navTree.refreshTree(navNodeId); } getTree(navNodeId: string): string[] | undefined @@ -169,10 +158,6 @@ export class NavNodeManagerService extends Bootstrap { this.navTree.delete(path); } - async refreshNode(navNodeId: string): Promise { - await this.navNodeInfoResource.refresh(navNodeId); - } - getNode(navNodeId: string): NavNode | undefined getNode(navNodeKey: NavNodeKey): NavNode | undefined getNode(navNodeKey: NavNodeKey[]): Array @@ -329,22 +314,6 @@ export class NavNodeManagerService extends Bootstrap { }; }; - async updateRoot(): Promise { - const enabled = await this.isNavTreeEnabled(); - if (enabled) { - await this.navTree.refresh(ROOT_NODE_PATH); - } - } - - async refreshRoot(): Promise { - this.navTree.delete(ROOT_NODE_PATH); - const enabled = await this.isNavTreeEnabled(); - if (enabled) { - // this.navTree.markOutdated(ROOT_NODE_PATH); - await this.navTree.refresh(ROOT_NODE_PATH); - } - } - private async connectionCreateHandler(connection: Connection) { const enabled = await this.isNavTreeEnabled(); if (!enabled) { @@ -352,7 +321,7 @@ export class NavNodeManagerService extends Bootstrap { } const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(connection.id); - this.markTreeOutdated(nodeId); + this.navTree.markTreeOutdated(nodeId); const tree = await this.navTree.load(ROOT_NODE_PATH); @@ -361,16 +330,6 @@ export class NavNodeManagerService extends Bootstrap { } } - private async connectionRefreshHandler(connections: Connection[]) { - const enabled = await this.isNavTreeEnabled(); - if (!enabled) { - return; - } - - // this.navTree.markOutdated(ROOT_NODE_PATH); - await this.navTree.refresh(ROOT_NODE_PATH); - } - private async connectionUpdateHandler(key: ResourceKey) { const enabled = await this.isNavTreeEnabled(); if (!enabled) { @@ -382,7 +341,7 @@ export class NavNodeManagerService extends Bootstrap { await this.navTree.load(ROOT_NODE_PATH); await ResourceKeyUtils.forEachAsync(key, async key => { const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key); - this.markTreeOutdated(nodeId); + this.navTree.markTreeOutdated(nodeId); // addOpenedConnection const connectionInfo = this.connectionInfo.get(key); diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts index fdf7312140..a797e56552 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -9,6 +9,7 @@ import { action, computed, makeObservable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; +import { Executor, IExecutor } from '@cloudbeaver/core-executor'; import { SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, @@ -39,6 +40,7 @@ interface INodeMetadata extends ICachedMapResourceMetadata { @injectable() export class NavTreeResource extends CachedMapResource { + readonly onNodeRefresh: IExecutor; protected metadata: MetadataMap; get childrenLimit(): number { @@ -66,10 +68,25 @@ export class NavTreeResource extends CachedMapResource { exception: null, includes: [], })); + + this.onNodeRefresh = new Executor(null, (a, b) => a === b); this.onDataOutdated.addHandler(navNodeInfoResource.markOutdated.bind(navNodeInfoResource)); this.sessionDataResource.onDataUpdate.addPostHandler(() => this.markOutdated()); } + async refreshTree(navNodeId: string): Promise { + await this.graphQLService.sdk.navRefreshNode({ + nodePath: navNodeId, + }); + this.markTreeOutdated(navNodeId); + await this.refresh(navNodeId); + await this.onNodeRefresh.execute(navNodeId); + } + + markTreeOutdated(navNodeId: ResourceKey): void { + this.markOutdated(resourceKeyList(this.getNestedChildren(navNodeId))); + } + setDetails(keyObject: ResourceKey, state: boolean): void { ResourceKeyUtils.forEach(keyObject, key => { const children = resourceKeyList(this.getNestedChildren(key)); @@ -253,6 +270,8 @@ export class NavTreeResource extends CachedMapResource { withDetails: metadata.withDetails, }); + navNodeInfo.hasChildren = navNodeChildren.length > 0; + return { navNodeChildren: navNodeChildren.slice(0, this.childrenLimit), navNodeInfo, parentPath }; } } diff --git a/webapp/packages/core-app/src/shared/NodesManager/useNode.ts b/webapp/packages/core-app/src/shared/NodesManager/useNode.ts index 3c4dd5d3c6..2e36b30192 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/useNode.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/useNode.ts @@ -6,23 +6,40 @@ * you may not use this file except in compliance with the License. */ -import { useCallback } from 'react'; - +import { useObjectRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; +import type { NavNode } from './EntityTypes'; import { NavNodeInfoResource } from './NavNodeInfoResource'; -export function useNode(navNodeId: string) { +interface IUseNodeHook { + navNodeId: string; + node: NavNode | undefined; + isLoading: () => boolean; + isLoaded: () => boolean; + isOutdated: () => boolean; +} + +const bindActions: Array = ['isLoading', 'isLoaded', 'isOutdated']; + +export function useNode(navNodeId: string): IUseNodeHook { const navNodeInfoResource = useService(NavNodeInfoResource); const node = navNodeInfoResource.get(navNodeId); - const deps = [navNodeId]; - - const isLoading = useCallback(() => navNodeInfoResource.isDataLoading(navNodeId), deps); - const isLoaded = useCallback(() => navNodeInfoResource.isLoaded(navNodeId), deps); - const isOutdated = useCallback(() => navNodeInfoResource.isOutdated(navNodeId), deps); - - return { - node, isLoading, isLoaded, isOutdated, - }; + return useObjectRef({ + navNodeId, + node, + isLoading() { + return navNodeInfoResource.isDataLoading(this.navNodeId); + }, + isLoaded() { + return navNodeInfoResource.isLoaded(this.navNodeId); + }, + isOutdated() { + return navNodeInfoResource.isOutdated(this.navNodeId); + }, + }, { + navNodeId, + node, + }, undefined, bindActions); } diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx index b97bb168eb..05dee4371f 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx @@ -6,11 +6,13 @@ * you may not use this file except in compliance with the License. */ -import { useCallback, useMemo, useRef, memo } from 'react'; +import { observable } from 'mobx'; +import { memo } from 'react'; import styled, { use } from 'reshadow'; import { useStyles } from '@cloudbeaver/core-theming'; +import { useObjectRef } from '../../useObjectRef'; import { ITreeNodeContext, TreeNodeContext } from './TreeNodeContext'; import { TREE_NODE_STYLES } from './TreeNodeStyles'; @@ -21,7 +23,7 @@ interface Props { leaf?: boolean; className?: string; onExpand?: () => void; - onSelect?: (multiple?: boolean) => void; + onSelect?: (multiple?: boolean, nested?: boolean) => void; onFilter?: (value: string) => void; filterValue?: string; onOpen?: () => void; @@ -37,41 +39,42 @@ export const TreeNode: React.FC = memo(function TreeNode({ children, ...handlers }) { - const handlersRef = useRef(handlers); - handlersRef.current = handlers; + const handlersRef = useObjectRef(handlers); - const handleExpand = useCallback(() => { - handlersRef.current.onExpand?.(); - }, []); - - const handleSelect = useCallback( - (multiple?: boolean): void => handlersRef.current.onSelect?.(multiple), - [] - ); - - const handleFilter = useCallback( - (value: string): void => handlersRef.current.onFilter?.(value), - [] - ); - - const handleOpen = useCallback(() => { - handlersRef.current.onOpen?.(); - }, []); - - const nodeContext = useMemo(() => ({ + const nodeContext = useObjectRef({ loading, selected, expanded, leaf, filterValue, - expand: handleExpand, - select: handleSelect, - filter: handleFilter, - open: handleOpen, - }), [loading, selected, expanded, leaf, filterValue, handleExpand, handleSelect, handleOpen, handleFilter]); + expand() { + handlersRef.onExpand?.(); + }, + select(multiple?: boolean, nested?: boolean): void { + handlersRef.onSelect?.(multiple, nested); + }, + filter(value: string): void { + handlersRef.onFilter?.(value); + }, + open() { + handlersRef.onOpen?.(); + }, + }, { + loading, + selected, + expanded, + leaf, + filterValue, + }, { + loading: observable.ref, + selected: observable.ref, + expanded: observable.ref, + leaf: observable.ref, + filterValue: observable.ref, + }); return styled(useStyles(TREE_NODE_STYLES))( - + {children} diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts index d6c9fe22f5..17b9dc67ff 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts @@ -13,7 +13,7 @@ export interface ITreeNodeContext { loading: boolean; selected: boolean; leaf: boolean; - select: (multiple?: boolean) => void; + select: (multiple?: boolean, nested?: boolean) => void; filter: (value: string) => void; filterValue: string; expand: () => void; diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx index 2ef30759d2..f5e041fca8 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx @@ -6,6 +6,7 @@ * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react-lite'; import { useContext } from 'react'; import { TreeNodeContext } from './TreeNodeContext'; @@ -20,7 +21,7 @@ interface Props { big?: boolean; } -export const TreeNodeControl: React.FC = function TreeNodeControl({ +export const TreeNodeControl: React.FC = observer(function TreeNodeControl({ onClick, className, children, @@ -61,4 +62,4 @@ export const TreeNodeControl: React.FC = function TreeNodeControl({ {children} ); -}; +}); diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx index 0fd443104d..a970ddd51d 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx @@ -6,6 +6,7 @@ * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react-lite'; import { useContext } from 'react'; import styled from 'reshadow'; import { css } from 'reshadow'; @@ -28,7 +29,7 @@ interface Props { className?: string; } -export const TreeNodeExpand: React.FC = function TreeNodeExpand({ +export const TreeNodeExpand: React.FC = observer(function TreeNodeExpand({ leaf, big, className, @@ -58,4 +59,4 @@ export const TreeNodeExpand: React.FC = function TreeNodeExpand({ {!context.loading && !context.leaf && !leaf && !big && } ); -}; +}); diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeFilter.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeFilter.tsx index 7b71ceaa4b..2e1847cc29 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeFilter.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeFilter.tsx @@ -6,6 +6,7 @@ * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react-lite'; import { useCallback, useContext, useEffect, useState } from 'react'; import styled, { css, use } from 'reshadow'; @@ -56,7 +57,7 @@ interface Props { className?: string; } -export const TreeNodeFilter: React.FC = function TreeNodeFilter({ +export const TreeNodeFilter: React.FC = observer(function TreeNodeFilter({ disabled, className, }) { @@ -102,7 +103,7 @@ export const TreeNodeFilter: React.FC = function TreeNodeFilter({ useEffect(() => () => context.filter(''), []); return styled(useStyles(styles))( - + = function TreeNodeFilter({ ); -}; +}); diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx index 74c16bbc1b..6c2c831e50 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx @@ -6,27 +6,24 @@ * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react-lite'; import { useContext } from 'react'; -import styled from 'reshadow'; -import { css } from 'reshadow'; import { Checkbox } from '../../FormControls/Checkboxes/Checkbox'; import { TreeNodeContext } from './TreeNodeContext'; -const styles = css` - Icon { - cursor: pointer; - height: 100%; - width: 100%; - } -`; - interface Props { + group?: boolean; + onSelect?: () => void; + selected?: boolean; disabled?: boolean; className?: string; } -export const TreeNodeSelect: React.FC = function TreeNodeSelect({ +export const TreeNodeSelect: React.FC = observer(function TreeNodeSelect({ + onSelect, + group, + selected, disabled, className, }) { @@ -45,9 +42,13 @@ export const TreeNodeSelect: React.FC = function TreeNodeSelect({ event.preventDefault(); }; - return styled(styles)( + return (
- context.select(true)} /> + context.select(true, group))} + />
); -}; +});