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