diff --git a/libs/extension/src/background/index.ts b/libs/extension/src/background/index.ts
index ed9698fb..cc8bd778 100644
--- a/libs/extension/src/background/index.ts
+++ b/libs/extension/src/background/index.ts
@@ -1,6 +1,6 @@
import { checkMessage, packagePayload, changeSource } from '../utils/transferTool';
import { RequestAllVNodeTreeInfos, InitDevToolPageConnection, DevToolBackground } from '../utils/constants';
-import { DevToolPanel, DevToolContentScript } from './../utils/constants';
+import { DevToolPanel, DevToolContentScript } from '../utils/constants';
// 多个页面、tab页共享一个 background,需要建立连接池,给每个tab建立连接
const connections = {};
diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx
index f5ff0051..328642bd 100644
--- a/libs/extension/src/components/ComponentInfo.tsx
+++ b/libs/extension/src/components/ComponentInfo.tsx
@@ -5,17 +5,19 @@ import Copy from '../svgs/Copy';
import Triangle from '../svgs/Triangle';
import { useState, useEffect } from 'horizon';
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 = {
name: string;
attrs: {
- props?: IAttr[];
- context?: IAttr[];
- state?: IAttr[];
- hooks?: IAttr[];
+ parsedProps?: IAttr[],
+ parsedState?: IAttr[],
+ parsedHooks?: IAttr[],
};
parents: IData[];
+ id: number;
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 [editableAttrs, setEditableAttrs] = useState(attrs);
useEffect(() => {
setCollapsedNode(collapseAllNodes(attrs));
+ setEditableAttrs(attrs);
}, [attrs]);
+
const handleCollapse = (item: IAttr) => {
const nodes = [...collapsedNode];
const i = nodes.indexOf(item);
@@ -44,7 +53,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
const showAttr = [];
let currentIndentation = null;
- attrs.forEach((item, index) => {
+ editableAttrs.forEach((item, index) => {
const indentation = item.indentation;
if (currentIndentation !== null) {
if (indentation > currentIndentation) {
@@ -53,17 +62,40 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
currentIndentation = null;
}
}
- const nextItem = attrs[index + 1];
+ const nextItem = editableAttrs[index + 1];
const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false;
const isCollapsed = collapsedNode.includes(item);
showAttr.push(
-
(handleCollapse(item))}>
+
handleCollapse(item)}>
{hasChild && }
{`${item.name}`}
{' :'}
- {item.type === 'string' || item.type === 'number'
- ? {item.value}
- : {item.value}}
+ {item.type === 'string' || item.type === 'number' ? (
+ {
+ 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);
+ }
+ }
+ }}
+ />
+ ) : (
+ {item.value}
+ )}
);
if (isCollapsed) {
@@ -74,7 +106,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
return (
-
{name}
+
{attrsName}
@@ -86,8 +118,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
);
}
-export default function ComponentInfo({ name, attrs, parents, onClickParent }: IComponentInfo) {
- const { state, props, context, hooks } = attrs;
+export default function ComponentInfo({ name, attrs, parents, id, onClickParent }: IComponentInfo) {
return (
@@ -104,10 +135,14 @@ export default function ComponentInfo({ name, attrs, parents, onClickParent }: I
>}
- {context &&
}
- {props && props.length !== 0 &&
}
- {state && state.length !== 0 &&
}
- {hooks && hooks.length !== 0 &&
}
+ {Object.keys(attrs).map(attrsType => {
+ const parsedAttrs = attrs[attrsType];
+ if (parsedAttrs && parsedAttrs.length !== 0) {
+ const attrsName = attrsType.slice(6); // parsedState => State
+ return
;
+ }
+ return null;
+ })}
{name &&
parents: {
@@ -122,4 +157,4 @@ export default function ComponentInfo({ name, attrs, parents, onClickParent }: I
);
-}
\ No newline at end of file
+}
diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts
index e0372d10..17725af9 100644
--- a/libs/extension/src/injector/index.ts
+++ b/libs/extension/src/injector/index.ts
@@ -1,5 +1,5 @@
import parseTreeRoot, { clearVNode, queryVNode } from '../parser/parseVNode';
-import { packagePayload, checkMessage } from './../utils/transferTool';
+import { packagePayload, checkMessage } from '../utils/transferTool';
import {
RequestAllVNodeTreeInfos,
AllVNodeTreesInfos,
@@ -7,11 +7,9 @@ import {
ComponentAttrs,
DevToolHook,
DevToolContentScript
-} from './../utils/constants';
-import { VNode } from './../../../horizon/src/renderer/vnode/VNode';
-import { ClassComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
-import { parseAttr, parseHooks } from '../parser/parseAttr';
-import { FunctionComponent } from './../../../horizon/src/renderer/vnode/VNodeTags';
+} from '../utils/constants';
+import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
+import { parseVNodeAttrs } from '../parser/parseAttr';
const roots = [];
@@ -48,24 +46,8 @@ function postMessage(type: string, data) {
function parseCompAttrs(id: number) {
const vNode: VNode = queryVNode(id);
- const tag = vNode.tag;
- if (tag === ClassComponent) {
- 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,
- });
- }
+ const parsedAttrs = parseVNodeAttrs(vNode);
+ postMessage(ComponentAttrs, parsedAttrs);
}
function injectHook() {
diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx
index af6e5d9a..993c3a66 100644
--- a/libs/extension/src/panel/App.tsx
+++ b/libs/extension/src/panel/App.tsx
@@ -9,13 +9,16 @@ import { FilterTree } from '../hooks/FilterTree';
import Close from '../svgs/Close';
import Arrow from './../svgs/Arrow';
import {
- InitDevToolPageConnection,
AllVNodeTreesInfos,
RequestComponentAttrs,
ComponentAttrs,
- DevToolPanel,
-} from './../utils/constants';
-import { packagePayload } from './../utils/transferTool';
+} from '../utils/constants';
+import {
+ addBackgroundMessageListener,
+ initBackgroundConnection,
+ postMessageToBackground, removeBackgroundMessageListener,
+} from '../panelConnection';
+import { IAttr } from '../parser/parseAttr';
const parseVNodeData = (rawData) => {
const idIndentationMap: {
@@ -59,45 +62,13 @@ const getParents = (item: IData | null, parsedVNodeData: IData[]) => {
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() {
const [parsedVNodeData, setParsedVNodeData] = useState([]);
- const [componentAttrs, setComponentAttrs] = useState({});
+ const [componentAttrs, setComponentAttrs] = useState<{
+ parsedProps?: IAttr[],
+ parsedState?: IAttr[],
+ parsedHooks?: IAttr[],
+ }>();
const [selectComp, setSelectComp] = useState(null);
const treeRootInfos = useRef<{id: number, length: number}[]>([]); // 记录保存的根节点 id 和长度,
@@ -119,14 +90,11 @@ function App() {
const parsedData = parseVNodeData(mockParsedVNodeData);
setParsedVNodeData(parsedData);
setComponentAttrs({
- state: parsedMockState,
- props: parsedMockState,
+ parsedProps: parsedMockState,
+ parsedState: parsedMockState,
});
} else {
- // 页面打开后发送初始化请求
- postMessage(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId);
- // 监听 background消息
- connection.onMessage.addListener(function (message) {
+ const handleBackgroundMessage = (message) => {
const { payload } = message;
if (payload) {
const { type, data } = payload;
@@ -145,13 +113,21 @@ function App() {
} else if (type === ComponentAttrs) {
const {parsedProps, parsedState, parsedHooks} = data;
setComponentAttrs({
- props: parsedProps,
- state: parsedState,
- hooks: parsedHooks,
+ parsedProps,
+ parsedState,
+ parsedHooks,
});
}
}
- });
+ };
+ console.log('handle connection');
+ // 在页面渲染后初始化连接
+ initBackgroundConnection();
+ // 监听 background消息
+ addBackgroundMessageListener(handleBackgroundMessage);
+ return () => {
+ removeBackgroundMessageListener(handleBackgroundMessage);
+ };
}
}, []);
@@ -162,11 +138,11 @@ function App() {
const handleSelectComp = (item: IData) => {
if (isDev) {
setComponentAttrs({
- state: parsedMockState,
- props: parsedMockState,
+ parsedProps: parsedMockState,
+ parsedState: parsedMockState,
});
} else {
- postMessage(RequestComponentAttrs, item.id);
+ postMessageToBackground(RequestComponentAttrs, item.id);
}
setSelectComp(item);
};
@@ -216,6 +192,7 @@ function App() {
name={selectComp ? selectComp.name : null}
attrs={selectComp ? componentAttrs : {}}
parents={parents}
+ id={selectComp ? selectComp.id : null}
onClickParent={handleClickParent} />
diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts
index e18f9bad..38238eda 100644
--- a/libs/extension/src/parser/parseAttr.ts
+++ b/libs/extension/src/parser/parseAttr.ts
@@ -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 的可编辑类型
type editableStringType = 'string' | 'number' | 'undefined' | 'null';
@@ -12,7 +15,7 @@ type showAsStringType = editableStringType | unEditableStringType;
export type IAttr = {
- name: string;
+ name: string | number;
indentation: number;
hIndex?: number; // 用于记录 hook 的 hIndex 值
} & ({
@@ -131,3 +134,61 @@ export function parseHooks(hooks: Hook
[]) {
});
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,
+ };
+}
diff --git a/libs/extension/src/utils/constants.ts b/libs/extension/src/utils/constants.ts
index e779a143..1be5c8dc 100644
--- a/libs/extension/src/utils/constants.ts
+++ b/libs/extension/src/utils/constants.ts
@@ -11,6 +11,14 @@ export const RequestComponentAttrs = 'get 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';
@@ -19,4 +27,4 @@ export const DevToolBackground = 'dev tool background';
export const DevToolContentScript = 'dev tool content script';
-export const DevToolHook = 'dev tool hook';
\ No newline at end of file
+export const DevToolHook = 'dev tool hook';