Match-id-7133f2734eacafd1263023fbe3921097f915d497
This commit is contained in:
parent
5cde85df95
commit
e13f3245bd
|
@ -35,7 +35,7 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) {
|
|||
newSet.add(index);
|
||||
}
|
||||
setCollapsedNode(newSet);
|
||||
}
|
||||
};
|
||||
|
||||
const showAttr = [];
|
||||
let currentIndentation = null;
|
||||
|
@ -76,7 +76,7 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) {
|
|||
{showAttr}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default function ComponentInfo({ name, attrs }: IComponentInfo) {
|
||||
|
@ -104,5 +104,5 @@ export default function ComponentInfo({ name, attrs }: IComponentInfo) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -68,6 +68,7 @@
|
|||
|
||||
.attrDetail {
|
||||
padding-bottom: 0.5rem;
|
||||
|
||||
.attrArrow {
|
||||
color: @arrow-color;
|
||||
width: 12px;
|
||||
|
|
|
@ -8,12 +8,12 @@ export default function Search(props: SearchProps) {
|
|||
const { onChange } = props;
|
||||
const handleChange = (event) => {
|
||||
onChange(event.target.value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<input
|
||||
onChange={handleChange}
|
||||
className={styles.search}
|
||||
placeholder={'Search (text or /regex/)'}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -5,10 +5,12 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.treeItem {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
line-height: 18px;
|
||||
|
||||
&:hover {
|
||||
background-color: @select-color;
|
||||
}
|
||||
|
@ -37,4 +39,3 @@
|
|||
background-color: rgb(141 199 248 / 60%);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,14 +43,14 @@ function Item(props: IItem) {
|
|||
const showIcon = hasChild ? <Arrow director={isCollapsed ? 'right' : 'down'} /> : '';
|
||||
const handleClickCollapse = () => {
|
||||
onCollapse(id);
|
||||
}
|
||||
};
|
||||
const handleClick = () => {
|
||||
onClick(id);
|
||||
}
|
||||
const itemAttr: any = {style, className: styles.treeItem, onClick: handleClick};
|
||||
};
|
||||
const itemAttr: any = { style, className: styles.treeItem, onClick: handleClick };
|
||||
if (isSelect) {
|
||||
itemAttr.tabIndex = 0;
|
||||
itemAttr.className = styles.treeItem + ' ' + styles.select
|
||||
itemAttr.className = styles.treeItem + ' ' + styles.select;
|
||||
}
|
||||
const reg = createRegExp(highlightValue);
|
||||
const heightCharacters = name.match(reg);
|
||||
|
@ -60,7 +60,7 @@ function Item(props: IItem) {
|
|||
showName = [];
|
||||
// 高亮第一次匹配即可
|
||||
const char = heightCharacters[0];
|
||||
let index = name.search(char);
|
||||
const index = name.search(char);
|
||||
const notHighlightStr = cutName.slice(0, index);
|
||||
showName.push(notHighlightStr);
|
||||
showName.push(<mark>{char}</mark>);
|
||||
|
@ -90,7 +90,7 @@ function Item(props: IItem) {
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) {
|
||||
|
@ -128,10 +128,11 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string
|
|||
currentCollapseIndentation = null;
|
||||
}
|
||||
}
|
||||
let id = item.id;
|
||||
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(
|
||||
<Item
|
||||
|
@ -146,7 +147,7 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string
|
|||
isSelect={id === selectItem}
|
||||
highlightValue={highlightValue}
|
||||
{...item} />
|
||||
)
|
||||
);
|
||||
}
|
||||
totalHeight = totalHeight + lineHeight;
|
||||
if (isCollapsed) {
|
||||
|
@ -155,19 +156,19 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string
|
|||
}
|
||||
});
|
||||
|
||||
const scroll = (event: any) => {
|
||||
const handleScroll = (event: any) => {
|
||||
const scrollTop = event.target.scrollTop;
|
||||
// 顶部留 100px 冗余高度
|
||||
setScrollTop(Math.max(scrollTop - 100, 0));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.treeContainer} onScroll={scroll}>
|
||||
<div className={styles.treeContainer} onScroll={handleScroll}>
|
||||
{showList}
|
||||
{/* 确保有足够的高度 */}
|
||||
<div style={{ marginTop: totalHeight }} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default VTree;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@arrow-color:rgb(95, 99, 104);
|
||||
@divider-color:rgb(202, 205, 209);
|
||||
@arrow-color: rgb(95, 99, 104);
|
||||
@divider-color: rgb(202, 205, 209);
|
||||
@attr-name-color: rgb(200, 0, 0);
|
||||
@component-name-color: rgb(136, 18, 128);
|
||||
@component-key-color: rgb(153, 69, 0);
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* 用一个纯数据类型的对象 tree 去表示树的结构是非常清晰的,但是它不能准确的模拟 VNode 中存在的引用
|
||||
* 关系,需要进行转换 getMockVNodeTree
|
||||
*/
|
||||
|
||||
import { parseAttr } from '../parser/parseAttr';
|
||||
import parseTreeRoot from '../parser/parseVNode';
|
||||
|
@ -9,7 +13,7 @@ const mockComponentNames = ['Apple', 'Pear', 'Banana', 'Orange', 'Jenny', 'Kiwi'
|
|||
function MockVNode(tag: string, props = {}, key = null, realNode = {}) {
|
||||
const vNode = new VNode(tag, props, key, realNode);
|
||||
const name = mockComponentNames.shift() || 'MockComponent';
|
||||
vNode.type = {name};
|
||||
vNode.type = { name };
|
||||
return vNode;
|
||||
}
|
||||
|
||||
|
@ -18,41 +22,49 @@ interface IMockTree {
|
|||
children?: IMockTree[],
|
||||
}
|
||||
|
||||
// 模拟树
|
||||
const tree: IMockTree = {
|
||||
tag: ClassComponent,
|
||||
children: [
|
||||
{tag: FunctionComponent},
|
||||
{tag: ClassComponent},
|
||||
{tag: FunctionComponent},
|
||||
{ tag: FunctionComponent },
|
||||
{ tag: ClassComponent },
|
||||
{ tag: FunctionComponent },
|
||||
{
|
||||
tag: FunctionComponent,
|
||||
children: [
|
||||
{tag: ClassComponent}
|
||||
{ tag: ClassComponent }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function addOneThousandNode(node: IMockTree) {
|
||||
const nodes = [];
|
||||
for(let i = 0; i < 1000; i++) {
|
||||
nodes.push({tag: FunctionComponent});
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
nodes.push({ tag: FunctionComponent });
|
||||
}
|
||||
node?.children.push({tag: ClassComponent,children: nodes});
|
||||
};
|
||||
node?.children.push({ tag: ClassComponent, children: nodes });
|
||||
}
|
||||
|
||||
addOneThousandNode(tree);
|
||||
|
||||
function getMockVNodeTree(tree: IMockTree, vNode: VNode) {
|
||||
const children = tree.children;
|
||||
/**
|
||||
* 将mock数据转变为 VNode 树
|
||||
*
|
||||
* @param node 树节点
|
||||
* @param vNode VNode节点
|
||||
*/
|
||||
function getMockVNodeTree(node: IMockTree, vNode: VNode) {
|
||||
const children = node.children;
|
||||
if (children && children.length !== 0) {
|
||||
const childNode = children[0];
|
||||
let childVNode = MockVNode(childNode.tag);
|
||||
childVNode.key = '0';
|
||||
getMockVNodeTree(childNode, childVNode);
|
||||
// 需要建立双链
|
||||
vNode.child = childVNode;
|
||||
childVNode.parent = vNode;
|
||||
for(let i = 1; i < children.length; i++) {
|
||||
for (let i = 1; i < children.length; i++) {
|
||||
const nextNode = children[i];
|
||||
const nextVNode = MockVNode(nextNode.tag);
|
||||
nextVNode.key = String(i);
|
||||
|
@ -68,19 +80,18 @@ getMockVNodeTree(tree, rootVNode);
|
|||
|
||||
export const mockParsedVNodeData = parseTreeRoot(rootVNode);
|
||||
|
||||
|
||||
const mockState = {
|
||||
str: 'jenny',
|
||||
num: 3,
|
||||
boolean: true,
|
||||
und: undefined,
|
||||
fun: () => {},
|
||||
fun: () => ({}),
|
||||
symbol: Symbol('sym'),
|
||||
map: new Map([['a', 'a']]),
|
||||
set: new Set(['a', 1, 2, Symbol('bambi')]),
|
||||
arr: [1,2,3,4],
|
||||
arr: [1, 2, 3, 4],
|
||||
obj: {
|
||||
niko: {jenny: 'jenny'}
|
||||
niko: { jenny: 'jenny' }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,47 +1,52 @@
|
|||
@import '../components/assets.less';
|
||||
|
||||
.app{
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
font-size: @common-font-size;
|
||||
}
|
||||
|
||||
.left{
|
||||
.left {
|
||||
flex: 7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.left_top {
|
||||
border-bottom: @divider-style;
|
||||
flex: 0 0 @top-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.select {
|
||||
padding: 0 0.25rem 0 0.25rem;
|
||||
flex: 0 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
flex: 0 0 1px;
|
||||
margin: 0 0.25rem 0 0.25rem;
|
||||
border-left: @divider-style;
|
||||
height: calc(100% - 1rem);
|
||||
}
|
||||
|
||||
.search {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
}
|
||||
|
||||
.left_bottom {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.right{
|
||||
.right {
|
||||
flex: 3;
|
||||
border-left: @divider-style;
|
||||
}
|
||||
|
||||
input{
|
||||
input {
|
||||
outline: none;
|
||||
border-width: 0;
|
||||
padding: 0;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {useState, useEffect} from 'horizon';
|
||||
import { useState, useEffect } from 'horizon';
|
||||
import VTree, { IData } from '../components/VTree';
|
||||
import Search from '../components/Search';
|
||||
import ComponentInfo from '../components/ComponentInfo';
|
||||
|
@ -8,7 +8,7 @@ import { mockParsedVNodeData, parsedMockState } from '../devtools/mock';
|
|||
|
||||
function App() {
|
||||
const [parsedVNodeData, setParsedVNodeData] = useState([]);
|
||||
const [componentInfo, setComponentInfo] = useState({name: null, attrs: {}});
|
||||
const [componentInfo, setComponentInfo] = useState({ name: null, attrs: {} });
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
useEffect(() => {
|
||||
if (isDev) {
|
||||
|
@ -19,7 +19,7 @@ function App() {
|
|||
state: parsedMockState,
|
||||
props: parsedMockState,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
const idIndentationMap: {
|
||||
|
@ -27,7 +27,7 @@ function App() {
|
|||
} = {};
|
||||
const data: IData[] = [];
|
||||
let i = 0;
|
||||
while(i < parsedVNodeData.length) {
|
||||
while (i < parsedVNodeData.length) {
|
||||
const id = parsedVNodeData[i] as string;
|
||||
i++;
|
||||
const name = parsedVNodeData[i] as string;
|
||||
|
@ -46,7 +46,7 @@ function App() {
|
|||
|
||||
const handleSearchChange = (str: string) => {
|
||||
setFilterValue(str);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
|
@ -57,15 +57,15 @@ function App() {
|
|||
</div>
|
||||
<div className={styles.divider} />
|
||||
<div className={styles.search}>
|
||||
<Search onChange={handleSearchChange}/>
|
||||
<Search onChange={handleSearchChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.left_bottom}>
|
||||
<VTree data={data} highlightValue={filterValue}/>
|
||||
<VTree data={data} highlightValue={filterValue} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<ComponentInfo name={componentInfo.name} attrs={componentInfo.attrs}/>
|
||||
<ComponentInfo name={componentInfo.name} attrs={componentInfo.attrs} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {render} from 'horizon';
|
||||
import { render } from 'horizon';
|
||||
import App from './App';
|
||||
|
||||
render(
|
||||
<App />,
|
||||
<App />,
|
||||
document.getElementById('root')
|
||||
);
|
|
@ -1,27 +1,34 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<script src='horizon.production.js'></script>
|
||||
<style>
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Horizon</title>
|
||||
<script src='horizon.production.js'></script>
|
||||
<style>
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
|
||||
// 将状态的值解析成固定格式
|
||||
export function parseAttr(rootAttr: any) {
|
||||
const result = [];
|
||||
let indentation = 0;
|
||||
const result: {
|
||||
name: string,
|
||||
type: string,
|
||||
value: string,
|
||||
indentation: number
|
||||
}[] = [];
|
||||
const indentation = 0;
|
||||
const parseSubAttr = (attr: any, parentIndentation: number, attrName: string) => {
|
||||
const stateType = typeof attr;
|
||||
let value: any;
|
||||
|
@ -20,7 +27,7 @@ export function parseAttr(rootAttr: any) {
|
|||
} else if (stateType === 'object') {
|
||||
if (attr === null) {
|
||||
showType = 'null';
|
||||
}else if (attr instanceof Map) {
|
||||
} else if (attr instanceof Map) {
|
||||
showType = 'map';
|
||||
const size = attr.size;
|
||||
value = `Map(${size})`;
|
||||
|
@ -28,7 +35,7 @@ export function parseAttr(rootAttr: any) {
|
|||
attr.forEach((value, key) => {
|
||||
parseSubAttr(value, parentIndentation + 2, key);
|
||||
});
|
||||
}
|
||||
};
|
||||
} else if (attr instanceof Set) {
|
||||
showType = 'set';
|
||||
const size = attr.size;
|
||||
|
@ -46,8 +53,8 @@ export function parseAttr(rootAttr: any) {
|
|||
addSubState = () => {
|
||||
attr.forEach((value, index) => {
|
||||
parseSubAttr(value, parentIndentation + 2, String(index));
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
} else {
|
||||
showType = stateType;
|
||||
value = '{...}';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { travelVNodeTree } from "../../../../libs/horizon/src/renderer/vnode/VNodeUtils";
|
||||
import { VNode } from "../../../../libs/horizon/src/renderer/Types";
|
||||
import { ClassComponent, FunctionComponent } from "../../../../libs/horizon/src/renderer/vnode/VNodeTags";
|
||||
import { travelVNodeTree } from '../../../../libs/horizon/src/renderer/vnode/VNodeUtils';
|
||||
import { VNode } from '../../../../libs/horizon/src/renderer/Types';
|
||||
import { ClassComponent, FunctionComponent } from '../../../../libs/horizon/src/renderer/vnode/VNodeTags';
|
||||
|
||||
// 建立双向映射关系,当用户在修改属性值后,可以找到对应的 VNode
|
||||
const VNodeToIdMap = new Map<VNode, number>();
|
||||
const IdToVNodeMap = new Map<number, VNode>();
|
||||
|
||||
|
@ -34,7 +35,7 @@ function parseTreeRoot(treeRoot: VNode) {
|
|||
if (isUserComponent(tag)) {
|
||||
const id = generateUid();
|
||||
result.push(id);
|
||||
const name = (node.type as Function).name;
|
||||
const name = node.type.name;
|
||||
result.push(name);
|
||||
const parent = getParentUserComponent(node);
|
||||
if (parent) {
|
||||
|
|
|
@ -2,16 +2,16 @@ interface IArrow {
|
|||
director: 'right' | 'down'
|
||||
}
|
||||
|
||||
export default function Arrow({director}: IArrow) {
|
||||
export default function Arrow({ director }: IArrow) {
|
||||
let d: string;
|
||||
if (director === 'right') {
|
||||
d = 'm2 0l12 8l-12 8 z'
|
||||
d = 'm2 0l12 8l-12 8 z';
|
||||
} else if (director === 'down') {
|
||||
d = 'm0 2h16 l-8 12 z';
|
||||
}
|
||||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='8px' height='8px'>
|
||||
<path d={d} fill='currentColor'/>
|
||||
<path d={d} fill='currentColor' />
|
||||
</svg>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
export default function Copy() {
|
||||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='1rem' height='1rem'>
|
||||
<path d='M0 0 H16 V16 H0 z M2 2 H8 V8 H2 V2z' fill='currentColor' fill-rule='evenodd'/>
|
||||
<path d='M0 0 H16 V16 H0 z M2 2 H8 V8 H2 V2z' fill='currentColor' fill-rule='evenodd' />
|
||||
</svg>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@ export default function Debug() {
|
|||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='1rem' height='1rem'>
|
||||
<path d='m2 0l12 8l-12 8 z' fill='#000'/>
|
||||
</svg>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ export default function Eye() {
|
|||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='1rem' height='1rem'>
|
||||
<ellipse cx="8" cy="8" rx="8" ry="6" />
|
||||
<circle cx="8" cy="8" r= "4" fill="rgb(255, 255, 255)"/>
|
||||
<circle cx="8" cy="8" r= "2" fill="#000000"/>
|
||||
<circle cx="8" cy="8" r="4" fill="rgb(255, 255, 255)" />
|
||||
<circle cx="8" cy="8" r="2" fill="#000000" />
|
||||
</svg>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
|
||||
export default function Select() {
|
||||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='1rem' height='1rem'>
|
||||
<path d='M14 6 V3 C14 2.5 13.5 2 13 2 H3 C2.5 2 2 2.5 2 3 V13 C2 13.5 2.5 14 3 14H6 V13 H3 V3 H13 V6z M7 7 L9 15 L11 12 L14 15 L15 14 L12 11 L15 9z' fill='#000' />
|
||||
</svg>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
export function createRegExp(expression: string){
|
||||
export function createRegExp(expression: string) {
|
||||
let str = expression;
|
||||
if (str[0] === '/') {
|
||||
str = str.slice(1);
|
||||
|
@ -9,7 +9,7 @@ export function createRegExp(expression: string){
|
|||
}
|
||||
try {
|
||||
return new RegExp(str, 'i');
|
||||
}catch(err) {
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -30,8 +30,8 @@ module.exports = {
|
|||
'@babel/preset-typescript',
|
||||
['@babel/preset-react', {
|
||||
runtime: 'classic',
|
||||
"pragma": "Horizon.createElement",
|
||||
"pragmaFrag": "Horizon.Fragment",
|
||||
'pragma': 'Horizon.createElement',
|
||||
'pragmaFrag': 'Horizon.Fragment',
|
||||
}]],
|
||||
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ module.exports = {
|
|||
{
|
||||
test: /\.less/i,
|
||||
use: [
|
||||
"style-loader",
|
||||
'style-loader',
|
||||
{
|
||||
loader: "css-loader",
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
|
||||
|
|
Loading…
Reference in New Issue