Match-id-e56b18c31b44e4091ce6cf98af6502f369a02364

This commit is contained in:
* 2022-04-25 19:55:21 +08:00 committed by *
parent 0237a0b72c
commit 58d5869a58
6 changed files with 165 additions and 102 deletions

View File

@ -1,6 +1,6 @@
import { checkMessage, packagePayload, changeSource } from '../utils/transferTool'; import { checkMessage, packagePayload, changeSource } from '../utils/transferTool';
import { RequestAllVNodeTreeInfos, InitDevToolPageConnection, DevToolBackground } from '../utils/constants'; import { RequestAllVNodeTreeInfos, InitDevToolPageConnection, DevToolBackground } from '../utils/constants';
import { DevToolPanel, DevToolContentScript } from './../utils/constants'; import { DevToolPanel, DevToolContentScript } from '../utils/constants';
// 多个页面、tab页共享一个 background需要建立连接池给每个tab建立连接 // 多个页面、tab页共享一个 background需要建立连接池给每个tab建立连接
const connections = {}; const connections = {};

View File

@ -5,17 +5,19 @@ import Copy from '../svgs/Copy';
import Triangle from '../svgs/Triangle'; import Triangle from '../svgs/Triangle';
import { useState, useEffect } from 'horizon'; import { useState, useEffect } from 'horizon';
import { IData } from './VTree'; import { IData } from './VTree';
import { IAttr } from '../parser/parseAttr'; import { buildAttrModifyData, IAttr } from '../parser/parseAttr';
import { postMessageToBackground } from '../panelConnection';
import { ModifyAttrs } from '../utils/constants';
type IComponentInfo = { type IComponentInfo = {
name: string; name: string;
attrs: { attrs: {
props?: IAttr[]; parsedProps?: IAttr[],
context?: IAttr[]; parsedState?: IAttr[],
state?: IAttr[]; parsedHooks?: IAttr[],
hooks?: IAttr[];
}; };
parents: IData[]; parents: IData[];
id: number;
onClickParent: (item: IData) => void; onClickParent: (item: IData) => void;
}; };
@ -26,11 +28,18 @@ function collapseAllNodes(attrs: IAttr[]) {
}); });
} }
function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) { function ComponentAttr({ attrsName, attrsType, attrs, id }: {
attrsName: string,
attrsType: string,
attrs: IAttr[],
id: number}) {
const [collapsedNode, setCollapsedNode] = useState(collapseAllNodes(attrs)); const [collapsedNode, setCollapsedNode] = useState(collapseAllNodes(attrs));
const [editableAttrs, setEditableAttrs] = useState(attrs);
useEffect(() => { useEffect(() => {
setCollapsedNode(collapseAllNodes(attrs)); setCollapsedNode(collapseAllNodes(attrs));
setEditableAttrs(attrs);
}, [attrs]); }, [attrs]);
const handleCollapse = (item: IAttr) => { const handleCollapse = (item: IAttr) => {
const nodes = [...collapsedNode]; const nodes = [...collapsedNode];
const i = nodes.indexOf(item); const i = nodes.indexOf(item);
@ -44,7 +53,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
const showAttr = []; const showAttr = [];
let currentIndentation = null; let currentIndentation = null;
attrs.forEach((item, index) => { editableAttrs.forEach((item, index) => {
const indentation = item.indentation; const indentation = item.indentation;
if (currentIndentation !== null) { if (currentIndentation !== null) {
if (indentation > currentIndentation) { if (indentation > currentIndentation) {
@ -53,17 +62,40 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
currentIndentation = null; currentIndentation = null;
} }
} }
const nextItem = attrs[index + 1]; const nextItem = editableAttrs[index + 1];
const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false; const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false;
const isCollapsed = collapsedNode.includes(item); const isCollapsed = collapsedNode.includes(item);
showAttr.push( showAttr.push(
<div style={{ paddingLeft: item.indentation * 10 }} key={index} onClick={() => (handleCollapse(item))}> <div style={{ paddingLeft: item.indentation * 10 }} key={index} onClick={() => handleCollapse(item)}>
<span className={styles.attrArrow}>{hasChild && <Triangle director={isCollapsed ? 'right' : 'down'} />}</span> <span className={styles.attrArrow}>{hasChild && <Triangle director={isCollapsed ? 'right' : 'down'} />}</span>
<span className={styles.attrName}>{`${item.name}`}</span> <span className={styles.attrName}>{`${item.name}`}</span>
{' :'} {' :'}
{item.type === 'string' || item.type === 'number' {item.type === 'string' || item.type === 'number' ? (
? <input value={item.value} className={styles.attrValue}>{item.value}</input> <input
: <span className={styles.attrValue}>{item.value}</span>} value={item.value}
className={styles.attrValue}
onChange={(event) => {
const nextAttrs = [...editableAttrs];
const nextItem = {...item};
nextItem.value = event.target.value;
nextAttrs[index] = nextItem;
setEditableAttrs(nextAttrs);
}}
onKeyUp={(event) => {
const value = (event.target as HTMLInputElement).value;
if (event.key === 'Enter') {
if(isDev) {
console.log('post attr change', value);
} else {
const data = buildAttrModifyData(attrsType,attrs, value,item, index, id);
postMessageToBackground(ModifyAttrs, data);
}
}
}}
/>
) : (
<span className={styles.attrValue}>{item.value}</span>
)}
</div> </div>
); );
if (isCollapsed) { if (isCollapsed) {
@ -74,7 +106,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
return ( return (
<div className={styles.attrContainer}> <div className={styles.attrContainer}>
<div className={styles.attrHead}> <div className={styles.attrHead}>
<span className={styles.attrType}>{name}</span> <span className={styles.attrType}>{attrsName}</span>
<span className={styles.attrCopy}> <span className={styles.attrCopy}>
<Copy /> <Copy />
</span> </span>
@ -86,8 +118,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
); );
} }
export default function ComponentInfo({ name, attrs, parents, onClickParent }: IComponentInfo) { export default function ComponentInfo({ name, attrs, parents, id, onClickParent }: IComponentInfo) {
const { state, props, context, hooks } = attrs;
return ( return (
<div className={styles.infoContainer} > <div className={styles.infoContainer} >
<div className={styles.componentInfoHead}> <div className={styles.componentInfoHead}>
@ -104,10 +135,14 @@ export default function ComponentInfo({ name, attrs, parents, onClickParent }: I
</>} </>}
</div> </div>
<div className={styles.componentInfoMain}> <div className={styles.componentInfoMain}>
{context && <ComponentAttr name={'context'} attrs={context} />} {Object.keys(attrs).map(attrsType => {
{props && props.length !== 0 && <ComponentAttr name={'props'} attrs={props} />} const parsedAttrs = attrs[attrsType];
{state && state.length !== 0 && <ComponentAttr name={'state'} attrs={state} />} if (parsedAttrs && parsedAttrs.length !== 0) {
{hooks && hooks.length !== 0 && <ComponentAttr name={'hook'} attrs={hooks} />} const attrsName = attrsType.slice(6); // parsedState => State
return <ComponentAttr attrsName={attrsName} attrs={parsedAttrs} id={id} attrsType={attrsType}/>;
}
return null;
})}
<div className={styles.parentsInfo}> <div className={styles.parentsInfo}>
{name && <div> {name && <div>
parents: { parents: {
@ -122,4 +157,4 @@ export default function ComponentInfo({ name, attrs, parents, onClickParent }: I
</div> </div>
</div> </div>
); );
} }

View File

@ -1,5 +1,5 @@
import parseTreeRoot, { clearVNode, queryVNode } from '../parser/parseVNode'; import parseTreeRoot, { clearVNode, queryVNode } from '../parser/parseVNode';
import { packagePayload, checkMessage } from './../utils/transferTool'; import { packagePayload, checkMessage } from '../utils/transferTool';
import { import {
RequestAllVNodeTreeInfos, RequestAllVNodeTreeInfos,
AllVNodeTreesInfos, AllVNodeTreesInfos,
@ -7,11 +7,9 @@ import {
ComponentAttrs, ComponentAttrs,
DevToolHook, DevToolHook,
DevToolContentScript DevToolContentScript
} from './../utils/constants'; } from '../utils/constants';
import { VNode } from './../../../horizon/src/renderer/vnode/VNode'; import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
import { ClassComponent } from '../../../horizon/src/renderer/vnode/VNodeTags'; import { parseVNodeAttrs } from '../parser/parseAttr';
import { parseAttr, parseHooks } from '../parser/parseAttr';
import { FunctionComponent } from './../../../horizon/src/renderer/vnode/VNodeTags';
const roots = []; const roots = [];
@ -48,24 +46,8 @@ function postMessage(type: string, data) {
function parseCompAttrs(id: number) { function parseCompAttrs(id: number) {
const vNode: VNode = queryVNode(id); const vNode: VNode = queryVNode(id);
const tag = vNode.tag; const parsedAttrs = parseVNodeAttrs(vNode);
if (tag === ClassComponent) { postMessage(ComponentAttrs, parsedAttrs);
const { props, state } = vNode;
const parsedProps = parseAttr(props);
const parsedState = parseAttr(state);
postMessage(ComponentAttrs, {
parsedProps,
parsedState,
});
} else if (tag === FunctionComponent) {
const { props, hooks } = vNode;
const parsedProps = parseAttr(props);
const parsedHooks = parseHooks(hooks);
postMessage(ComponentAttrs, {
parsedProps,
parsedHooks,
});
}
} }
function injectHook() { function injectHook() {

View File

@ -9,13 +9,16 @@ import { FilterTree } from '../hooks/FilterTree';
import Close from '../svgs/Close'; import Close from '../svgs/Close';
import Arrow from './../svgs/Arrow'; import Arrow from './../svgs/Arrow';
import { import {
InitDevToolPageConnection,
AllVNodeTreesInfos, AllVNodeTreesInfos,
RequestComponentAttrs, RequestComponentAttrs,
ComponentAttrs, ComponentAttrs,
DevToolPanel, } from '../utils/constants';
} from './../utils/constants'; import {
import { packagePayload } from './../utils/transferTool'; addBackgroundMessageListener,
initBackgroundConnection,
postMessageToBackground, removeBackgroundMessageListener,
} from '../panelConnection';
import { IAttr } from '../parser/parseAttr';
const parseVNodeData = (rawData) => { const parseVNodeData = (rawData) => {
const idIndentationMap: { const idIndentationMap: {
@ -59,45 +62,13 @@ const getParents = (item: IData | null, parsedVNodeData: IData[]) => {
return parents; return parents;
}; };
let connection;
if (!isDev) {
// 与 background 的唯一连接
connection = chrome.runtime.connect({
name: 'panel'
});
}
let reconnectTimes = 0;
function postMessage(type: string, data: any) {
try {
connection.postMessage(packagePayload({
type: type,
data: data,
}, DevToolPanel));
} catch(err) {
// 可能出现 port 关闭的场景,需要重新建立连接,增加可靠性
if (reconnectTimes === 20) {
reconnectTimes = 0;
console.error('reconnect failed');
return;
}
console.error(err);
reconnectTimes++;
// 重建连接
connection = chrome.runtime.connect({
name: 'panel'
});
// 重新发送初始化消息
postMessage(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId);
// 初始化成功后才会重新发送消息
postMessage(type, data);
}
}
function App() { function App() {
const [parsedVNodeData, setParsedVNodeData] = useState([]); const [parsedVNodeData, setParsedVNodeData] = useState([]);
const [componentAttrs, setComponentAttrs] = useState({}); const [componentAttrs, setComponentAttrs] = useState<{
parsedProps?: IAttr[],
parsedState?: IAttr[],
parsedHooks?: IAttr[],
}>();
const [selectComp, setSelectComp] = useState(null); const [selectComp, setSelectComp] = useState(null);
const treeRootInfos = useRef<{id: number, length: number}[]>([]); // 记录保存的根节点 id 和长度, const treeRootInfos = useRef<{id: number, length: number}[]>([]); // 记录保存的根节点 id 和长度,
@ -119,14 +90,11 @@ function App() {
const parsedData = parseVNodeData(mockParsedVNodeData); const parsedData = parseVNodeData(mockParsedVNodeData);
setParsedVNodeData(parsedData); setParsedVNodeData(parsedData);
setComponentAttrs({ setComponentAttrs({
state: parsedMockState, parsedProps: parsedMockState,
props: parsedMockState, parsedState: parsedMockState,
}); });
} else { } else {
// 页面打开后发送初始化请求 const handleBackgroundMessage = (message) => {
postMessage(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId);
// 监听 background消息
connection.onMessage.addListener(function (message) {
const { payload } = message; const { payload } = message;
if (payload) { if (payload) {
const { type, data } = payload; const { type, data } = payload;
@ -145,13 +113,21 @@ function App() {
} else if (type === ComponentAttrs) { } else if (type === ComponentAttrs) {
const {parsedProps, parsedState, parsedHooks} = data; const {parsedProps, parsedState, parsedHooks} = data;
setComponentAttrs({ setComponentAttrs({
props: parsedProps, parsedProps,
state: parsedState, parsedState,
hooks: parsedHooks, parsedHooks,
}); });
} }
} }
}); };
console.log('handle connection');
// 在页面渲染后初始化连接
initBackgroundConnection();
// 监听 background消息
addBackgroundMessageListener(handleBackgroundMessage);
return () => {
removeBackgroundMessageListener(handleBackgroundMessage);
};
} }
}, []); }, []);
@ -162,11 +138,11 @@ function App() {
const handleSelectComp = (item: IData) => { const handleSelectComp = (item: IData) => {
if (isDev) { if (isDev) {
setComponentAttrs({ setComponentAttrs({
state: parsedMockState, parsedProps: parsedMockState,
props: parsedMockState, parsedState: parsedMockState,
}); });
} else { } else {
postMessage(RequestComponentAttrs, item.id); postMessageToBackground(RequestComponentAttrs, item.id);
} }
setSelectComp(item); setSelectComp(item);
}; };
@ -216,6 +192,7 @@ function App() {
name={selectComp ? selectComp.name : null} name={selectComp ? selectComp.name : null}
attrs={selectComp ? componentAttrs : {}} attrs={selectComp ? componentAttrs : {}}
parents={parents} parents={parents}
id={selectComp ? selectComp.id : null}
onClickParent={handleClickParent} /> onClickParent={handleClickParent} />
</div> </div>
</div> </div>

View File

@ -1,5 +1,8 @@
import { Hook, Reducer, Ref } from './../../../horizon/src/renderer/hooks/HookType'; import { Hook, Reducer, Ref } from '../../../horizon/src/renderer/hooks/HookType';
import { ModifyHooks, ModifyProps, ModifyState } from '../utils/constants';
import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
import { ClassComponent, FunctionComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
// 展示值为 string 的可编辑类型 // 展示值为 string 的可编辑类型
type editableStringType = 'string' | 'number' | 'undefined' | 'null'; type editableStringType = 'string' | 'number' | 'undefined' | 'null';
@ -12,7 +15,7 @@ type showAsStringType = editableStringType | unEditableStringType;
export type IAttr = { export type IAttr = {
name: string; name: string | number;
indentation: number; indentation: number;
hIndex?: number; // 用于记录 hook 的 hIndex 值 hIndex?: number; // 用于记录 hook 的 hIndex 值
} & ({ } & ({
@ -131,3 +134,61 @@ export function parseHooks(hooks: Hook<any, any>[]) {
}); });
return result; return result;
} }
export function parseVNodeAttrs(vNode: VNode) {
const tag = vNode.tag;
if (tag === ClassComponent) {
const { props, state } = vNode;
const parsedProps = parseAttr(props);
const parsedState = parseAttr(state);
return {
parsedProps,
parsedState,
};
} else if (tag === FunctionComponent) {
const { props, hooks } = vNode;
const parsedProps = parseAttr(props);
const parsedHooks = parseHooks(hooks);
return {
parsedProps,
parsedHooks,
};
}
}
// 计算属性的访问顺序
function calculateAttrAccessPath(item: IAttr, index: number, attrs: IAttr[]) {
let currentIndentation = item.indentation;
const path = [item.name];
for(let i = index - 1; i >= 0; i--) {
const lastItem = attrs[i];
const lastIndentation = lastItem.indentation;
if (lastIndentation < currentIndentation) {
path.push(lastItem.name);
currentIndentation = lastIndentation;
}
}
path.reverse();
return path;
}
export function buildAttrModifyData(parsedAttrsType: string, attrs: IAttr[], value, item: IAttr, index: number, id: number) {
const path = calculateAttrAccessPath(item, index, attrs);
let type;
if (parsedAttrsType === 'parsedProps') {
type = ModifyProps;
} else if (parsedAttrsType === 'parsedState') {
type = ModifyState;
path[0] = item.hIndex;
} else if (parsedAttrsType === 'parsedHooks') {
type = ModifyHooks;
} else {
return null;
}
return {
id: id,
type: type,
value: value,
path: path,
};
}

View File

@ -11,6 +11,14 @@ export const RequestComponentAttrs = 'get component attrs';
// 返回组件属性 // 返回组件属性
export const ComponentAttrs = 'component attrs'; export const ComponentAttrs = 'component attrs';
export const ModifyAttrs = 'modify attrs';
export const ModifyProps = 'modify props';
export const ModifyState = 'modify state';
export const ModifyHooks = 'modify hooks';
// 传递消息来源标志 // 传递消息来源标志
export const DevToolPanel = 'dev tool panel'; export const DevToolPanel = 'dev tool panel';
@ -19,4 +27,4 @@ export const DevToolBackground = 'dev tool background';
export const DevToolContentScript = 'dev tool content script'; export const DevToolContentScript = 'dev tool content script';
export const DevToolHook = 'dev tool hook'; export const DevToolHook = 'dev tool hook';