diff --git a/desktop/flipper-server-core/src/server/startServer.tsx b/desktop/flipper-server-core/src/server/startServer.tsx index 16c4d79c0..5016e5fbf 100644 --- a/desktop/flipper-server-core/src/server/startServer.tsx +++ b/desktop/flipper-server-core/src/server/startServer.tsx @@ -178,7 +178,9 @@ async function startHTTPServer( }); return new Promise((resolve) => { - console.log(`Starting server on http://localhost:${config.port}`); + console.info( + `[flipper-server] Starting server on http://localhost:${config.port}`, + ); const readyForIncomingConnections = ( serverImpl: FlipperServerImpl, companionEnv: FlipperServerCompanionEnv, diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx index d88c4f487..fc3ab0f22 100644 --- a/desktop/plugins/public/ui-debugger/components/main.tsx +++ b/desktop/plugins/public/ui-debugger/components/main.tsx @@ -134,6 +134,7 @@ export function Component() { bottomPanel != null ? bottomPanelHeight : 0 } nodes={nodes} + metadata={metadata} rootId={rootId} /> diff --git a/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx b/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx index 657dce2cc..b410ca136 100644 --- a/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx +++ b/desktop/plugins/public/ui-debugger/components/tree/ContextMenu.tsx @@ -7,19 +7,25 @@ * @format */ -import {FrameworkEvent, Id, ClientNode} from '../../ClientTypes'; +import { + FrameworkEvent, + Id, + ClientNode, + MetadataId, + Metadata, +} from '../../ClientTypes'; import {OnSelectNode, ViewMode} from '../../DesktopTypes'; -import React, {useEffect, useState} from 'react'; +import React, {useState} from 'react'; import {DataSource, getFlipperLib} from 'flipper-plugin'; -import {Dropdown, MenuProps} from 'antd'; +import {Dropdown, MenuProps, message} from 'antd'; import {tracker} from '../../utils/tracker'; import { bigGrepContextMenuItems, ideContextMenuItems, } from '../fb-stubs/IDEContextMenu'; import { - CopyFilled, CopyOutlined, + ExportOutlined, FullscreenExitOutlined, FullscreenOutlined, MenuFoldOutlined, @@ -29,12 +35,14 @@ import { TableOutlined, } from '@ant-design/icons'; import {filterOutFalsy} from '../../utils/array'; +import {exportNode} from '../../utils/dataTransform'; type MenuItems = MenuProps['items']; export const ContextMenu: React.FC<{ frameworkEvents: DataSource; nodes: Map; + metadata: Map; hoveredNodeId?: Id; focusedNodeId?: Id; onFocusNode: (id?: Id) => void; @@ -57,6 +65,7 @@ export const ContextMenu: React.FC<{ onCollapseRecursively, onCollapseNonAncestors, onSelectNode, + metadata, }) => { const [_, setIdeItemsRerender] = useState(0); const hoveredNode = nodes.get(hoveredNodeId ?? Number.MAX_SAFE_INTEGER); @@ -166,6 +175,30 @@ export const ContextMenu: React.FC<{ }, }), ), + {type: 'divider'}, + { + key: 'export-node', + label: 'Export', + icon: , + onClick: () => { + getFlipperLib().writeTextToClipboard( + exportNode(hoveredNode, metadata, nodes), + ); + message.success('Exported'); + }, + }, + { + key: 'export-node-recursive', + label: 'Export with children', + icon: , + onClick: () => { + getFlipperLib().writeTextToClipboard( + exportNode(hoveredNode, metadata, nodes, true), + ); + message.success('Exported'); + }, + }, + {type: 'divider'}, ...(bigGrepContextMenuItems(hoveredNode) || []), ...(ideContextMenuItems(hoveredNode, () => setIdeItemsRerender((value) => value + 1), diff --git a/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx b/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx index 1069fa7b6..52d9b42b9 100644 --- a/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx +++ b/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx @@ -7,7 +7,7 @@ * @format */ -import {Id, ClientNode} from '../../ClientTypes'; +import {Id, ClientNode, MetadataId, Metadata} from '../../ClientTypes'; import {Color, OnSelectNode} from '../../DesktopTypes'; import React, { CSSProperties, @@ -55,11 +55,13 @@ export type TreeNode = ClientNode & { }; export function Tree2({ nodes, + metadata, rootId, additionalHeightOffset, }: { additionalHeightOffset: number; nodes: Map; + metadata: Map; rootId: Id; }) { const instance = usePlugin(plugin); @@ -216,6 +218,7 @@ export function Tree2({ text={searchTerm} highlightColor={theme.searchHighlightBackground.yellow}> , + nodes: Map, + recursive: boolean = false, +): any { + const rawExport: any = ( + node: ClientNode, + metadata: Map, + nodes: Map, + recursive: boolean = false, + ) => { + return { + ...node, + attributes: transform(node.attributes, metadata), + children: recursive + ? node.children.map((child) => { + const childNode = nodes.get(child); + if (childNode == null) { + throw new Error(`Node ${child} not found`); + } + + return rawExport(childNode, metadata, nodes, recursive); + }) + : [], + }; + }; + + return JSON.stringify(rawExport(node, metadata, nodes, recursive), null, 2); +}