diff --git a/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx b/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx
index 819281804..181a1cf7e 100644
--- a/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx
+++ b/desktop/plugins/public/ui-debugger/components/tree/Tree.tsx
@@ -20,6 +20,7 @@ import React, {
import {
HighlightManager,
HighlightProvider,
+ Layout,
styled,
theme,
useHighlighter,
@@ -27,30 +28,28 @@ import {
useValue,
} from 'flipper-plugin';
import {plugin} from '../../index';
-import {Glyph} from 'flipper';
-import {head} from 'lodash';
+import {head, last} from 'lodash';
import {Badge, Typography} from 'antd';
import {useVirtualizer} from '@tanstack/react-virtual';
import {ContextMenu} from './ContextMenu';
import {MillisSinceEpoch, useKeyboardControls} from './useKeyboardControls';
import {toTreeList} from './toTreeList';
+import {CaretDownOutlined} from '@ant-design/icons';
const {Text} = Typography;
-type LineStyle = 'ToParent' | 'ToChildren';
-
type NodeIndentGuide = {
depth: number;
- style: LineStyle;
addHorizontalMarker: boolean;
trimBottom: boolean;
+ color: 'primary' | 'secondary';
};
export type TreeNode = ClientNode & {
depth: number;
idx: number;
isExpanded: boolean;
- indentGuide: NodeIndentGuide | null;
+ indentGuides: NodeIndentGuide[];
frameworkEvents: number | null;
};
export function Tree2({
@@ -111,7 +110,7 @@ export function Tree2({
const rowVirtualizer = useVirtualizer({
count: treeNodes.length,
getScrollElement: () => parentRef.current,
- estimateSize: () => 26,
+ estimateSize: () => TreeItemHeightNumber,
overscan: 20,
});
@@ -261,31 +260,82 @@ export function Tree2({
);
}
-function IndentGuide({indentGuide}: {indentGuide: NodeIndentGuide}) {
- const verticalLinePadding = `${renderDepthOffset * indentGuide.depth + 8}px`;
+const secondaryColor = theme.buttonDefaultBackground;
+const GuideOffset = 11;
- return (
-
-
- {indentGuide.addHorizontalMarker && (
-
- )}
-
- );
-}
+const IndentGuides = React.memo(
+ ({
+ isSelected,
+ indentGuides,
+ hasExpandChildrenIcon,
+ }: {
+ isSelected: boolean;
+ hasExpandChildrenIcon: boolean;
+ indentGuides: NodeIndentGuide[];
+ }) => {
+ const lastGuide = last(indentGuides);
+
+ const lastGuidePadding = `${
+ renderDepthOffset * (lastGuide?.depth ?? 0) + GuideOffset
+ }px`;
+
+ return (
+
+ {indentGuides.map((guide, idx) => {
+ const indentGuideLinePadding = `${
+ renderDepthOffset * guide.depth + GuideOffset
+ }px`;
+
+ const isLastGuide = idx === indentGuides.length - 1;
+ const drawHalfprimary = isSelected && isLastGuide;
+
+ const firstHalf =
+ guide.color === 'primary' ? theme.primaryColor : secondaryColor;
+
+ const secondHalf = guide.trimBottom
+ ? 'transparent'
+ : guide.color === 'primary' && !drawHalfprimary
+ ? theme.primaryColor
+ : secondaryColor;
+
+ return (
+
+ );
+ })}
+ {lastGuide?.addHorizontalMarker && (
+
+ )}
+
+ );
+ },
+ (props, nextProps) =>
+ props.hasExpandChildrenIcon === nextProps.hasExpandChildrenIcon &&
+ props.indentGuides === nextProps.indentGuides &&
+ props.isSelected === nextProps.isSelected,
+);
function TreeNodeRow({
transform,
@@ -314,6 +364,8 @@ function TreeNodeRow({
onCollapseNode: (node: Id) => void;
onHoverNode: (node: Id) => void;
}) {
+ const showExpandChildrenIcon = treeNode.children.length > 0;
+ const isSelected = treeNode.id === selectedNode;
return (
- {treeNode.indentGuide != null && (
-
- )}
+
+
{
const kbIsNoLongerReservingScroll =
@@ -347,7 +402,7 @@ function TreeNodeRow({
style={{overflow: 'visible'}}>
0}
+ showIcon={showExpandChildrenIcon}
onClick={() => {
if (treeNode.isExpanded) {
onCollapseNode(treeNode.id);
@@ -356,8 +411,8 @@ function TreeNodeRow({
}
}}
/>
- {nodeIcon(treeNode)}
+ {nodeIcon(treeNode)}
{treeNode.frameworkEvents && (
+
- >
+
);
}
@@ -404,7 +463,8 @@ function InlineAttributes({attributes}: {attributes: Record}) {
);
}
-const TreeItemHeight = '26px';
+const TreeItemHeightNumber = 28;
+const TreeItemHeight = `${TreeItemHeightNumber}px`;
const HalfTreeItemHeight = `calc(${TreeItemHeight} / 2)`;
const TreeNodeContent = styled.li<{
@@ -414,12 +474,12 @@ const TreeNodeContent = styled.li<{
isHighlighted: boolean;
}>(({item, isHovered, isSelected, isHighlighted}) => ({
display: 'flex',
- alignItems: 'baseline',
+ alignItems: 'center',
height: TreeItemHeight,
paddingLeft: `${item.depth * renderDepthOffset}px`,
borderWidth: '1px',
borderRadius: '3px',
- borderColor: isHovered ? theme.selectionBackgroundColor : 'transparent',
+ borderColor: 'transparent',
borderStyle: 'solid',
overflow: 'hidden',
whiteSpace: 'nowrap',
@@ -427,6 +487,8 @@ const TreeNodeContent = styled.li<{
? 'rgba(255,0,0,.3)'
: isSelected
? theme.selectionBackgroundColor
+ : isHovered
+ ? theme.backgroundWash
: theme.backgroundDefault,
}));
@@ -440,57 +502,82 @@ function ExpandedIconOrSpace(props: {
{
e.stopPropagation();
props.onClick();
}}>
-
);
} else {
- return ;
+ return (
+
+ );
}
}
function HighlightedText(props: {text: string}) {
const highlightManager: HighlightManager = useHighlighter();
- return {highlightManager.render(props.text)} ;
+ return (
+ {highlightManager.render(props.text)}
+ );
}
-function nodeIcon(node: ClientNode) {
+function nodeIcon(node: TreeNode) {
if (node.tags.includes('LithoMountable')) {
- return ;
+ return ;
} else if (node.tags.includes('Litho')) {
- return ;
+ return ;
} else if (node.tags.includes('CK')) {
if (node.tags.includes('iOS')) {
- return ;
+ return ;
}
- return ;
+ return ;
} else if (node.tags.includes('BloksBoundTree')) {
- return ;
+ return ;
} else if (node.tags.includes('BloksDerived')) {
- return ;
+ return ;
+ } else {
+ return (
+
+ );
}
}
-const DecorationImage = styled.img({
- height: 12,
- marginRight: 5,
- width: 12,
+const NodeIconSize = 14;
+const IconRightMargin = '4px';
+const NodeIconImage = styled.img({
+ height: NodeIconSize,
+ width: NodeIconSize,
+ marginRight: IconRightMargin,
+ userSelect: 'none',
});
-const renderDepthOffset = 12;
+const renderDepthOffset = 14;
//due to virtualisation the out of the box dom based scrolling doesnt work
function findSearchMatchingIndexes(
diff --git a/desktop/plugins/public/ui-debugger/components/tree/toTreeList.tsx b/desktop/plugins/public/ui-debugger/components/tree/toTreeList.tsx
index 804567ab2..f6fb1faa5 100644
--- a/desktop/plugins/public/ui-debugger/components/tree/toTreeList.tsx
+++ b/desktop/plugins/public/ui-debugger/components/tree/toTreeList.tsx
@@ -13,13 +13,14 @@ import {
ClientNode,
} from '../../ClientTypes';
import {DataSource} from 'flipper-plugin';
-import {last} from 'lodash';
+import {concat, last} from 'lodash';
import {reverse} from 'lodash/fp';
import {TreeNode} from './Tree';
type TreeListStackItem = {
node: ClientNode;
depth: number;
+ parentIndentGuideDepths: number[];
isChildOfSelectedNode: boolean;
selectedNodeDepth: number;
};
@@ -37,7 +38,13 @@ export function toTreeList(
return [];
}
const stack = [
- {node: root, depth: 0, isChildOfSelectedNode: false, selectedNodeDepth: 0},
+ {
+ node: root,
+ depth: 0,
+ isChildOfSelectedNode: false,
+ selectedNodeDepth: 0,
+ parentIndentGuideDepths: [],
+ },
] as TreeListStackItem[];
const treeNodes = [] as TreeNode[];
@@ -48,11 +55,12 @@ export function toTreeList(
const {node, depth} = stackItem;
- //if the previous item has an indent guide but we don't then it was the last segment
- //so we trim the bottom
- const prevItemLine = last(treeNodes)?.indentGuide;
- if (prevItemLine != null && stackItem.isChildOfSelectedNode === false) {
- prevItemLine.trimBottom = true;
+ const prevItemLine = last(treeNodes);
+ //trim all the guides that have now ended
+ if (prevItemLine != null) {
+ for (let i = depth; i < prevItemLine.depth; i++) {
+ prevItemLine.indentGuides[i].trimBottom = true;
+ }
}
const isExpanded = expandedNodes.has(node.id);
@@ -73,15 +81,23 @@ export function toTreeList(
depth,
isExpanded,
frameworkEvents: events.length > 0 ? events.length : null,
- indentGuide: stackItem.isChildOfSelectedNode
- ? {
- depth: stackItem.selectedNodeDepth,
- style: 'ToChildren',
- //if first child of selected node add horizontal marker
- addHorizontalMarker: depth === stackItem.selectedNodeDepth + 1,
+ indentGuides: stackItem.parentIndentGuideDepths.map(
+ (parentGuideDepth, idx) => {
+ const isLastGuide =
+ idx === stackItem.parentIndentGuideDepths.length - 1;
+ return {
+ depth: parentGuideDepth,
+ addHorizontalMarker: isLastGuide,
trimBottom: false,
- }
- : null,
+
+ color:
+ stackItem.isChildOfSelectedNode &&
+ parentGuideDepth === stackItem.selectedNodeDepth
+ ? 'primary'
+ : 'secondary',
+ };
+ },
+ ),
});
i++;
@@ -97,12 +113,11 @@ export function toTreeList(
if (prevNode.depth < depth) {
break;
}
- prevNode.indentGuide = {
- depth: selectedNodeDepth - 1,
- style: 'ToParent',
- addHorizontalMarker: prevNode.depth == depth,
- trimBottom: prevNode.id === selectedNode,
- };
+ const selectedDepthIndentGuide =
+ prevNode.indentGuides[selectedNodeDepth - 1];
+ if (selectedDepthIndentGuide) {
+ selectedDepthIndentGuide.color = 'primary';
+ }
}
}
@@ -114,6 +129,10 @@ export function toTreeList(
stack.push({
node: child,
depth: depth + 1,
+ parentIndentGuideDepths: concat(
+ stackItem.parentIndentGuideDepths,
+ depth,
+ ),
isChildOfSelectedNode: isChildOfSelectedNode,
selectedNodeDepth: selectedNodeDepth,
});
@@ -122,10 +141,12 @@ export function toTreeList(
}
}
- //always trim last indent guide
- const prevItemLine = last(treeNodes)?.indentGuide;
+ //always trim last indent guides since they have 'ended'
+ const prevItemLine = last(treeNodes);
if (prevItemLine != null) {
- prevItemLine.trimBottom = true;
+ prevItemLine.indentGuides.forEach((guide) => {
+ guide.trimBottom = true;
+ });
}
return treeNodes;
diff --git a/desktop/static/icons/android-logo.png b/desktop/static/icons/android-logo.png
new file mode 100644
index 000000000..33ec0c304
Binary files /dev/null and b/desktop/static/icons/android-logo.png differ
diff --git a/desktop/static/icons/ios-logo.png b/desktop/static/icons/ios-logo.png
new file mode 100644
index 000000000..5e67e19db
Binary files /dev/null and b/desktop/static/icons/ios-logo.png differ