New Tree design
Summary: 1. Add indent guidess to all tree depths 2. Monospaced font 3. cleaned up alignment of icons 4. Gave everything a bit more space to breathe Changelog: UI Debugger Tree UI refresh, added indent guides and fixed alignment Reviewed By: mweststrate Differential Revision: D47626869 fbshipit-source-id: e4509621cda6c254f7dd5a7ec9b99c13efb577f4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
3891a5d61b
commit
0e15dce033
@@ -20,6 +20,7 @@ import React, {
|
|||||||
import {
|
import {
|
||||||
HighlightManager,
|
HighlightManager,
|
||||||
HighlightProvider,
|
HighlightProvider,
|
||||||
|
Layout,
|
||||||
styled,
|
styled,
|
||||||
theme,
|
theme,
|
||||||
useHighlighter,
|
useHighlighter,
|
||||||
@@ -27,30 +28,28 @@ import {
|
|||||||
useValue,
|
useValue,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import {plugin} from '../../index';
|
import {plugin} from '../../index';
|
||||||
import {Glyph} from 'flipper';
|
import {head, last} from 'lodash';
|
||||||
import {head} from 'lodash';
|
|
||||||
import {Badge, Typography} from 'antd';
|
import {Badge, Typography} from 'antd';
|
||||||
|
|
||||||
import {useVirtualizer} from '@tanstack/react-virtual';
|
import {useVirtualizer} from '@tanstack/react-virtual';
|
||||||
import {ContextMenu} from './ContextMenu';
|
import {ContextMenu} from './ContextMenu';
|
||||||
import {MillisSinceEpoch, useKeyboardControls} from './useKeyboardControls';
|
import {MillisSinceEpoch, useKeyboardControls} from './useKeyboardControls';
|
||||||
import {toTreeList} from './toTreeList';
|
import {toTreeList} from './toTreeList';
|
||||||
|
import {CaretDownOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const {Text} = Typography;
|
||||||
|
|
||||||
type LineStyle = 'ToParent' | 'ToChildren';
|
|
||||||
|
|
||||||
type NodeIndentGuide = {
|
type NodeIndentGuide = {
|
||||||
depth: number;
|
depth: number;
|
||||||
style: LineStyle;
|
|
||||||
addHorizontalMarker: boolean;
|
addHorizontalMarker: boolean;
|
||||||
trimBottom: boolean;
|
trimBottom: boolean;
|
||||||
|
color: 'primary' | 'secondary';
|
||||||
};
|
};
|
||||||
export type TreeNode = ClientNode & {
|
export type TreeNode = ClientNode & {
|
||||||
depth: number;
|
depth: number;
|
||||||
idx: number;
|
idx: number;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
indentGuide: NodeIndentGuide | null;
|
indentGuides: NodeIndentGuide[];
|
||||||
frameworkEvents: number | null;
|
frameworkEvents: number | null;
|
||||||
};
|
};
|
||||||
export function Tree2({
|
export function Tree2({
|
||||||
@@ -111,7 +110,7 @@ export function Tree2({
|
|||||||
const rowVirtualizer = useVirtualizer({
|
const rowVirtualizer = useVirtualizer({
|
||||||
count: treeNodes.length,
|
count: treeNodes.length,
|
||||||
getScrollElement: () => parentRef.current,
|
getScrollElement: () => parentRef.current,
|
||||||
estimateSize: () => 26,
|
estimateSize: () => TreeItemHeightNumber,
|
||||||
overscan: 20,
|
overscan: 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -261,31 +260,82 @@ export function Tree2({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function IndentGuide({indentGuide}: {indentGuide: NodeIndentGuide}) {
|
const secondaryColor = theme.buttonDefaultBackground;
|
||||||
const verticalLinePadding = `${renderDepthOffset * indentGuide.depth + 8}px`;
|
const GuideOffset = 11;
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div>
|
<div style={{pointerEvents: 'none'}}>
|
||||||
|
{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 (
|
||||||
|
<div
|
||||||
|
key={guide.depth}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: indentGuideLinePadding,
|
||||||
|
height: TreeItemHeight,
|
||||||
|
borderRight: `1px solid`,
|
||||||
|
borderImage: `linear-gradient(to bottom, ${firstHalf} 50%, ${secondHalf} 50%) 1`,
|
||||||
|
borderImageSlice: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{lastGuide?.addHorizontalMarker && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
width: verticalLinePadding,
|
width: hasExpandChildrenIcon
|
||||||
height: indentGuide.trimBottom ? HalfTreeItemHeight : TreeItemHeight,
|
? renderDepthOffset / 2
|
||||||
borderRight: `1px solid ${theme.primaryColor}`,
|
: renderDepthOffset,
|
||||||
}}></div>
|
|
||||||
{indentGuide.addHorizontalMarker && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
width: renderDepthOffset / 3,
|
|
||||||
height: HalfTreeItemHeight,
|
height: HalfTreeItemHeight,
|
||||||
borderBottom: `1px solid ${theme.primaryColor}`,
|
borderBottom: `1px solid ${
|
||||||
marginLeft: verticalLinePadding,
|
lastGuide.color === 'primary'
|
||||||
|
? theme.primaryColor
|
||||||
|
: secondaryColor
|
||||||
|
}`,
|
||||||
|
marginLeft: lastGuidePadding,
|
||||||
}}></div>
|
}}></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
(props, nextProps) =>
|
||||||
|
props.hasExpandChildrenIcon === nextProps.hasExpandChildrenIcon &&
|
||||||
|
props.indentGuides === nextProps.indentGuides &&
|
||||||
|
props.isSelected === nextProps.isSelected,
|
||||||
|
);
|
||||||
|
|
||||||
function TreeNodeRow({
|
function TreeNodeRow({
|
||||||
transform,
|
transform,
|
||||||
@@ -314,6 +364,8 @@ function TreeNodeRow({
|
|||||||
onCollapseNode: (node: Id) => void;
|
onCollapseNode: (node: Id) => void;
|
||||||
onHoverNode: (node: Id) => void;
|
onHoverNode: (node: Id) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const showExpandChildrenIcon = treeNode.children.length > 0;
|
||||||
|
const isSelected = treeNode.id === selectedNode;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
@@ -325,12 +377,15 @@ function TreeNodeRow({
|
|||||||
transform: transform,
|
transform: transform,
|
||||||
//Due to absolute positioning width is set outside of react via a useLayoutEffect in parent
|
//Due to absolute positioning width is set outside of react via a useLayoutEffect in parent
|
||||||
}}>
|
}}>
|
||||||
{treeNode.indentGuide != null && (
|
<IndentGuides
|
||||||
<IndentGuide indentGuide={treeNode.indentGuide} />
|
isSelected={isSelected}
|
||||||
)}
|
hasExpandChildrenIcon={showExpandChildrenIcon}
|
||||||
|
indentGuides={treeNode.indentGuides}
|
||||||
|
/>
|
||||||
|
|
||||||
<TreeNodeContent
|
<TreeNodeContent
|
||||||
isHighlighted={highlightedNodes.has(treeNode.id)}
|
isHighlighted={highlightedNodes.has(treeNode.id)}
|
||||||
isSelected={treeNode.id === selectedNode}
|
isSelected={isSelected}
|
||||||
isHovered={hoveredNode === treeNode.id}
|
isHovered={hoveredNode === treeNode.id}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
const kbIsNoLongerReservingScroll =
|
const kbIsNoLongerReservingScroll =
|
||||||
@@ -347,7 +402,7 @@ function TreeNodeRow({
|
|||||||
style={{overflow: 'visible'}}>
|
style={{overflow: 'visible'}}>
|
||||||
<ExpandedIconOrSpace
|
<ExpandedIconOrSpace
|
||||||
expanded={treeNode.isExpanded}
|
expanded={treeNode.isExpanded}
|
||||||
showIcon={treeNode.children.length > 0}
|
showIcon={showExpandChildrenIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (treeNode.isExpanded) {
|
if (treeNode.isExpanded) {
|
||||||
onCollapseNode(treeNode.id);
|
onCollapseNode(treeNode.id);
|
||||||
@@ -356,8 +411,8 @@ function TreeNodeRow({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{nodeIcon(treeNode)}
|
|
||||||
|
|
||||||
|
{nodeIcon(treeNode)}
|
||||||
<TreeNodeTextContent treeNode={treeNode} />
|
<TreeNodeTextContent treeNode={treeNode} />
|
||||||
{treeNode.frameworkEvents && (
|
{treeNode.frameworkEvents && (
|
||||||
<Badge
|
<Badge
|
||||||
@@ -375,10 +430,14 @@ function TreeNodeRow({
|
|||||||
|
|
||||||
function TreeNodeTextContent({treeNode}: {treeNode: TreeNode}) {
|
function TreeNodeTextContent({treeNode}: {treeNode: TreeNode}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<Layout.Horizontal
|
||||||
|
style={{
|
||||||
|
alignItems: 'baseline',
|
||||||
|
userSelect: 'none',
|
||||||
|
}}>
|
||||||
<HighlightedText text={treeNode.name} />
|
<HighlightedText text={treeNode.name} />
|
||||||
<InlineAttributes attributes={treeNode.inlineAttributes} />
|
<InlineAttributes attributes={treeNode.inlineAttributes} />
|
||||||
</>
|
</Layout.Horizontal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +463,8 @@ function InlineAttributes({attributes}: {attributes: Record<string, string>}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TreeItemHeight = '26px';
|
const TreeItemHeightNumber = 28;
|
||||||
|
const TreeItemHeight = `${TreeItemHeightNumber}px`;
|
||||||
const HalfTreeItemHeight = `calc(${TreeItemHeight} / 2)`;
|
const HalfTreeItemHeight = `calc(${TreeItemHeight} / 2)`;
|
||||||
|
|
||||||
const TreeNodeContent = styled.li<{
|
const TreeNodeContent = styled.li<{
|
||||||
@@ -414,12 +474,12 @@ const TreeNodeContent = styled.li<{
|
|||||||
isHighlighted: boolean;
|
isHighlighted: boolean;
|
||||||
}>(({item, isHovered, isSelected, isHighlighted}) => ({
|
}>(({item, isHovered, isSelected, isHighlighted}) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'baseline',
|
alignItems: 'center',
|
||||||
height: TreeItemHeight,
|
height: TreeItemHeight,
|
||||||
paddingLeft: `${item.depth * renderDepthOffset}px`,
|
paddingLeft: `${item.depth * renderDepthOffset}px`,
|
||||||
borderWidth: '1px',
|
borderWidth: '1px',
|
||||||
borderRadius: '3px',
|
borderRadius: '3px',
|
||||||
borderColor: isHovered ? theme.selectionBackgroundColor : 'transparent',
|
borderColor: 'transparent',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
@@ -427,6 +487,8 @@ const TreeNodeContent = styled.li<{
|
|||||||
? 'rgba(255,0,0,.3)'
|
? 'rgba(255,0,0,.3)'
|
||||||
: isSelected
|
: isSelected
|
||||||
? theme.selectionBackgroundColor
|
? theme.selectionBackgroundColor
|
||||||
|
: isHovered
|
||||||
|
? theme.backgroundWash
|
||||||
: theme.backgroundDefault,
|
: theme.backgroundDefault,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -440,57 +502,82 @@ function ExpandedIconOrSpace(props: {
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
style={{display: 'flex'}}
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
height: TreeItemHeight,
|
||||||
|
width: 20,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
props.onClick();
|
props.onClick();
|
||||||
}}>
|
}}>
|
||||||
<Glyph
|
<CaretDownOutlined
|
||||||
style={{
|
style={{
|
||||||
transform: props.expanded ? 'rotate(90deg)' : '',
|
cursor: 'pointer',
|
||||||
marginRight: '4px',
|
color: theme.textColorPlaceholder,
|
||||||
marginBottom: props.expanded ? '2px' : '',
|
fontSize: 14,
|
||||||
|
transform: props.expanded ? '' : 'rotate(-90deg)',
|
||||||
}}
|
}}
|
||||||
name="chevron-right"
|
|
||||||
size={12}
|
|
||||||
color="grey"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <div style={{width: '16px'}}></div>;
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 20,
|
||||||
|
height: TreeItemHeight,
|
||||||
|
}}></div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function HighlightedText(props: {text: string}) {
|
function HighlightedText(props: {text: string}) {
|
||||||
const highlightManager: HighlightManager = useHighlighter();
|
const highlightManager: HighlightManager = useHighlighter();
|
||||||
return <span>{highlightManager.render(props.text)} </span>;
|
return (
|
||||||
|
<Typography.Text>{highlightManager.render(props.text)} </Typography.Text>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeIcon(node: ClientNode) {
|
function nodeIcon(node: TreeNode) {
|
||||||
if (node.tags.includes('LithoMountable')) {
|
if (node.tags.includes('LithoMountable')) {
|
||||||
return <DecorationImage src="icons/litho-logo-blue.png" />;
|
return <NodeIconImage src="icons/litho-logo-blue.png" />;
|
||||||
} else if (node.tags.includes('Litho')) {
|
} else if (node.tags.includes('Litho')) {
|
||||||
return <DecorationImage src="icons/litho-logo.png" />;
|
return <NodeIconImage src="icons/litho-logo.png" />;
|
||||||
} else if (node.tags.includes('CK')) {
|
} else if (node.tags.includes('CK')) {
|
||||||
if (node.tags.includes('iOS')) {
|
if (node.tags.includes('iOS')) {
|
||||||
return <DecorationImage src="icons/ck-mounted-logo.png" />;
|
return <NodeIconImage src="icons/ck-mounted-logo.png" />;
|
||||||
}
|
}
|
||||||
return <DecorationImage src="icons/ck-logo.png" />;
|
return <NodeIconImage src="icons/ck-logo.png" />;
|
||||||
} else if (node.tags.includes('BloksBoundTree')) {
|
} else if (node.tags.includes('BloksBoundTree')) {
|
||||||
return <DecorationImage src="facebook/bloks-logo-orange.png" />;
|
return <NodeIconImage src="facebook/bloks-logo-orange.png" />;
|
||||||
} else if (node.tags.includes('BloksDerived')) {
|
} else if (node.tags.includes('BloksDerived')) {
|
||||||
return <DecorationImage src="facebook/bloks-logo-blue.png" />;
|
return <NodeIconImage src="facebook/bloks-logo-blue.png" />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: NodeIconSize,
|
||||||
|
width: 0,
|
||||||
|
marginRight: IconRightMargin,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DecorationImage = styled.img({
|
const NodeIconSize = 14;
|
||||||
height: 12,
|
const IconRightMargin = '4px';
|
||||||
marginRight: 5,
|
const NodeIconImage = styled.img({
|
||||||
width: 12,
|
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
|
//due to virtualisation the out of the box dom based scrolling doesnt work
|
||||||
function findSearchMatchingIndexes(
|
function findSearchMatchingIndexes(
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ import {
|
|||||||
ClientNode,
|
ClientNode,
|
||||||
} from '../../ClientTypes';
|
} from '../../ClientTypes';
|
||||||
import {DataSource} from 'flipper-plugin';
|
import {DataSource} from 'flipper-plugin';
|
||||||
import {last} from 'lodash';
|
import {concat, last} from 'lodash';
|
||||||
import {reverse} from 'lodash/fp';
|
import {reverse} from 'lodash/fp';
|
||||||
import {TreeNode} from './Tree';
|
import {TreeNode} from './Tree';
|
||||||
|
|
||||||
type TreeListStackItem = {
|
type TreeListStackItem = {
|
||||||
node: ClientNode;
|
node: ClientNode;
|
||||||
depth: number;
|
depth: number;
|
||||||
|
parentIndentGuideDepths: number[];
|
||||||
isChildOfSelectedNode: boolean;
|
isChildOfSelectedNode: boolean;
|
||||||
selectedNodeDepth: number;
|
selectedNodeDepth: number;
|
||||||
};
|
};
|
||||||
@@ -37,7 +38,13 @@ export function toTreeList(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const stack = [
|
const stack = [
|
||||||
{node: root, depth: 0, isChildOfSelectedNode: false, selectedNodeDepth: 0},
|
{
|
||||||
|
node: root,
|
||||||
|
depth: 0,
|
||||||
|
isChildOfSelectedNode: false,
|
||||||
|
selectedNodeDepth: 0,
|
||||||
|
parentIndentGuideDepths: [],
|
||||||
|
},
|
||||||
] as TreeListStackItem[];
|
] as TreeListStackItem[];
|
||||||
|
|
||||||
const treeNodes = [] as TreeNode[];
|
const treeNodes = [] as TreeNode[];
|
||||||
@@ -48,11 +55,12 @@ export function toTreeList(
|
|||||||
|
|
||||||
const {node, depth} = stackItem;
|
const {node, depth} = stackItem;
|
||||||
|
|
||||||
//if the previous item has an indent guide but we don't then it was the last segment
|
const prevItemLine = last(treeNodes);
|
||||||
//so we trim the bottom
|
//trim all the guides that have now ended
|
||||||
const prevItemLine = last(treeNodes)?.indentGuide;
|
if (prevItemLine != null) {
|
||||||
if (prevItemLine != null && stackItem.isChildOfSelectedNode === false) {
|
for (let i = depth; i < prevItemLine.depth; i++) {
|
||||||
prevItemLine.trimBottom = true;
|
prevItemLine.indentGuides[i].trimBottom = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isExpanded = expandedNodes.has(node.id);
|
const isExpanded = expandedNodes.has(node.id);
|
||||||
@@ -73,15 +81,23 @@ export function toTreeList(
|
|||||||
depth,
|
depth,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
frameworkEvents: events.length > 0 ? events.length : null,
|
frameworkEvents: events.length > 0 ? events.length : null,
|
||||||
indentGuide: stackItem.isChildOfSelectedNode
|
indentGuides: stackItem.parentIndentGuideDepths.map(
|
||||||
? {
|
(parentGuideDepth, idx) => {
|
||||||
depth: stackItem.selectedNodeDepth,
|
const isLastGuide =
|
||||||
style: 'ToChildren',
|
idx === stackItem.parentIndentGuideDepths.length - 1;
|
||||||
//if first child of selected node add horizontal marker
|
return {
|
||||||
addHorizontalMarker: depth === stackItem.selectedNodeDepth + 1,
|
depth: parentGuideDepth,
|
||||||
|
addHorizontalMarker: isLastGuide,
|
||||||
trimBottom: false,
|
trimBottom: false,
|
||||||
}
|
|
||||||
: null,
|
color:
|
||||||
|
stackItem.isChildOfSelectedNode &&
|
||||||
|
parentGuideDepth === stackItem.selectedNodeDepth
|
||||||
|
? 'primary'
|
||||||
|
: 'secondary',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
});
|
});
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
@@ -97,12 +113,11 @@ export function toTreeList(
|
|||||||
if (prevNode.depth < depth) {
|
if (prevNode.depth < depth) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
prevNode.indentGuide = {
|
const selectedDepthIndentGuide =
|
||||||
depth: selectedNodeDepth - 1,
|
prevNode.indentGuides[selectedNodeDepth - 1];
|
||||||
style: 'ToParent',
|
if (selectedDepthIndentGuide) {
|
||||||
addHorizontalMarker: prevNode.depth == depth,
|
selectedDepthIndentGuide.color = 'primary';
|
||||||
trimBottom: prevNode.id === selectedNode,
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +129,10 @@ export function toTreeList(
|
|||||||
stack.push({
|
stack.push({
|
||||||
node: child,
|
node: child,
|
||||||
depth: depth + 1,
|
depth: depth + 1,
|
||||||
|
parentIndentGuideDepths: concat(
|
||||||
|
stackItem.parentIndentGuideDepths,
|
||||||
|
depth,
|
||||||
|
),
|
||||||
isChildOfSelectedNode: isChildOfSelectedNode,
|
isChildOfSelectedNode: isChildOfSelectedNode,
|
||||||
selectedNodeDepth: selectedNodeDepth,
|
selectedNodeDepth: selectedNodeDepth,
|
||||||
});
|
});
|
||||||
@@ -122,10 +141,12 @@ export function toTreeList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//always trim last indent guide
|
//always trim last indent guides since they have 'ended'
|
||||||
const prevItemLine = last(treeNodes)?.indentGuide;
|
const prevItemLine = last(treeNodes);
|
||||||
if (prevItemLine != null) {
|
if (prevItemLine != null) {
|
||||||
prevItemLine.trimBottom = true;
|
prevItemLine.indentGuides.forEach((guide) => {
|
||||||
|
guide.trimBottom = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return treeNodes;
|
return treeNodes;
|
||||||
|
|||||||
BIN
desktop/static/icons/android-logo.png
Normal file
BIN
desktop/static/icons/android-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
BIN
desktop/static/icons/ios-logo.png
Normal file
BIN
desktop/static/icons/ios-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
Reference in New Issue
Block a user