Match-id-7133f2734eacafd1263023fbe3921097f915d497

This commit is contained in:
* 2022-03-31 10:25:52 +08:00 committed by *
parent 5cde85df95
commit e13f3245bd
21 changed files with 155 additions and 122 deletions

View File

@ -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>
)
);
}

View File

@ -68,6 +68,7 @@
.attrDetail {
padding-bottom: 0.5rem;
.attrArrow {
color: @arrow-color;
width: 12px;

View File

@ -1,3 +1,3 @@
.search {
width: 100%;
}
}

View File

@ -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/)'}
/>
)
);
}

View File

@ -5,10 +5,12 @@
width: 100%;
height: 100%;
overflow-y: auto;
.treeItem {
width: 100%;
position: absolute;
line-height: 18px;
&:hover {
background-color: @select-color;
}
@ -19,15 +21,15 @@
width: 12px;
padding-left: 0.5rem;
}
.componentName {
color: @component-name-color;
}
.componentKeyName {
color: @component-key-color;
}
.componentKeyValue {
color: @componentKeyValue-color;
}
@ -37,4 +39,3 @@
background-color: rgb(141 199 248 / 60%);
}
}

View File

@ -26,14 +26,14 @@ const lineHeight = 18;
const indentationLength = 20;
function Item(props: IItem) {
const {
name,
style,
const {
name,
style,
userKey,
hasChild,
onCollapse,
isCollapsed,
id,
hasChild,
onCollapse,
isCollapsed,
id,
indentation,
onClick,
isSelect,
@ -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;

View File

@ -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);
@ -11,4 +11,4 @@
@divider-width: 0.2px;
@common-font-size: 12px;
@divider-style: @divider-color solid @divider-width;
@divider-style: @divider-color solid @divider-width;

View File

@ -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,20 +80,19 @@ 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' }
}
};
export const parsedMockState = parseAttr(mockState);
export const parsedMockState = parseAttr(mockState);

View File

@ -1,48 +1,53 @@
@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;
flex: 1;
height: 0;
}
}
.right{
.right {
flex: 3;
border-left: @divider-style;
}
input{
input {
outline: none;
border-width: 0;
padding: 0;
}
}

View File

@ -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>
);

View File

@ -1,7 +1,7 @@
import {render} from 'horizon';
import { render } from 'horizon';
import App from './App';
render(
<App />,
<App />,
document.getElementById('root')
);

View File

@ -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>

View File

@ -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 = '{...}';

View File

@ -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) {

View File

@ -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>
)
);
}

View File

@ -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>
)
);
}

View File

@ -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>
)
);
}

View File

@ -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>
)
);
}

View File

@ -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>
)
);
}

View File

@ -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;
}
}

View File

@ -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,14 +41,14 @@ module.exports = {
{
test: /\.less/i,
use: [
"style-loader",
{
loader: "css-loader",
options: {
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
}
},
}
},
'less-loader'],
}]
},