From d62aeb0ba46405bf9b0f306daa6adb3fe25616d3 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 2 Feb 2022 16:47:22 +0300 Subject: [PATCH] feat: CB-1405 filtering objects in nav tree --- .../src/NavigationTree/ElementsTree.tsx | 98 ++++++++++++++----- .../src/NavigationTree/NavigationTree.tsx | 2 +- .../NavigationTreeNode/NavigationNode.tsx | 4 - .../NavigationNode/NavigationNodeNested.tsx | 5 +- .../NavigationTreeNode/useNavigationNode.ts | 8 +- .../NavigationTree/elementsTreeNameFilter.ts | 64 ++++++++++-- .../navigationTreeConnectionGroupFilter.ts | 3 +- .../navigationTreeDuplicateIdFilter.ts | 2 +- .../src/NavigationTree/useElementsTree.ts | 26 ++--- webapp/packages/core-app/src/locales/en.ts | 2 + webapp/packages/core-app/src/locales/it.ts | 2 + webapp/packages/core-app/src/locales/ru.ts | 2 + webapp/packages/core-app/src/locales/zh.ts | 76 +++++++------- .../src/Tree/TreeNode/ITreeNodeState.ts | 1 - .../src/Tree/TreeNode/TreeNode.tsx | 9 -- .../src/Tree/TreeNode/TreeNodeContext.ts | 4 +- .../src/Tree/TreeNode/TreeNodeControl.tsx | 12 ++- .../src/Tree/TreeNode/TreeNodeExpand.tsx | 4 + .../src/Tree/TreeNode/TreeNodeSelect.tsx | 14 +-- 19 files changed, 217 insertions(+), 121 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx b/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx index ec87ac1059..a6a7b74669 100644 --- a/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx +++ b/webapp/packages/core-app/src/NavigationTree/ElementsTree.tsx @@ -10,8 +10,10 @@ import { observer } from 'mobx-react-lite'; import { useMemo, useCallback } from 'react'; import styled, { css } from 'reshadow'; -import { FolderExplorer, FolderExplorerPath, Loader, useFolderExplorer, useMapResource } from '@cloudbeaver/core-blocks'; +import { Filter, FolderExplorer, FolderExplorerPath, Loader, useFolderExplorer, useMapResource } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import { ComponentStyle, composes, useStyles } from '@cloudbeaver/core-theming'; import type { MetadataMap } from '@cloudbeaver/core-utils'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; @@ -19,7 +21,7 @@ import { EObjectFeature } from '../shared/NodesManager/EObjectFeature'; import { NavNodeInfoResource, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource'; import { NavTreeResource } from '../shared/NodesManager/NavTreeResource'; import { ElementsTreeLoader } from './ElementsTreeLoader'; -import { elementsTreeNameFilter } from './elementsTreeNameFilter'; +import { elementsTreeNameFilter, NavNodeFilterCompareFn } from './elementsTreeNameFilter'; import type { NavTreeControlComponent } from './NavigationNodeComponent'; import { NavigationNodeNested } from './NavigationTreeNode/NavigationNode/NavigationNodeNested'; import { NavigationNodeElement } from './NavigationTreeNode/NavigationNodeElement'; @@ -27,16 +29,39 @@ import { NavigationTreeService } from './NavigationTreeService'; import { ITreeContext, TreeContext } from './TreeContext'; import { IElementsTreeCustomRenderer, IElementsTreeFilter, ITreeNodeState, useElementsTree } from './useElementsTree'; -const styles = css` - tree { - position: relative; - box-sizing: border-box; - } +const styles = composes( + css` + filter { + composes: theme-background-surface from global; + } + `, + css` + box { + display: flex; + flex-direction: column; + box-sizing: border-box; + } - FolderExplorerPath { - padding: 0 12px 8px 12px; - } -`; + tree { + position: relative; + box-sizing: border-box; + flex: 1; + } + + FolderExplorerPath { + padding: 0 12px 8px 12px; + } + + filter { + padding: 8px 24px; + flex: 0 0 auto; + display: block; + position: sticky; + top: 0; + z-index: 1; + } + ` +); interface Props { root?: string; @@ -44,11 +69,14 @@ interface Props { disabled?: boolean; selectionTree?: boolean; foldersTree?: boolean; + filter?: boolean; showFolderExplorerPath?: boolean; localState?: MetadataMap; control?: NavTreeControlComponent; emptyPlaceholder?: React.FC; + style?: ComponentStyle; className?: string; + navNodeFilterCompare?: NavNodeFilterCompareFn; filters?: IElementsTreeFilter[]; renderers?: IElementsTreeCustomRenderer[]; customSelect?: (node: NavNode, multiple: boolean, nested: boolean) => void; @@ -58,7 +86,7 @@ interface Props { onClick?: (node: NavNode) => Promise | void; onOpen?: (node: NavNode) => Promise | void; onSelect?: (node: NavNode, state: boolean) => void; - onFilter?: (node: NavNode, value: string) => void; + onFilter?: (value: string) => void; } export const ElementsTree = observer(function ElementsTree({ @@ -70,9 +98,12 @@ export const ElementsTree = observer(function ElementsTree({ selectionTree = false, showFolderExplorerPath = false, foldersTree = false, + filter = false, emptyPlaceholder, + navNodeFilterCompare, filters, renderers, + style, className, isGroup, beforeSelect, @@ -84,10 +115,13 @@ export const ElementsTree = observer(function ElementsTree({ onFilter, }) { const folderExplorer = useFolderExplorer(baseRoot); - const root = folderExplorer.folder; - const fullPath = folderExplorer.fullPath; + const navTreeResource = useService(NavTreeResource); const navNodeInfoResource = useService(NavNodeInfoResource); const navigationTreeService = useService(NavigationTreeService); + const translate = useTranslate(); + + const root = folderExplorer.folder; + const fullPath = folderExplorer.fullPath; const autoOpenFolders = useCallback(async function autoOpenFolders(nodeId: string, path: string[]) { path = [...path]; @@ -118,7 +152,7 @@ export const ElementsTree = observer(function ElementsTree({ }, []); - const children = useMapResource(ElementsTree, NavTreeResource, root, { + const children = useMapResource(ElementsTree, navTreeResource, root, { onLoad: async resource => { const preload = await resource.preloadNodeParents(fullPath); @@ -130,7 +164,11 @@ export const ElementsTree = observer(function ElementsTree({ }, }); - const nameFilter = useMemo(() => elementsTreeNameFilter(navNodeInfoResource), [navNodeInfoResource]); + const nameFilter = useMemo(() => elementsTreeNameFilter( + navTreeResource, + navNodeInfoResource, + navNodeFilterCompare + ), [navTreeResource, navNodeInfoResource, navNodeFilterCompare]); const tree = useElementsTree({ baseRoot, @@ -195,7 +233,7 @@ export const ElementsTree = observer(function ElementsTree({ const loaderAvailable = !context.tree.foldersTree || context.folderExplorer.root === root; - return styled(styles)( + return styled(useStyles(styles, style))( (function ElementsTree({ keepData={keepData} > - - - {tree.showFolderExplorerPath && } - - {loaderAvailable && } - - + + {filter && ( + + context.tree.setFilter(value as string)} + /> + + )} + + + {tree.showFolderExplorerPath && } + + {loaderAvailable && } + + + ); diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx index dd88ca676d..a6594fcf61 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx @@ -30,12 +30,12 @@ const navigationTreeStyles = css` display: flex; flex: 1; flex-direction: column; + overflow: auto; } ElementsTree { padding-top: 16px; min-width: 100%; - min-height: 100%; width: max-content; } diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx index 36b2dacc4c..25700a69c0 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode.tsx @@ -34,8 +34,6 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat handleExpand, handleOpen, handleSelect, - handleFilter, - filterValue, } = useNavigationNode(node, path); const Control = control || NavigationNodeControl; @@ -53,12 +51,10 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat expanded={expanded} externalExpanded={expandedExternal} leaf={leaf} - filterValue={filterValue} onExpand={handleExpand} onClick={handleClick} onOpen={handleOpen} onSelect={handleSelect} - onFilter={handleFilter} > {(expanded || expandedExternal) && } 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 705ae8be49..81f875303f 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.tsx @@ -10,7 +10,8 @@ import { observer } from 'mobx-react-lite'; import { useContext, useMemo } from 'react'; import styled from 'reshadow'; -import { getComputed, TreeNodeNested, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; +import { getComputed, TreeNodeNested, TreeNodeNestedMessage, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; import type { NavTreeNodeComponent } from '../../NavigationNodeComponent'; @@ -31,6 +32,7 @@ export const NavigationNodeNested = observer(function NavigationNodeNeste }) { const styles = useStyles(TREE_NODE_STYLES); const treeContext = useContext(TreeContext); + const translate = useTranslate(); const nextPath = useMemo(() => [...path, nodeId], [path, nodeId]); const children = getComputed( @@ -50,6 +52,7 @@ export const NavigationNodeNested = observer(function NavigationNodeNeste return styled(styles)( {children.map(child => )} + {children.length === 0 && {translate('app_navigationTree_node_empty')}} ); }); diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts index a6e5d5b299..9b9b52770d 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationNode.ts @@ -30,8 +30,6 @@ interface INavigationNode { handleOpen: () => Promise; handleClick: (leaf: boolean) => Promise; handleSelect: (isMultiple?: boolean, nested?: boolean) => Promise; - handleFilter: (value: string) => Promise; - filterValue: string; } export function useNavigationNode(node: NavNode, path: string[]): INavigationNode { @@ -51,7 +49,6 @@ export function useNavigationNode(node: NavNode, path: string[]): INavigationNod const control = getComputed(() => contextRef.context?.control); const disabled = getComputed(() => contextRef.context?.tree.disabled || false); const selected = getComputed(() => contextRef.context?.tree.isNodeSelected(node.id) || false); - const filterValue = getComputed(() => state?.filter || ''); const handleClick = async (leaf: boolean) => await contextRef.context?.onClick?.(node, path, leaf); const handleOpen = async () => await contextRef.context?.onOpen?.(node, path); @@ -60,12 +57,11 @@ export function useNavigationNode(node: NavNode, path: string[]): INavigationNod multiple = false, nested = false ) => await contextRef.context?.tree.select(node, multiple, nested); - const handleFilter = async (value: string) => await contextRef.context?.tree.filter(node, value); useEffect(() => () => { if (!contextRef.context?.selectionTree) { if (contextRef.context?.tree.isNodeSelected(node.id)) { - contextRef.context?.tree.select(node, true, false); + contextRef.context.tree.select(node, true, false); } } }, [node]); @@ -83,8 +79,6 @@ export function useNavigationNode(node: NavNode, path: string[]): INavigationNod handleClick, handleOpen, handleSelect, - handleFilter, - filterValue, }; } diff --git a/webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts b/webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts index bab0b3bde7..de56efecf0 100644 --- a/webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts +++ b/webapp/packages/core-app/src/NavigationTree/elementsTreeNameFilter.ts @@ -11,25 +11,75 @@ import type { MetadataMap } from '@cloudbeaver/core-utils'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; import type { NavNodeInfoResource } from '../shared/NodesManager/NavNodeInfoResource'; +import type { NavTreeResource } from '../shared/NodesManager/NavTreeResource'; import type { IElementsTreeFilter, ITreeNodeState } from './useElementsTree'; +export type NavNodeFilterCompareFn = (node: NavNode, filter: string)=> boolean; + 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 === '') { +export function elementsTreeNameFilter( + navTreeResource: NavTreeResource, + navNodeInfoResource: NavNodeInfoResource, + compare: NavNodeFilterCompareFn = compareNodes +): IElementsTreeFilter { + return (filter: string, node: NavNode, children: string[], state: MetadataMap) => { + if (filter === '' || compare(node, filter)) { return children; } - const nodes = resource + const nodes = navNodeInfoResource .get(resourceKeyList(children)) .filter(isDefined) - .filter(child => child.name?.toLowerCase().includes(nodeState.filter.toLowerCase())); + .filter(child => filterNode( + navTreeResource, + navNodeInfoResource, + compare, + filter, + child, + state + )); return nodes.map(node => node.id); }; } + +function filterNode( + navTreeResource: NavTreeResource, + navNodeInfoResource: NavNodeInfoResource, + compare: NavNodeFilterCompareFn, + filter: string, + node: NavNode, + state: MetadataMap +): boolean { + const nodeState = state.get(node.id); + + + if (compare(node, filter)) { + return true; + } + + if (nodeState.expanded) { + const children = navTreeResource.get(node.id) || []; + + return navNodeInfoResource + .get(resourceKeyList(children)) + .filter(isDefined) + .some(child => filterNode( + navTreeResource, + navNodeInfoResource, + compare, + filter, + child, + state + )); + } + + return false; +} + +function compareNodes(node: NavNode, filter: string): boolean { + return node.name?.toLowerCase().includes(filter.toLowerCase()) ?? false; +} diff --git a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts index 78a6198990..887ff8dd28 100644 --- a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts +++ b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts @@ -12,7 +12,6 @@ import { resourceKeyList } from '@cloudbeaver/core-sdk'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; import { EObjectFeature } from '../shared/NodesManager/EObjectFeature'; import { NavNodeInfoResource, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource'; -import { NodeManagerUtils } from '../shared/NodesManager/NodeManagerUtils'; import type { IElementsTreeFilter } from './useElementsTree'; export const NAVIGATION_TREE_CONNECTION_GROUPS = { @@ -62,7 +61,7 @@ export function navigationTreeConnectionGroupFilter( connectionInfoResource: ConnectionInfoResource, resource: NavNodeInfoResource ): IElementsTreeFilter { - return (node: NavNode, children: string[]) => { + return (filter: string, node: NavNode, children: string[]) => { if (node.id !== ROOT_NODE_PATH) { return children; } diff --git a/webapp/packages/core-app/src/NavigationTree/navigationTreeDuplicateIdFilter.ts b/webapp/packages/core-app/src/NavigationTree/navigationTreeDuplicateIdFilter.ts index c970b4d766..5db9ada14f 100644 --- a/webapp/packages/core-app/src/NavigationTree/navigationTreeDuplicateIdFilter.ts +++ b/webapp/packages/core-app/src/NavigationTree/navigationTreeDuplicateIdFilter.ts @@ -15,7 +15,7 @@ import type { IElementsTreeFilter } from './useElementsTree'; export function navigationTreeDuplicateFilter( navNodeViewService: NavNodeViewService, ): IElementsTreeFilter { - return (node: NavNode, children: string[]) => { + return (filter: string, node: NavNode, children: string[]) => { const { nodes, duplicates } = navNodeViewService.filterDuplicates(children); untracked(() => { diff --git a/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts b/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts index 30625243ff..92a0033ea4 100644 --- a/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts +++ b/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts @@ -24,13 +24,13 @@ import { NavigationTreeService } from './NavigationTreeService'; export type IElementsTreeCustomRenderer = (nodeId: string) => NavigationNodeRendererComponent | undefined; export type IElementsTreeFilter = ( + filter: string, node: NavNode, children: string[], state: MetadataMap ) => string[]; export interface ITreeNodeState { - filter: string; selected: boolean; expanded: boolean; } @@ -55,12 +55,13 @@ interface IOptions { isGroup?: (node: NavNode) => boolean; onExpand?: (node: NavNode, state: boolean) => Promise | void; onSelect?: (node: NavNode, state: boolean) => Promise | void; - onFilter?: (node: NavNode, value: string) => Promise | void; + onFilter?: (value: string) => Promise | void; } export interface IElementsTree { baseRoot: string; root: string; + filter: string; loading: boolean; disabled: boolean; foldersTree: boolean; @@ -71,7 +72,7 @@ export interface IElementsTree { isNodeSelected: (nodeId: string) => boolean; getNodeChildren: (nodeId: string) => string[]; isGroup?: (node: NavNode) => boolean; - filter: (node: NavNode, value: string) => Promise; + setFilter: (value: string) => Promise; select: (node: NavNode, multiple: boolean, nested: boolean) => Promise; expand: (node: NavNode, state: boolean) => Promise; } @@ -83,7 +84,6 @@ export function useElementsTree(options: IOptions): IElementsTree { const connectionInfoResource = useService(ConnectionInfoResource); const [localTreeNodesState] = useState(() => new MetadataMap(() => ({ - filter: '', selected: false, expanded: false, }))); @@ -170,7 +170,7 @@ export function useElementsTree(options: IOptions): IElementsTree { return (options.filters || []) .reduce( - (children, filter) => filter(node, children, state), + (children, filter) => filter(elementsTree.filter, node, children, state), navTreeService.getChildren(node.id) || [] ); } @@ -247,6 +247,7 @@ export function useElementsTree(options: IOptions): IElementsTree { const elementsTree = useObservableRef(() => ({ state, + filter: '', loading: options.keepData || false, getNodeState(nodeId: string) { return this.state.get(nodeId); @@ -258,19 +259,19 @@ export function useElementsTree(options: IOptions): IElementsTree { const children = getNodeChildren(nodeId); if (children.length > 0) { - return children.every(child => elementsTree.getNodeState(child).selected); - } else { - return false; + return children.every(child => this.isNodeSelected(child)); } + + return false; } + return this.getNodeState(nodeId).selected; }, getNodeChildren, - async filter(node: NavNode, value: string) { - const treeNodeState = this.state.get(node.id); - treeNodeState.filter = value; + async setFilter(value: string) { + this.filter = value; - await options.onFilter?.(node, value); + await options.onFilter?.(value); }, async expand(node: NavNode, state: boolean) { const treeNodeState = this.state.get(node.id); @@ -318,6 +319,7 @@ export function useElementsTree(options: IOptions): IElementsTree { isGroup: observable.ref, disabled: observable.ref, root: observable.ref, + filter: observable.ref, loading: observable.ref, renderers: observable.ref, baseRoot: observable.ref, diff --git a/webapp/packages/core-app/src/locales/en.ts b/webapp/packages/core-app/src/locales/en.ts index 988fd8838d..0e2451dc0f 100644 --- a/webapp/packages/core-app/src/locales/en.ts +++ b/webapp/packages/core-app/src/locales/en.ts @@ -11,6 +11,8 @@ export default [ ['app_shared_settingsMenu_lang', 'Language'], ['app_topnavbar_connection_schema_manager_not_selected', '-- not selected --'], ['app_navigationTree_openNodeTab', 'Open'], + ['app_navigationTree_search', 'Search elements...'], + ['app_navigationTree_node_empty', 'Empty'], ['app_navigationTree_connection_view', 'Connection view'], ['app_navigationTree_connection_view_option_simple', 'Simple'], ['app_navigationTree_connection_view_option_advanced', 'Advanced'], diff --git a/webapp/packages/core-app/src/locales/it.ts b/webapp/packages/core-app/src/locales/it.ts index ae4835d1bc..b8b324bad9 100644 --- a/webapp/packages/core-app/src/locales/it.ts +++ b/webapp/packages/core-app/src/locales/it.ts @@ -11,6 +11,8 @@ export default [ ['app_shared_settingsMenu_lang', 'Lingua'], ['app_topnavbar_connection_schema_manager_not_selected', '-- non selezionato --'], ['app_navigationTree_openNodeTab', 'Apri'], + ['app_navigationTree_search', 'Search elements...'], + ['app_navigationTree_node_empty', 'Empty'], ['app_navigationTree_connection_view', 'Vista di connessione'], ['app_navigationTree_connection_view_option_simple', 'Semplice'], ['app_navigationTree_connection_view_option_advanced', 'Avanzato'], diff --git a/webapp/packages/core-app/src/locales/ru.ts b/webapp/packages/core-app/src/locales/ru.ts index 41ca651cdc..918b981ecd 100644 --- a/webapp/packages/core-app/src/locales/ru.ts +++ b/webapp/packages/core-app/src/locales/ru.ts @@ -14,6 +14,8 @@ export default [ 'Нет открытых объектов для отображения. Дважды нажмите на элемент в дереве навигации, чтобы открыть его.', ], ['app_navigationTree_openNodeTab', 'Открыть'], + ['app_navigationTree_search', 'Искать элементы...'], + ['app_navigationTree_node_empty', 'Пусто'], ['app_navigationTree_connection_view', 'Отображение'], ['app_navigationTree_connection_view_option_simple', 'Упрощенное'], ['app_navigationTree_connection_view_option_advanced', 'Продвинутое'], diff --git a/webapp/packages/core-app/src/locales/zh.ts b/webapp/packages/core-app/src/locales/zh.ts index 75100e1dd5..a56b58dd4d 100644 --- a/webapp/packages/core-app/src/locales/zh.ts +++ b/webapp/packages/core-app/src/locales/zh.ts @@ -1,38 +1,40 @@ export default [ - ['app_shared_connectionMenu_newConnection', '新连接'], - ['app_shared_connectionMenu_connection', '连接'], - ['app_shared_connectionMenu_custom', '手动'], - ['app_shared_connectionMenu_disconnect', '断开所有连接'], - ['app_shared_toolsMenu_tools', '工具'], - ['app_shared_toolsMenu_logViewer', '日志查看器'], - ['app_shared_toolsMenu_logViewer_placeholder', '日志查看器未激活'], - ['app_shared_settingsMenu_config', '配置'], - ['app_shared_settingsMenu_theme', '主题'], - ['app_shared_settingsMenu_lang', '语言'], - ['app_topnavbar_connection_schema_manager_not_selected', '-- 为选择 --'], - ['app_navigationTree_openNodeTab', '打开'], - ['app_navigationTree_connection_view', '连接视图'], - ['app_navigationTree_connection_view_option_simple', '简单'], - ['app_navigationTree_connection_view_option_advanced', '完善'], - ['app_navigationTree_connection_view_option_custom', '自定义'], - ['app_navigationTree_connection_view_option_showSystemObjects', '展示系统对象'], - ['app_navigationTree_refreshNode', '刷新'], - ['app_navigationTree_node_change_name_error', '更改名称时发生错误'], - ['app_navigationTree_context_disconnect', '断开连接'], - ['app_navigationTree_connection_group_user', '用户连接'], - ['app_navigationTree_connection_group_shared', '共享连接'], - ['app_shared_inlineEditor_dialog_title', '编辑模式'], - ['app_shared_inlineEditor_dialog_apply', '应用'], - ['app_shared_inlineEditor_dialog_cancel', '取消'], - [ - 'app_shared_navigationTabsBar_placeholder', - '没有要显示的对象。双击导航树中的对象将其打开。', - ], - ['app_shared_sql_generators_panel_title', '生成SQL'], - ['app_shared_sql_generators_dialog_title', '生成的SQL'], - ['app_log_view_clear_log', '清除日志'], - ['app_log_view_entry_type', '类型'], - ['app_log_view_entry_timestamp', '时间戳'], - ['app_log_view_entry_message', '消息'], - ['app_log_view_entry_details', '详情'], - ]; + ['app_shared_connectionMenu_newConnection', '新连接'], + ['app_shared_connectionMenu_connection', '连接'], + ['app_shared_connectionMenu_custom', '手动'], + ['app_shared_connectionMenu_disconnect', '断开所有连接'], + ['app_shared_toolsMenu_tools', '工具'], + ['app_shared_toolsMenu_logViewer', '日志查看器'], + ['app_shared_toolsMenu_logViewer_placeholder', '日志查看器未激活'], + ['app_shared_settingsMenu_config', '配置'], + ['app_shared_settingsMenu_theme', '主题'], + ['app_shared_settingsMenu_lang', '语言'], + ['app_topnavbar_connection_schema_manager_not_selected', '-- 为选择 --'], + ['app_navigationTree_search', 'Search elements...'], + ['app_navigationTree_node_empty', 'Empty'], + ['app_navigationTree_openNodeTab', '打开'], + ['app_navigationTree_connection_view', '连接视图'], + ['app_navigationTree_connection_view_option_simple', '简单'], + ['app_navigationTree_connection_view_option_advanced', '完善'], + ['app_navigationTree_connection_view_option_custom', '自定义'], + ['app_navigationTree_connection_view_option_showSystemObjects', '展示系统对象'], + ['app_navigationTree_refreshNode', '刷新'], + ['app_navigationTree_node_change_name_error', '更改名称时发生错误'], + ['app_navigationTree_context_disconnect', '断开连接'], + ['app_navigationTree_connection_group_user', '用户连接'], + ['app_navigationTree_connection_group_shared', '共享连接'], + ['app_shared_inlineEditor_dialog_title', '编辑模式'], + ['app_shared_inlineEditor_dialog_apply', '应用'], + ['app_shared_inlineEditor_dialog_cancel', '取消'], + [ + 'app_shared_navigationTabsBar_placeholder', + '没有要显示的对象。双击导航树中的对象将其打开。', + ], + ['app_shared_sql_generators_panel_title', '生成SQL'], + ['app_shared_sql_generators_dialog_title', '生成的SQL'], + ['app_log_view_clear_log', '清除日志'], + ['app_log_view_entry_type', '类型'], + ['app_log_view_entry_timestamp', '时间戳'], + ['app_log_view_entry_message', '消息'], + ['app_log_view_entry_details', '详情'], +]; diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/ITreeNodeState.ts b/webapp/packages/core-blocks/src/Tree/TreeNode/ITreeNodeState.ts index 1d565ef439..0610f16883 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/ITreeNodeState.ts +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/ITreeNodeState.ts @@ -14,5 +14,4 @@ export interface ITreeNodeState { externalExpanded?: boolean; expanded?: boolean; leaf?: boolean; - filterValue?: string; } diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx index 9ba0e1b87e..090472f796 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNode.tsx @@ -23,7 +23,6 @@ interface Props extends ITreeNodeState { onClick?: (leaf: boolean) => Promise | void; onExpand?: () => Promise | void; onSelect?: (multiple?: boolean, nested?: boolean) => Promise | void; - onFilter?: (value: string) => Promise | void; onOpen?: () => Promise | void; } @@ -36,7 +35,6 @@ export const TreeNode: React.FC = observer(function TreeNode({ loading = false, selected = false, disabled = false, - filterValue = '', expanded = false, externalExpanded, leaf = false, @@ -76,11 +74,6 @@ export const TreeNode: React.FC = observer(function TreeNode({ await handlersRef.onSelect?.(multiple, nested); }); }, - async filter(value: string) { - await processAction(async () => { - await handlersRef.onFilter?.(value); - }); - }, async open() { await processAction(async () => { await handlersRef.onOpen?.(); @@ -96,7 +89,6 @@ export const TreeNode: React.FC = observer(function TreeNode({ expanded: observable.ref, externalExpanded: observable.ref, leaf: observable.ref, - filterValue: observable.ref, }, { group, disabled, @@ -105,7 +97,6 @@ export const TreeNode: React.FC = observer(function TreeNode({ expanded, externalExpanded: externalExpanded || false, leaf, - filterValue, }); return styled(useStyles(TREE_NODE_STYLES))( diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts index a35f63d380..b72fc7a917 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts @@ -18,11 +18,9 @@ export interface ITreeNodeContext { leaf: boolean; group: boolean; readonly select: (multiple?: boolean, nested?: boolean) => Promise; - readonly filter: (value: string) => Promise; - filterValue: string; readonly click?: () => Promise; readonly expand: () => Promise; readonly open: () => Promise; } -export const TreeNodeContext = createContext(undefined as any); +export const TreeNodeContext = createContext(undefined as any); // TODO: remove cast to any diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx index 27bd8e91ed..2f37cb75a1 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx @@ -21,17 +21,18 @@ const KEY = { }; interface Props extends ITreeNodeState { + title?: string; onClick?: (event: React.MouseEvent) => void; className?: string; big?: boolean; } export const TreeNodeControl = observer(function TreeNodeControl({ + title, group, disabled, loading, selected, - filterValue, expanded, externalExpanded, leaf, @@ -41,6 +42,10 @@ export const TreeNodeControl = observer(function TreeNodeControl({ }) { const context = useContext(TreeNodeContext); + if (!context) { + throw new Error('Context not provided'); + } + if (group !== undefined) { context.group = group; } @@ -57,10 +62,6 @@ export const TreeNodeControl = observer(function TreeNodeControl({ context.selected = selected; } - if (filterValue !== undefined) { - context.filterValue = filterValue; - } - if (expanded !== undefined) { context.expanded = expanded; } @@ -109,6 +110,7 @@ export const TreeNodeControl = observer(function TreeNodeControl({ return (
(function TreeNodeExpand({ }) { const context = useContext(TreeNodeContext); + if (!context) { + throw new Error('Context not provided'); + } + if (context.externalExpanded || context.disabled) { disabled = true; } diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx index fdc2bee20f..4d6d7a6137 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeSelect.tsx @@ -58,7 +58,7 @@ export const TreeNodeSelect = observer(function TreeNodeSelect({ async function handleSelect() { await onSelect?.(); - await context.select(true, group); + await context!.select(true, group); } const handleClick = (event: React.MouseEvent) => { @@ -74,12 +74,12 @@ export const TreeNodeSelect = observer(function TreeNodeSelect({ {loadIndicator && context.loading ? : ( - - )} + + )}
); });