diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx index 9c76b503..10a1f637 100644 --- a/libs/extension/src/components/ComponentInfo.tsx +++ b/libs/extension/src/components/ComponentInfo.tsx @@ -2,8 +2,9 @@ import styles from './ComponentsInfo.less'; import Eye from '../svgs/Eye'; import Debug from '../svgs/Debug'; import Copy from '../svgs/Copy'; -import Arrow from '../svgs/Arrow'; +import Triangle from '../svgs/Triangle'; import { useState } from 'horizon'; +import { IData } from './VTree'; type IComponentInfo = { name: string; @@ -12,7 +13,9 @@ type IComponentInfo = { context?: IAttr[]; state?: IAttr[]; hooks?: IAttr[]; - } + }; + parents: IData[]; + onClickParent: (item: IData) => void; }; type IAttr = { @@ -22,24 +25,29 @@ type IAttr = { indentation: number; } -function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { - const [collapsedNode, setCollapsedNode] = useState(new Set()); - const handleCollapse = (index: number) => { - const newSet = new Set(); - collapsedNode.forEach(value => { - newSet.add(value); - }); - if (newSet.has(index)) { - newSet.delete(index); +function collapseAllNodes(attrs: IAttr[]) { + return attrs.filter((item, index) => { + const nextItem = attrs[index + 1]; + return nextItem ? nextItem.indentation - item.indentation > 0 : false; + }); +} + +function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) { + const [collapsedNode, setCollapsedNode] = useState(collapseAllNodes(attrs)); + const handleCollapse = (item: IAttr) => { + const nodes = [...collapsedNode]; + const i = nodes.indexOf(item); + if (i === -1) { + nodes.push(item); } else { - newSet.add(index); + nodes.splice(i, 1); } - setCollapsedNode(newSet); + setCollapsedNode(nodes); }; const showAttr = []; let currentIndentation = null; - attr.forEach((item, index) => { + attrs.forEach((item, index) => { const indentation = item.indentation; if (currentIndentation !== null) { if (indentation > currentIndentation) { @@ -48,12 +56,12 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { currentIndentation = null; } } - const nextItem = attr[index + 1]; + const nextItem = attrs[index + 1]; const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false; - const isCollapsed = collapsedNode.has(index); + const isCollapsed = collapsedNode.includes(item); showAttr.push( -
(handleCollapse(index))}> - {hasChild && } +
(handleCollapse(item))}> + {hasChild && } {`${item.name}`} {' :'} {item.value} @@ -79,28 +87,38 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { ); } -export default function ComponentInfo({ name, attrs }: IComponentInfo) { +export default function ComponentInfo({ name, attrs, parents, onClickParent }: IComponentInfo) { const { state, props, context, hooks } = attrs; return (
- - {name} - - - - - - - + {name && <> + + {name} + + + + + + + + }
- {context && } - {props && } - {state && } - {hooks && } -
- rendered by + {context && } + {props && } + {state && } + {hooks && } +
+ {name &&
+ parents: { + parents.map(item => ()) + } +
}
diff --git a/libs/extension/src/components/ComponentsInfo.less b/libs/extension/src/components/ComponentsInfo.less index 3622363e..9a52e2fb 100644 --- a/libs/extension/src/components/ComponentsInfo.less +++ b/libs/extension/src/components/ComponentsInfo.less @@ -32,11 +32,11 @@ .componentInfoMain { overflow-y: auto; - :last-child { + >:last-child { border-bottom: unset; } - :first-child { + >:first-child { padding: unset; } @@ -57,15 +57,11 @@ .attrType { flex: 1 1 0; } - - .attrCopy { flex: 0 0 1rem; padding-right: 1rem; } } - - .attrDetail { padding-bottom: 0.5rem; @@ -84,9 +80,19 @@ } } } + } - .renderInfo { - flex: 1 1 0; + .parentsInfo { + flex: 1 1 0; + .parent { + display: block; + cursor: pointer; + text-align: left; + color: @component-name-color; + width: 100%; + &:hover { + background-color: @select-color; + } } } } diff --git a/libs/extension/src/components/FilterTree.ts b/libs/extension/src/components/FilterTree.ts new file mode 100644 index 00000000..446bd05d --- /dev/null +++ b/libs/extension/src/components/FilterTree.ts @@ -0,0 +1,147 @@ +// 过滤树的抽象逻辑 +// 需要知道渲染了哪些数据,过滤的字符串/正则表达式 +// 控制Tree组件位置跳转,告知匹配结果 +// 清空搜索框,告知搜索框当前是第几个结果,跳转搜索结果 +// +// 跳转搜索结果的交互逻辑: +// 如果当前页面存在匹配项,页面不动 +// 如果当前页面不存在匹配项,页面跳转到第一个匹配项位置 +// 如果匹配项被折叠,需要展开其父节点。注意只展开当前匹配项的父节点,其他匹配项的父节点不展开 +// 跳转到上一个匹配项或下一个匹配项时,如果匹配项被折叠,需要展开其父节点 +// +// 寻找父节点: +// 找到该节点的缩进值,和index值,在data中向上遍历,通过缩进值判断父节点 + +import { useState, useRef } from 'horizon'; +import { createRegExp } from '../utils'; + +/** + * 把节点的父节点从收起节点数组中删除,并返回新的收起节点数组 + * + * @param item 需要展开父节点的节点 + * @param data 全部数据 + * @param collapsedNodes 收起节点数据 + * @returns 新的收起节点数组 + */ +function expandItemParent(item: BaseType, data: BaseType[], collapsedNodes: BaseType[]): BaseType[] { + const index = data.indexOf(item); + let currentIndentation = item.indentation; + // 不对原始数据进行修改 + const newCollapsedNodes = [...collapsedNodes]; + for (let i = index - 1; i >= 0; i--) { + const lastData = data[i]; + const lastIndentation = lastData.indentation; + // 缩进更小,找到了父节点 + if (lastIndentation < currentIndentation) { + // 更新缩进值,只找父节点的父节点,避免修改父节点的兄弟节点的展开状态 + currentIndentation = lastIndentation; + const cIndex = newCollapsedNodes.indexOf(lastData); + if (cIndex !== -1) { + newCollapsedNodes.splice(cIndex, 1); + } + } + } + return newCollapsedNodes; +} + +type BaseType = { + id: string, + name: string, + indentation: number, +} + +export function FilterTree(props: { data: T[] }) { + const { data } = props; + const [filterValue, setFilterValue] = useState(''); + const [currentItem, setCurrentItem] = useState(null); // 当前选中的匹配项 + const showItemsRef = useRef([]); // 页面展示的 items + const matchItemsRef = useRef([]); // 匹配过滤条件的 items + const collapsedNodesRef = useRef([]); // 折叠节点,如果匹配 item 被折叠了,需要展开 + + const matchItems = matchItemsRef.current; + const collapsedNodes = collapsedNodesRef.current; + + const updateCollapsedNodes = (item: BaseType) => { + const newcollapsedNodes = expandItemParent(item, data, collapsedNodes); + // 如果新旧收起节点数组长度不一样,说明存在收起节点 + if (newcollapsedNodes.length !== collapsedNodes.length) { + // 更新引用,确保 VTree 拿到新的 collapsedNodes + collapsedNodesRef.current = newcollapsedNodes; + } + }; + + const onChangeSearchValue = (search: string) => { + const reg = createRegExp(search); + let newCurrentItem = null; + let newMatchItems = []; + if (search !== '') { + const showItems: T[] = showItemsRef.current; + newMatchItems = data.reduce((pre, current) => { + const { name } = current; + if (reg && name.match(reg)) { + pre.push(current); + // 如果当前页面显示的 item 存在匹配项,则把它设置为 currentItem + if (newCurrentItem === null && showItems.includes(current)) { + newCurrentItem = current; + } + } + return pre; + }, []); + if (newMatchItems.length === 0) { + setCurrentItem(null); + } else { + if (newCurrentItem === null) { + const item = newMatchItems[0]; + // 不处于当前展示页面,需要展开父节点 + updateCollapsedNodes(item); + setCurrentItem(item); + } else { + setCurrentItem(newCurrentItem); + } + } + } else { + setCurrentItem(null); + } + matchItemsRef.current = newMatchItems; + setFilterValue(search); + }; + const onSelectNext = () => { + const index = matchItems.indexOf(currentItem); + const nextIndex = index + 1; + const item = nextIndex < matchItemsRef.current.length ? matchItems[nextIndex] : matchItems[0]; + // 可能不处于当前展示页面,需要展开父节点 + updateCollapsedNodes(item); + setCurrentItem(item); + }; + const onSelectLast = () => { + const index = matchItems.indexOf(currentItem); + const last = index - 1; + const item = last >= 0 ? matchItems[last] : matchItems[matchItems.length - 1]; + // 可能不处于当前展示页面,需要展开父节点 + updateCollapsedNodes(item); + setCurrentItem(item); + }; + const setShowItems = (items) => { + showItemsRef.current = [...items]; + }; + const onClear = () => { + onChangeSearchValue(''); + }; + const setCollapsedNodes = (items) => { + // 不更新引用,避免子组件的重复渲染 + collapsedNodesRef.current.length = 0; + collapsedNodesRef.current.push(...items); + }; + return { + filterValue, + onChangeSearchValue, + onClear, + currentItem, + matchItems, + onSelectNext, + onSelectLast, + setShowItems, + collapsedNodes, + setCollapsedNodes, + }; +} diff --git a/libs/extension/src/components/ResizeEvent.ts b/libs/extension/src/components/ResizeEvent.ts new file mode 100644 index 00000000..0150d0ad --- /dev/null +++ b/libs/extension/src/components/ResizeEvent.ts @@ -0,0 +1,78 @@ +/** + * + * 由于 ResizeObserver 对 IE 和低版本主流浏览器不兼容,需要我们自己解决这个问题。 + * 这是一个不依赖任何框架的监听 dom 元素尺寸变化的解决方案。 + * 浏览器出于性能的考虑,只有 window 的 resize 事件会触发。我们通过 object 标签可以得到 + * 一个 window 对象,让 object dom 元素成为待观测 dom 的子元素,并且和待观测 dom 大小一致。 + * 这样一旦待观测 dom 的大小发生变化, window 的大小也会发生变化,我们就可以通过监听 window + * 大小变化的方式监听待观测 dom 的大小变化。 + * + *
+ * --> 和父 div 保持大小一致 + * --> 添加 resize 事件监听 + * + *
+ * + */ + +function timeout(fn) { + return setTimeout(fn, 20); +} + +function requestFrame(fn) { + const raf = requestAnimationFrame || timeout; + return raf(fn); +} + +function cancelFrame(id) { + const cancel = cancelAnimationFrame || clearTimeout; + cancel(id); +} + +// 在闲置帧触发回调事件,如果在本次触发前存在未处理回调事件, +// 需要取消未处理的回调事件 +function resizeListener(event) { + const win = event.target; + if (win.__resizeRAF__) { + cancelFrame(win.__resizeRAF__); + } + win.__resizeRAF__ = requestFrame(function () { + const observeElement = win.__observeElement__; + observeElement.__resizeCallbacks__.forEach(function (fn) { + fn.call(observeElement, observeElement, event); + }); + }); +} + +function loadObserver() { + // 将待观测元素传递给 object 标签的 window 对象,这样在触发 resize 事件时可以拿到待观测元素 + this.contentDocument.defaultView.__observeElement__ = this.__observeElement__; + // 给 html 的 window 对象添加 resize 事件 + this.contentDocument.defaultView.addEventListener('resize', resizeListener); +} + +export function addResizeListener(element: any, fn: any) { + if (!element.__resizeCallbacks__) { + element.__resizeCallbacks__ = [fn]; + element.style.position = 'relative'; + const observer = document.createElement('object'); + observer.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;'); + observer.data = 'about:blank'; + observer.onload = loadObserver; + observer.type = 'text/html'; + observer.__observeElement__ = element; + element.__observer__ = observer; + element.appendChild(observer); + } else { + element.__resizeCallbacks__.push(fn); + } +} + +export function removeResizeListener(element, fn) { + element.__resizeCallbacks__.splice(element.__resizeCallbacks__.indexOf(fn), 1); + if (!element.__resizeCallbacks__.length) { + element.__observer__.contentDocument.defaultView.removeEventListener('resize', resizeListener); + element.removeChild(element.__observer__); + element.__observer__ = null; + } +} diff --git a/libs/extension/src/components/Search.tsx b/libs/extension/src/components/Search.tsx index ce644325..1ea264d3 100644 --- a/libs/extension/src/components/Search.tsx +++ b/libs/extension/src/components/Search.tsx @@ -2,10 +2,11 @@ import styles from './Search.less'; interface SearchProps { onChange: (event: any) => void, + value: string, } export default function Search(props: SearchProps) { - const { onChange } = props; + const { onChange, value } = props; const handleChange = (event) => { onChange(event.target.value); }; @@ -13,6 +14,7 @@ export default function Search(props: SearchProps) { ); diff --git a/libs/extension/src/components/SizeObserver.tsx b/libs/extension/src/components/SizeObserver.tsx new file mode 100644 index 00000000..3d430093 --- /dev/null +++ b/libs/extension/src/components/SizeObserver.tsx @@ -0,0 +1,33 @@ +import { useEffect, useState, useRef } from 'horizon'; +import { addResizeListener, removeResizeListener } from './ResizeEvent'; + + +export function SizeObserver(props) { + const { children, ...rest } = props; + const containerRef = useRef(); + const [size, setSize] = useState(); + const notifyChild = (element) => { + setSize({ + width: element.offsetWidth, + height: element.offsetHeight, + }); + }; + useEffect(() => { + const element = containerRef.current; + setSize({ + width: element.offsetWidth, + height: element.offsetHeight, + }); + addResizeListener(element, notifyChild); + return () => { + removeResizeListener(element, notifyChild); + }; + }, []); + const myChild = size ? children(size.width, size.height) : null; + + return ( +
+ {myChild} +
+ ); +} \ No newline at end of file diff --git a/libs/extension/src/components/VList.less b/libs/extension/src/components/VList.less new file mode 100644 index 00000000..8c14e471 --- /dev/null +++ b/libs/extension/src/components/VList.less @@ -0,0 +1,11 @@ +.container { + position: relative; + overflow-y: auto; + height: 100%; + width: 100%; +} + +.item { + position: absolute; + width: 100%; +} \ No newline at end of file diff --git a/libs/extension/src/components/VList.tsx b/libs/extension/src/components/VList.tsx new file mode 100644 index 00000000..c932a492 --- /dev/null +++ b/libs/extension/src/components/VList.tsx @@ -0,0 +1,98 @@ + +import { useState, useRef, useEffect } from 'horizon'; +import styles from './VList.less'; + +interface IProps { + data: T[], + width: number, // 暂时未用到,当需要支持横向滚动时使用 + height: number, // VList 的高度 + children: any, // horizon 组件,组件类型是 T + itemHeight: number, + scrollToItem?: T, // 滚动到指定项位置,如果该项在可见区域内,不滚动,如果不在,则滚动到中间位置 + onRendered: (renderInfo: renderInfoType) => void; + filter?(data: T): boolean, // false 表示该行不显示 +} + +export type renderInfoType = { + visibleItems: T[], + skipItemCountBeforeScrollItem: number, +}; + +export function VList(props: IProps) { + const { + data, + height, + children, + itemHeight, + scrollToItem, + filter, + onRendered, + } = props; + const [scrollTop, setScrollTop] = useState(data.indexOf(scrollToItem) * itemHeight); + const renderInfoRef: { current: renderInfoType } = useRef({ visibleItems: [], skipItemCountBeforeScrollItem: 0 }); + const containerRef = useRef(); + useEffect(() => { + onRendered(renderInfoRef.current); + }); + + useEffect(() => { + if (scrollToItem) { + const renderInfo = renderInfoRef.current; + // 在滚动区域,不滚动 + if (!renderInfo.visibleItems.includes(scrollToItem)) { + const index = data.indexOf(scrollToItem); + // top值计算需要减掉filter条件判定不显示项 + const totalCount = index - renderInfoRef.current.skipItemCountBeforeScrollItem; + // 显示在页面中间 + const top = totalCount * itemHeight - height / 2; + containerRef.current.scrollTo({ top: top }); + } + } + }, [scrollToItem]); + + const handleScroll = (event: any) => { + const scrollTop = event.target.scrollTop; + setScrollTop(scrollTop); + }; + const showList: any[] = []; + let totalHeight = 0; + // 顶部冗余 + const startShowTopValue = Math.max(scrollTop - itemHeight * 4, 0); + // 底部冗余 + const showNum = Math.floor(height / itemHeight) + 4; + // 如果最后一个显示不全,不统计在显示 ids 内 + const maxTop = scrollTop + height - itemHeight; + // 清空记录的上次渲染的数据 + renderInfoRef.current.visibleItems.length = 0; + const scrollItemIndex = data.indexOf(scrollToItem); + renderInfoRef.current.skipItemCountBeforeScrollItem = 0; + data.forEach((item, i) => { + if (filter && !filter(item)) { + if (scrollItemIndex > i) { + renderInfoRef.current.skipItemCountBeforeScrollItem++; + } + return; + } + if (totalHeight >= startShowTopValue && showList.length <= showNum) { + showList.push( +
+ {children(i, item)} +
+ ); + if (totalHeight >= scrollTop && totalHeight < maxTop) { + renderInfoRef.current.visibleItems.push(item); + } + } + totalHeight += itemHeight; + }); + + return ( +
+ {showList} +
+
+ ); +} diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index 1460ce0f..0f34f9cd 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -1,10 +1,7 @@ @import 'assets.less'; .treeContainer { - position: relative; - width: 100%; height: 100%; - overflow-y: auto; .treeItem { width: 100%; diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 86f1daf7..a286e411 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -1,7 +1,9 @@ -import { useState } from 'horizon'; +import { useState, useEffect } from 'horizon'; import styles from './VTree.less'; -import Arrow from '../svgs/Arrow'; +import Triangle from '../svgs/Triangle'; import { createRegExp } from './../utils'; +import { SizeObserver } from './SizeObserver'; +import { renderInfoType, VList } from './VList'; export interface IData { id: string; @@ -10,44 +12,44 @@ export interface IData { userKey: string; } -type IItem = { - style: any, +interface IItem { hasChild: boolean, - onCollapse: (id: string) => void, - onClick: (id: string) => void, + onCollapse: (data: IData) => void, + onClick: (id: IData) => void, isCollapsed: boolean, isSelect: boolean, highlightValue: string, -} & IData + data: IData, +} -// TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值 -const showNum = 70; -const lineHeight = 18; const indentationLength = 20; function Item(props: IItem) { const { - name, - style, - userKey, hasChild, onCollapse, isCollapsed, - id, - indentation, + data, onClick, isSelect, - highlightValue, + highlightValue = '', } = props; + + const { + name, + userKey, + indentation, + } = data; + const isShowKey = userKey !== ''; - const showIcon = hasChild ? : ''; + const showIcon = hasChild ? : ''; const handleClickCollapse = () => { - onCollapse(id); + onCollapse(data); }; const handleClick = () => { - onClick(id); + onClick(data); }; - const itemAttr: any = { style, className: styles.treeItem, onClick: handleClick }; + const itemAttr: any = { className: styles.treeItem, onClick: handleClick }; if (isSelect) { itemAttr.tabIndex = 0; itemAttr.className = styles.treeItem + ' ' + styles.select; @@ -93,81 +95,103 @@ function Item(props: IItem) { ); } -function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) { - const [scrollTop, setScrollTop] = useState(0); - const [collapseNode, setCollapseNode] = useState(new Set()); - const [selectItem, setSelectItem] = useState(); - const changeCollapseNode = (id: string) => { - const nodes = new Set(); - collapseNode.forEach(value => { - nodes.add(value); - }); - if (nodes.has(id)) { - nodes.delete(id); +function VTree(props: { + data: IData[], + highlightValue: string, + scrollToItem: IData, + onRendered: (renderInfo: renderInfoType) => void, + collapsedNodes?: IData[], + onCollapseNode?: (item: IData[]) => void, + selectItem: IData[], + onSelectItem: (item: IData) => void, +}) { + const { data, highlightValue, scrollToItem, onRendered, onCollapseNode, onSelectItem } = props; + const [collapseNode, setCollapseNode] = useState(props.collapsedNodes || []); + const [selectItem, setSelectItem] = useState(props.selectItem); + useEffect(() => { + setSelectItem(scrollToItem); + }, [scrollToItem]); + useEffect(() => { + if (props.selectItem !== selectItem) { + setSelectItem(props.selectItem); + } + }, [props.selectItem]); + useEffect(() => { + setCollapseNode(props.collapsedNodes || []); + }, [props.collapsedNodes]); + + const changeCollapseNode = (item: IData) => { + const nodes: IData[] = [...collapseNode]; + const index = nodes.indexOf(item); + if (index === -1) { + nodes.push(item); } else { - nodes.add(id); + nodes.splice(index, 1); } setCollapseNode(nodes); + if (onCollapseNode) { + onCollapseNode(nodes); + } }; - const handleClickItem = (id: string) => { - setSelectItem(id); + const handleClickItem = (item: IData) => { + setSelectItem(item); + if (onSelectItem) { + onSelectItem(item); + } }; - const showList: any[] = []; - let totalHeight = 0; let currentCollapseIndentation: null | number = null; - data.forEach((item, index) => { - // 存在未处理完的收起节点 + // 过滤掉折叠的 item,不展示在 VList 中 + const filter = (item: IData) => { if (currentCollapseIndentation !== null) { - const indentation = item.indentation; // 缩进更大,不显示 - if (indentation > currentCollapseIndentation) { - return; + if (item.indentation > currentCollapseIndentation) { + return false; } else { // 缩进小,说明完成了该收起节点的子节点处理。 currentCollapseIndentation = null; } } - const id = item.id; - const isCollapsed = collapseNode.has(id); - if (totalHeight >= scrollTop && showList.length <= showNum) { - const nextItem = data[index + 1]; - // 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标 - const hasChild = nextItem ? nextItem.indentation > item.indentation : false; - showList.push( - - ); - } - totalHeight = totalHeight + lineHeight; + const isCollapsed = collapseNode.includes(item); if (isCollapsed) { // 该节点需要收起子节点 currentCollapseIndentation = item.indentation; } - }); - - const handleScroll = (event: any) => { - const scrollTop = event.target.scrollTop; - // 顶部留 100px 冗余高度 - setScrollTop(Math.max(scrollTop - 100, 0)); + return true; }; return ( -
- {showList} - {/* 确保有足够的高度 */} -
-
+ + {(width: number, height: number) => { + return ( + + {(index: number, item: IData) => { + // 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标 + const nextItem = data[index + 1]; + const hasChild = nextItem && nextItem.indentation > item.indentation; + return ( + + ); + }} + + ); + }} + ); } diff --git a/libs/extension/src/components/assets.less b/libs/extension/src/components/assets.less index 7e74bacf..45e1b0c4 100644 --- a/libs/extension/src/components/assets.less +++ b/libs/extension/src/components/assets.less @@ -6,6 +6,7 @@ @componentKeyValue-color: rgb(26, 26, 166); @component-attr-color: rgb(200, 0, 0); @select-color: rgb(141 199 248 / 60%); +@hover-color: black; @top-height: 2.625rem; @divider-width: 0.2px; diff --git a/libs/extension/src/panel/App.less b/libs/extension/src/panel/App.less index f48e225a..b974c557 100644 --- a/libs/extension/src/panel/App.less +++ b/libs/extension/src/panel/App.less @@ -7,6 +7,12 @@ font-size: @common-font-size; } +button { + border: none; + background: none; + padding: 0; +} + .left { flex: 7; display: flex; @@ -17,6 +23,7 @@ flex: 0 0 @top-height; display: flex; align-items: center; + padding-right: 0.4rem; .select { padding: 0 0.25rem 0 0.25rem; @@ -33,6 +40,20 @@ .search { flex: 1 1 0; } + + .searchResult{ + flex: 0 0 ; + padding: 0 0.4rem; + } + + .searchAction { + flex: 0 0 1rem; + height: 1rem; + color: @arrow-color; + &:hover{ + color: @hover-color; + } + } } .left_bottom { diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index 2b9ef6bc..7cea62e9 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -5,36 +5,24 @@ import ComponentInfo from '../components/ComponentInfo'; import styles from './App.less'; import Select from '../svgs/Select'; import { mockParsedVNodeData, parsedMockState } from '../devtools/mock'; +import { FilterTree } from '../components/FilterTree'; +import Close from '../svgs/Close'; +import Arrow from './../svgs/Arrow'; -function App() { - const [parsedVNodeData, setParsedVNodeData] = useState([]); - const [componentInfo, setComponentInfo] = useState({ name: null, attrs: {} }); - const [filterValue, setFilterValue] = useState(''); - useEffect(() => { - if (isDev) { - setParsedVNodeData(mockParsedVNodeData); - setComponentInfo({ - name: 'Demo', - attrs: { - state: parsedMockState, - props: parsedMockState, - }, - }); - } - }, []); +const parseVNodeData = (rawData) => { const idIndentationMap: { [id: string]: number; } = {}; const data: IData[] = []; let i = 0; - while (i < parsedVNodeData.length) { - const id = parsedVNodeData[i] as string; + while (i < rawData.length) { + const id = rawData[i] as string; i++; - const name = parsedVNodeData[i] as string; + const name = rawData[i] as string; i++; - const parentId = parsedVNodeData[i] as string; + const parentId = rawData[i] as string; i++; - const userKey = parsedVNodeData[i] as string; + const userKey = rawData[i] as string; i++; const indentation = parentId === '' ? 0 : idIndentationMap[parentId] + 1; idIndentationMap[id] = indentation; @@ -43,11 +31,76 @@ function App() { }; data.push(item); } + return data; +}; + +const getParents = (item: IData | null, parsedVNodeData: IData[]) => { + const parents: IData[] = []; + if (item) { + const index = parsedVNodeData.indexOf(item); + let indentation = item.indentation; + for (let i = index; i >= 0; i--) { + const last = parsedVNodeData[i]; + const lastIndentation = last.indentation; + if (lastIndentation < indentation) { + parents.push(last); + indentation = lastIndentation; + } + } + } + return parents; +}; + +function App() { + const [parsedVNodeData, setParsedVNodeData] = useState([]); + const [componentAttrs, setComponentAttrs] = useState({}); + const [selectComp, setSelectComp] = useState(null); + + const { + filterValue, + onChangeSearchValue: setFilterValue, + onClear, + currentItem, + matchItems, + onSelectNext, + onSelectLast, + setShowItems, + collapsedNodes, + setCollapsedNodes, + } = FilterTree({ data: parsedVNodeData }); + + useEffect(() => { + if (isDev) { + const parsedData = parseVNodeData(mockParsedVNodeData); + setParsedVNodeData(parsedData); + setComponentAttrs({ + state: parsedMockState, + props: parsedMockState, + }); + } + }, []); const handleSearchChange = (str: string) => { setFilterValue(str); }; + const handleSelectComp = (item: IData) => { + setComponentAttrs({ + state: parsedMockState, + props: parsedMockState, + }); + setSelectComp(item); + }; + + const handleClickParent = (item: IData) => { + setSelectComp(item); + }; + + const onRendered = (info) => { + setShowItems(info.visibleItems); + }; + const parents = getParents(selectComp, parsedVNodeData); + return (
@@ -57,15 +110,34 @@ function App() {
- +
+ {filterValue !== '' && <> + {`${matchItems.indexOf(currentItem) + 1}/${matchItems.length}`} +
+ + + + }
- +
- +
); diff --git a/libs/extension/src/svgs/Arrow.tsx b/libs/extension/src/svgs/Arrow.tsx index 0cb49d0d..4ee84159 100644 --- a/libs/extension/src/svgs/Arrow.tsx +++ b/libs/extension/src/svgs/Arrow.tsx @@ -1,16 +1,16 @@ interface IArrow { - director: 'right' | 'down' + direction: 'up' | 'down' } -export default function Arrow({ director }: IArrow) { +export default function Arrow({ direction: director }: IArrow) { let d: string; - if (director === 'right') { - d = 'm2 0l12 8l-12 8 z'; + if (director === 'up') { + d = 'M4 9.5 L5 10.5 L8 7.5 L11 10.5 L12 9.5 L8 5.5 z'; } else if (director === 'down') { - d = 'm0 2h16 l-8 12 z'; + d = 'M5 5.5 L4 6.5 L8 10.5 L12 6.5 L11 5.5 L8 8.5z'; } return ( - + ); diff --git a/libs/extension/src/svgs/Close.tsx b/libs/extension/src/svgs/Close.tsx new file mode 100644 index 00000000..621082fd --- /dev/null +++ b/libs/extension/src/svgs/Close.tsx @@ -0,0 +1,8 @@ + +export default function Close() { + return ( + + + + ); +} diff --git a/libs/extension/src/svgs/Triangle.tsx b/libs/extension/src/svgs/Triangle.tsx new file mode 100644 index 00000000..ecb6b3a0 --- /dev/null +++ b/libs/extension/src/svgs/Triangle.tsx @@ -0,0 +1,17 @@ +interface IArrow { + director: 'right' | 'down' +} + +export default function Triangle({ director }: IArrow) { + let d: string; + if (director === 'right') { + d = 'm2 0l12 8l-12 8 z'; + } else if (director === 'down') { + d = 'm0 2h16 l-8 12 z'; + } + return ( + + + + ); +} \ No newline at end of file