From 3722ac1feaa0e28dddad7ac9d46d287c13800fe2 Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Thu, 24 Nov 2022 09:23:16 -0800 Subject: [PATCH] Permanent search bar Summary: Out of the box the library search has some issues. when search matches it steals focus from the input. Eventually we want to customise the rendering of the tree items anyway so this lays the foundation for taht Reviewed By: antonk52 Differential Revision: D41336524 fbshipit-source-id: 194f67023edd0675cd9bd8d6134260439c6b2785 --- .../flipper-plugin/src/__tests__/api.node.tsx | 3 + desktop/flipper-plugin/src/index.tsx | 7 +- .../public/ui-debugger/components/Tree.tsx | 219 ++++++++++++------ .../public/ui-debugger/components/main.tsx | 22 +- desktop/plugins/public/ui-debugger/index.tsx | 2 + docs/extending/flipper-plugin.mdx | 23 ++ 6 files changed, 202 insertions(+), 74 deletions(-) diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index 0af03ab3d..cd41a058e 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -39,6 +39,8 @@ test('Correct top level API exposed', () => { "Dialog", "ElementsInspector", "FileSelector", + "HighlightContext", + "HighlightProvider", "Layout", "MarkerTimeline", "MasterDetail", @@ -67,6 +69,7 @@ test('Correct top level API exposed', () => { "textContent", "theme", "timeout", + "useHighlighter", "useLocalStorageState", "useLogger", "useMemoize", diff --git a/desktop/flipper-plugin/src/index.tsx b/desktop/flipper-plugin/src/index.tsx index 0c71c42e7..9fdb4e898 100644 --- a/desktop/flipper-plugin/src/index.tsx +++ b/desktop/flipper-plugin/src/index.tsx @@ -70,7 +70,12 @@ export {Tabs, Tab} from './ui/Tabs'; export {useLocalStorageState} from './utils/useLocalStorageState'; export {FileSelector} from './ui/FileSelector'; -export {HighlightManager} from './ui/Highlight'; +export { + HighlightManager, + HighlightContext, + HighlightProvider, + useHighlighter, +} from './ui/Highlight'; export { DataValueExtractor, DataInspectorExpanded, diff --git a/desktop/plugins/public/ui-debugger/components/Tree.tsx b/desktop/plugins/public/ui-debugger/components/Tree.tsx index 4b3102c9c..95604fe9c 100644 --- a/desktop/plugins/public/ui-debugger/components/Tree.tsx +++ b/desktop/plugins/public/ui-debugger/components/Tree.tsx @@ -13,14 +13,23 @@ import { Tree as ComplexTree, ControlledTreeEnvironment, TreeItem, + TreeInformation, + TreeItemRenderContext, + InteractionMode, + TreeEnvironmentRef, } from 'react-complex-tree'; import {plugin} from '../index'; -import {usePlugin, useValue} from 'flipper-plugin'; import { - InteractionMode, - TreeEnvironmentRef, -} from 'react-complex-tree/lib/esm/types'; + usePlugin, + useValue, + HighlightManager, + HighlightProvider, + HighlightContext, + useHighlighter, + theme, +} from 'flipper-plugin'; + import {head} from 'lodash'; export function Tree(props: { @@ -32,77 +41,155 @@ export function Tree(props: { const instance = usePlugin(plugin); const expandedItems = useValue(instance.treeState).expandedNodes; const items = useMemo(() => toComplexTree(props.nodes), [props.nodes]); - const hoveredNodes = useValue(instance.hoveredNodes); - const treeRef = useRef(); + const treeEnvRef = useRef(); + + const searchTerm = useValue(instance.searchTerm); useEffect(() => { //this makes the keyboard arrow controls work always, even when using the visualiser - treeRef.current?.focusTree('tree', true); - }, [hoveredNodes, props.selectedNode]); - return ( - item.data.name} - canRename={false} - canDragAndDrop={false} - canSearch - autoFocus - viewState={{ - tree: { - focusedItem: head(hoveredNodes), - expandedItems, - selectedItems: props.selectedNode ? [props.selectedNode] : [], - }, - }} - onFocusItem={(item) => { - instance.hoveredNodes.set([item.index]); - }} - onExpandItem={(item) => { - instance.treeState.update((draft) => { - draft.expandedNodes.push(item.index); - }); - }} - onCollapseItem={(item) => - instance.treeState.update((draft) => { - draft.expandedNodes = draft.expandedNodes.filter( - (expandedItemIndex) => expandedItemIndex !== item.index, - ); - }) - } - onSelectItems={(items) => props.onSelectNode(items[0])} - defaultInteractionMode={{ - mode: 'custom', - extends: InteractionMode.DoubleClickItemToExpand, - createInteractiveElementProps: ( - item, - treeId, - actions, - renderFlags, - ) => ({ - onClick: () => { - if (renderFlags.isSelected) { - actions.unselectItem(); - } else { - actions.selectItem(); - } - }, + treeEnvRef.current?.focusTree('tree', true); + }, [props.selectedNode]); - onMouseOver: () => { - instance.hoveredNodes.set([item.index]); + return ( + + item.data.name} + canRename={false} + canDragAndDrop={false} + viewState={{ + tree: { + focusedItem: head(hoveredNodes), + expandedItems, + selectedItems: props.selectedNode ? [props.selectedNode] : [], }, - }), - }}> - - + }} + onFocusItem={(item) => { + instance.hoveredNodes.set([item.index]); + }} + onExpandItem={(item) => { + instance.treeState.update((draft) => { + draft.expandedNodes.push(item.index); + }); + }} + onCollapseItem={(item) => + instance.treeState.update((draft) => { + draft.expandedNodes = draft.expandedNodes.filter( + (expandedItemIndex) => expandedItemIndex !== item.index, + ); + }) + } + renderItem={renderItem} + onSelectItems={(items) => props.onSelectNode(items[0])} + defaultInteractionMode={{ + mode: 'custom', + extends: InteractionMode.DoubleClickItemToExpand, + createInteractiveElementProps: ( + item, + treeId, + actions, + renderFlags, + ) => ({ + onClick: () => { + if (renderFlags.isSelected) { + actions.unselectItem(); + } else { + actions.selectItem(); + } + }, + + onMouseOver: () => { + instance.hoveredNodes.set([item.index]); + }, + }), + }}> + + + ); } +//copied from https://github.com/lukasbach/react-complex-tree/blob/e3dcc435933284376a0fc6e3cc651e67ead678b5/packages/core/src/renderers/createDefaultRenderers.tsx +const cx = (...classNames: Array) => + classNames.filter((cn) => !!cn).join(' '); +const renderDepthOffset = 5; + +function renderItem({ + item, + depth, + children, + arrow, + context, +}: { + item: TreeItem; + depth: number; + children: React.ReactNode | null; + title: React.ReactNode; + arrow: React.ReactNode; + context: TreeItemRenderContext; + info: TreeInformation; +}) { + return ( +
  • +
    + {arrow} +
    + +
    +
    + {children} +
  • + ); +} + +function HighlightedText(props: {text: string}) { + const highlightManager: HighlightManager = useHighlighter(); + return {highlightManager.render(props.text)}; +} + function toComplexTree(nodes: Map): Record> { const res: Record> = {}; for (const node of nodes.values()) { diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx index 0c4b45cbe..367aea63a 100644 --- a/desktop/plugins/public/ui-debugger/components/main.tsx +++ b/desktop/plugins/public/ui-debugger/components/main.tsx @@ -17,6 +17,7 @@ import {Tree} from './Tree'; import {Visualization2D} from './Visualization2D'; import {useKeyboardModifiers} from '../hooks/useKeyboardModifiers'; import {Inspector} from './sidebar/Inspector'; +import {Input} from 'antd'; export function Component() { const instance = usePlugin(plugin); @@ -30,6 +31,7 @@ export function Component() { useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show)); + const searchTerm = useValue(instance.searchTerm); const {ctrlPressed} = useKeyboardModifiers(); function renderSidebar( @@ -51,14 +53,20 @@ export function Component() { if (rootId) { return ( - - + instance.searchTerm.set(e.target.value)} /> - + + + + ) { const rootId = createState(undefined); const metadata = createState>(new Map()); + const searchTerm = createState(''); client.onMessage('init', (event) => { rootId.set(event.rootId); @@ -108,6 +109,7 @@ export function plugin(client: PluginClient) { hoveredNodes, perfEvents, treeState, + searchTerm, }; } diff --git a/docs/extending/flipper-plugin.mdx b/docs/extending/flipper-plugin.mdx index 614db4eef..81e564e37 100644 --- a/docs/extending/flipper-plugin.mdx +++ b/docs/extending/flipper-plugin.mdx @@ -889,6 +889,29 @@ const [showWhitespace, setShowWhitespace] = useLocalStorageState( Layout elements can be used to organize the screen layout. See the [Style Guide](style-guide.mdx) for more details. +### HighlightContext + +### HighlightProvider + +React context provider for Highlight context. All wrapped componets can access context or use the useHighligher helper. Example +```typescript jsx + + + +```` + +### useHighlighter + +Hook to be used inside a Highlight context to render text with highlighting applied. Example +```typescript jsx +function HighlightedText(props: {text: string}) { + const highlightManager: HighlightManager = useHighlighter(); + return {highlightManager.render(props.text)}; +} +``` + ### DataTable ### DataFormatter