!84 [inula-dev-tools]<feat> 状态管理器调试页面合入
Merge pull request !84 from 涂旭辉/master
This commit is contained in:
commit
02be3494b5
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Inula, { useState } from 'openinula';
|
||||||
|
import { Modal } from './Modal';
|
||||||
|
import { highlight, sendMessage } from './utils';
|
||||||
|
|
||||||
|
function executeAction(storeId: string, name: string, args: any[]) {
|
||||||
|
sendMessage({
|
||||||
|
type: 'inulax run action',
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
storeId,
|
||||||
|
action: name,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryAction(storeId: string, name: string, args: any[]) {
|
||||||
|
sendMessage({
|
||||||
|
type: 'inulax queue action',
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
storeId,
|
||||||
|
action: name,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActionRunner({ foo, storeId, actionName }) {
|
||||||
|
const [data, setState] = useState({
|
||||||
|
modal: false,
|
||||||
|
gatheredAttrs: [],
|
||||||
|
query: false,
|
||||||
|
});
|
||||||
|
const modalIsOpen = data.modal;
|
||||||
|
const gatheredAttrs = data.gatheredAttrs;
|
||||||
|
function setData(val) {
|
||||||
|
const newData = {
|
||||||
|
modal: data.modal,
|
||||||
|
gatheredAttrs: data.gatheredAttrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(val).forEach(([key, value]) => (newData[key] = value));
|
||||||
|
|
||||||
|
setState(newData as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plainFunction = foo.replace(/\{.*}/gms, '');
|
||||||
|
const attributes = plainFunction
|
||||||
|
.replace(/^.*\(/g, '')
|
||||||
|
.replace(/\).*$/, '')
|
||||||
|
.split(/, ?/)
|
||||||
|
.filter((item, index) => index > 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
title={'Run action'}
|
||||||
|
onClick={() => {
|
||||||
|
if (attributes.length > 0) {
|
||||||
|
setData({ modal: false, gatheredAttrs: [], query: false });
|
||||||
|
} else {
|
||||||
|
executeAction(storeId, actionName, gatheredAttrs);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<b
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
☼
|
||||||
|
<span
|
||||||
|
title={'Add to action queue'}
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (attributes.len > 0) {
|
||||||
|
setData({ modal: true, gatheredAttrs: [], query: true });
|
||||||
|
} else {
|
||||||
|
queryAction(storeId, actionName, gatheredAttrs);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
⌛︎{' '}
|
||||||
|
</span>
|
||||||
|
</b>
|
||||||
|
<span>
|
||||||
|
<i>{plainFunction}</i>
|
||||||
|
{' {...}'}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{modalIsOpen ? (
|
||||||
|
<Modal
|
||||||
|
closeModal={() => {
|
||||||
|
setData({ modal: false });
|
||||||
|
}}
|
||||||
|
then={data => {
|
||||||
|
if (gatheredAttrs.length === attributes.length - 1) {
|
||||||
|
setData({ modal: false });
|
||||||
|
executeAction(storeId, actionName, gatheredAttrs.concat(data));
|
||||||
|
} else {
|
||||||
|
setData({
|
||||||
|
gatheredAttrs: gatheredAttrs.concat([data]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3>{data.query ? 'Query action:' : 'Run action:'}</h3>
|
||||||
|
<p>{highlight(plainFunction, attributes[gatheredAttrs.length])}</p>
|
||||||
|
</Modal>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,335 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from 'openinula';
|
||||||
|
import styles from './PanelX.less';
|
||||||
|
import { Tree } from './Tree';
|
||||||
|
import {displayValue, omit} from './utils';
|
||||||
|
|
||||||
|
type Mutation = {
|
||||||
|
mutation: boolean;
|
||||||
|
items?: Mutation[];
|
||||||
|
attributes?: { [key: string]: Mutation };
|
||||||
|
values?: Mutation[];
|
||||||
|
entries?: Mutation[];
|
||||||
|
from?: any;
|
||||||
|
to?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DiffTree({
|
||||||
|
mutation,
|
||||||
|
indent = 0,
|
||||||
|
index = '',
|
||||||
|
expand = false,
|
||||||
|
search = '',
|
||||||
|
forcedExpand = false,
|
||||||
|
omitAttrs = [],
|
||||||
|
doNotDisplayIcon = false,
|
||||||
|
forcedLabel = null,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
mutation: Mutation;
|
||||||
|
indent: number;
|
||||||
|
index?: string | number;
|
||||||
|
expand?: boolean;
|
||||||
|
search: string;
|
||||||
|
forcedExpand?: boolean;
|
||||||
|
omitAttrs: string[];
|
||||||
|
doNotDisplayIcon?: boolean;
|
||||||
|
forcedLabel?: string | number | null;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
if (omitAttrs.length && mutation.attributes) {
|
||||||
|
mutation.attributes = omit(mutation.attributes, ...omitAttrs);
|
||||||
|
mutation.from = mutation.from && omit(mutation.from, ...omitAttrs);
|
||||||
|
mutation.to = mutation.to && omit(mutation.to, ...omitAttrs);
|
||||||
|
}
|
||||||
|
const [expanded, setExpanded] = useState(expand);
|
||||||
|
|
||||||
|
const deleted = mutation.mutation && !('to' in mutation);
|
||||||
|
const newValue = mutation.mutation && !('from' in mutation);
|
||||||
|
const mutated = mutation.mutation;
|
||||||
|
|
||||||
|
const isArray = mutated && mutation.items;
|
||||||
|
const isObject = mutated && mutation.attributes;
|
||||||
|
const isMap = mutated && mutation.entries;
|
||||||
|
const isSet = mutated && mutation.values;
|
||||||
|
const isPrimitive = !isArray && !isObject && !isMap && !isSet;
|
||||||
|
|
||||||
|
if (!mutated) {
|
||||||
|
return (
|
||||||
|
<Tree
|
||||||
|
data={mutation.to}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
expand={expand}
|
||||||
|
forcedExpand={forcedExpand}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
forcedLabel={forcedLabel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue) {
|
||||||
|
return (
|
||||||
|
<Tree
|
||||||
|
data={mutation.to}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
expand={expand}
|
||||||
|
forcedExpand={forcedExpand}
|
||||||
|
className={styles.added}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
forcedLabel={forcedLabel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleted) {
|
||||||
|
return (
|
||||||
|
<Tree
|
||||||
|
data={mutation.from}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
expand={expand}
|
||||||
|
forcedExpand={forcedExpand}
|
||||||
|
className={styles.deleted}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
forcedLabel={forcedLabel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
}}
|
||||||
|
className={`${
|
||||||
|
expanded
|
||||||
|
? 'expanded'
|
||||||
|
: `not-expanded ${
|
||||||
|
mutated && !isPrimitive && !expanded ? styles.changed : ''
|
||||||
|
}`
|
||||||
|
}`}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{new Array(Math.max(indent, 0)).fill(<span> </span>)}
|
||||||
|
{isPrimitive ? (
|
||||||
|
// 如果两个 value 是基本变量并且不同,则简单显示不同点
|
||||||
|
<div
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tree
|
||||||
|
data={mutation.from}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
index={index}
|
||||||
|
className={styles.deleted}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
/>
|
||||||
|
<Tree
|
||||||
|
data={mutation.to}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
index={index}
|
||||||
|
className={styles.added}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// 如果至少有一个是复杂变量,则需要展开按钮
|
||||||
|
<>
|
||||||
|
{forcedExpand ? '' : expanded ? <span>▼</span> : <span>▶</span>}
|
||||||
|
{index === 0 || index ? (
|
||||||
|
<b className={styles.purple}>{displayValue(index, search)}: </b>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{isArray ? (
|
||||||
|
// 如果都是数组进行比较
|
||||||
|
expanded ? (
|
||||||
|
[
|
||||||
|
Array(Math.max(mutation.from.length, mutation.to.length))
|
||||||
|
.fill(true)
|
||||||
|
.map((i, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{mutation.items[index].mutation ? (
|
||||||
|
<DiffTree
|
||||||
|
mutation={{
|
||||||
|
...mutation.items[index],
|
||||||
|
to: mutation.to[index],
|
||||||
|
}}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
forcedLabel={index}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Tree
|
||||||
|
data={mutation.to[index]}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
index={index}
|
||||||
|
className={styles.default}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
) : (
|
||||||
|
forcedLabel || `Array(${mutation.to?.length})`
|
||||||
|
)
|
||||||
|
) : isSet ? (
|
||||||
|
expanded ? (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{forcedLabel || `Set(${mutation.to?.values.length})`}
|
||||||
|
</div>
|
||||||
|
{Array(
|
||||||
|
Math.max(
|
||||||
|
mutation.from?.values.length,
|
||||||
|
mutation.to?.values.length
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.fill(true)
|
||||||
|
.map((i ,index) => (
|
||||||
|
<div>
|
||||||
|
{mutation.values[index].mutation ? (
|
||||||
|
<DiffTree
|
||||||
|
mutation={{
|
||||||
|
...mutation.values[index],
|
||||||
|
}}
|
||||||
|
indent={indent + 2}
|
||||||
|
search={search}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Tree
|
||||||
|
data={mutation.to?.values[index]}
|
||||||
|
indent={indent + 2}
|
||||||
|
search={search}
|
||||||
|
className={styles.default}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{forcedLabel || `Set(${mutation.to?.values.length})`}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
) : isMap ? (
|
||||||
|
expanded ? (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
{forcedLabel || `Map(${mutation.to?.entries.length})`}
|
||||||
|
</span>
|
||||||
|
{Array(
|
||||||
|
Math.max(
|
||||||
|
mutation.from?.entries.length,
|
||||||
|
mutation.to?.entries.length
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.fill(true)
|
||||||
|
.map((i, index) =>
|
||||||
|
mutation.entries[index].mutation ? (
|
||||||
|
<div>
|
||||||
|
<DiffTree
|
||||||
|
mutation={{
|
||||||
|
...mutation.entries[index],
|
||||||
|
}}
|
||||||
|
indent={indent + 2}
|
||||||
|
search={search}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
forcedLabel={'[map item]'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Tree
|
||||||
|
data={mutation.to?.entries[index]}
|
||||||
|
indent={indent + 2}
|
||||||
|
search={search}
|
||||||
|
className={styles.default}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
forcedLabel={'[map item]'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{forcedLabel || `Map(${mutation.to?.entries.length})`}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
) : expanded ? (
|
||||||
|
// 如果都是 object 进行比较
|
||||||
|
Object.entries(mutation.attributes).map(([key, item]) => {
|
||||||
|
return item.mutation ? (
|
||||||
|
<span onClick={e => e.stopPropagation()}>
|
||||||
|
{
|
||||||
|
<DiffTree
|
||||||
|
mutation={item}
|
||||||
|
index={key}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
className={!expanded && mutated ? '' : styles.changed}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span onClick={e => e.stopPropagation()}>
|
||||||
|
{
|
||||||
|
<Tree
|
||||||
|
data={mutation.to[key]}
|
||||||
|
index={key}
|
||||||
|
indent={indent}
|
||||||
|
search={search}
|
||||||
|
className={styles.default}
|
||||||
|
omitAttrs={omitAttrs}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
forcedLabel || '{ ... }'
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,402 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useState, useRef } from 'openinula';
|
||||||
|
import { DevToolPanel } from '../utils/constants';
|
||||||
|
import {
|
||||||
|
initBackgroundConnection,
|
||||||
|
addBackgroundMessageListener,
|
||||||
|
removeBackgroundMessageListener,
|
||||||
|
} from '../panelConnection';
|
||||||
|
import { Table } from './Table';
|
||||||
|
import { Tree } from './Tree';
|
||||||
|
import {fullTextSearch, omit} from './utils';
|
||||||
|
import styles from './PanelX.less';
|
||||||
|
import { Checkbox } from '../utils/Checkbox';
|
||||||
|
import { DiffTree } from './DiffTree';
|
||||||
|
|
||||||
|
const eventTypes = {
|
||||||
|
INITIALIZED: 'inulax store initialized',
|
||||||
|
STATE_CHANGE: 'inulax state change',
|
||||||
|
SUBSCRIBED: 'inulax subscribed',
|
||||||
|
UNSUBSCRIBED: 'inulax unsubscribed',
|
||||||
|
ACTION: 'inulax action',
|
||||||
|
ACTION_QUEUED: 'inulax action queued',
|
||||||
|
QUEUE_PENDING: 'inulax queue pending',
|
||||||
|
QUEUE_FINISHED: 'inulax queue finished',
|
||||||
|
};
|
||||||
|
|
||||||
|
const otherTypes = {
|
||||||
|
GET_EVENTS: 'inulax getEvents',
|
||||||
|
GET_PERSISTENCE: 'inulax getPersistence',
|
||||||
|
EVENTS: 'inulax events',
|
||||||
|
FLUSH_EVENTS: 'inulax flush events',
|
||||||
|
SET_PERSISTENT: 'inulax setPersistent',
|
||||||
|
RESET_EVENTS: 'inulax resetEvents',
|
||||||
|
};
|
||||||
|
|
||||||
|
function extractDataByType(message, search) {
|
||||||
|
if (message.type === eventTypes.ACTION) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tree
|
||||||
|
data={{
|
||||||
|
Action: `${message.data.action.action}${
|
||||||
|
message.data.fromQueue ? ' (queued)' : ''
|
||||||
|
}`
|
||||||
|
}}
|
||||||
|
expand={true}
|
||||||
|
indent={-4}
|
||||||
|
forcedExpand={true}
|
||||||
|
search={search}
|
||||||
|
omitAttrs={['_inulaObserver']}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === eventTypes.STATE_CHANGE) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<b>{`${message.data.change.vNodes.length} nodes changed:`}</b>
|
||||||
|
<Tree
|
||||||
|
data={message.data.change.vNodes.map(vNode => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<i>{vNode.type}</i>()
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span className={styles.grey}>N/A</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EventLog({ setNextStore, setEventFilter, eventFilter }) {
|
||||||
|
const [log, setLog] = useState([]);
|
||||||
|
const [initlized, setInitlized] = useState(false);
|
||||||
|
const [persistent, setPersistent] = useState(false);
|
||||||
|
const filterField = useRef(null);
|
||||||
|
|
||||||
|
const addFilter = (key, value) => {
|
||||||
|
const filters = { ...eventFilter };
|
||||||
|
filters[key] = value;
|
||||||
|
|
||||||
|
setEventFilter(filters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFilter = key => {
|
||||||
|
const filters = { ...eventFilter };
|
||||||
|
delete filters[key];
|
||||||
|
|
||||||
|
setEventFilter(filters);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!initlized) {
|
||||||
|
setTimeout(() => {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'INULA_DEV_TOOLS',
|
||||||
|
payload: {
|
||||||
|
type: otherTypes.GET_EVENTS,
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
},
|
||||||
|
from: DevToolPanel,
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'INULA_DEV_TOOLS',
|
||||||
|
payload: {
|
||||||
|
type: otherTypes.GET_PERSISTENCE,
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
},
|
||||||
|
from: DevToolPanel,
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const lisener = message => {
|
||||||
|
if (message.payload.type.startsWith('inulax')) {
|
||||||
|
if (message.payload.type === otherTypes.EVENTS) {
|
||||||
|
setLog(message.payload.events);
|
||||||
|
setInitlized(true);
|
||||||
|
} else if (message.payload.type === otherTypes.SET_PERSISTENT) {
|
||||||
|
setPersistent(message.payload.persistent);
|
||||||
|
} else if (message.payload.type === otherTypes.FLUSH_EVENTS) {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'INULA_DEV_TOOLS',
|
||||||
|
payload: {
|
||||||
|
type: otherTypes.GET_EVENTS,
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
},
|
||||||
|
from: DevToolPanel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
initBackgroundConnection('panel');
|
||||||
|
addBackgroundMessageListener(lisener);
|
||||||
|
return () => {
|
||||||
|
removeBackgroundMessageListener(lisener);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const filters = Object.entries(eventFilter);
|
||||||
|
const usedTypes = { all: 0 };
|
||||||
|
|
||||||
|
const processedData = log
|
||||||
|
.filter(event => {
|
||||||
|
if (!Object.values(eventTypes).includes(event.message.type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
usedTypes.all++;
|
||||||
|
if (!usedTypes[event.message.type]) {
|
||||||
|
usedTypes[event.message.type] = 1;
|
||||||
|
} else {
|
||||||
|
usedTypes[event.message.type]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filters.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !filters.some(([key, value]) => {
|
||||||
|
if (key === 'fulltext') {
|
||||||
|
const result = fullTextSearch(event, value);
|
||||||
|
return !result;
|
||||||
|
}
|
||||||
|
const keys = key.split('.');
|
||||||
|
let search = event;
|
||||||
|
keys.forEach(attr => {
|
||||||
|
search = search[attr];
|
||||||
|
});
|
||||||
|
return value !== search;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.map(event => {
|
||||||
|
const date = new Date(event.timestamp);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: event.id,
|
||||||
|
timestamp: event.timestamp,
|
||||||
|
type: event.message.type,
|
||||||
|
time: `${date.toLocaleTimeString()} - ${date.toLocaleDateString()}`,
|
||||||
|
state: event.message.type === eventTypes.STATE_CHANGE ? (
|
||||||
|
<DiffTree
|
||||||
|
mutation={event.message.data.change.mutation}
|
||||||
|
expand={true}
|
||||||
|
forcedExpand={true}
|
||||||
|
indent={0}
|
||||||
|
search={eventFilter['fulltext']}
|
||||||
|
omitAttrs={['_inulaObserver']}
|
||||||
|
doNotDisplayIcon={true}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Tree
|
||||||
|
data={event.message.data.store.$s}
|
||||||
|
expand={true}
|
||||||
|
search={eventFilter['fulltext']}
|
||||||
|
forcedExpand={true}
|
||||||
|
indent={-4}
|
||||||
|
omitAttrs={['_inulaObserver']}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
storeClick: (
|
||||||
|
<span
|
||||||
|
className={styles.link}
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
setNextStore(event.message.data.store.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.message.data.store.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
additionalData: extractDataByType(
|
||||||
|
event.message,
|
||||||
|
eventFilter['fulltext']
|
||||||
|
),
|
||||||
|
storeId: event.message.data.store.id,
|
||||||
|
event,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginTop: '0px', margin: '5px' }}>
|
||||||
|
<input
|
||||||
|
ref={filterField}
|
||||||
|
type={'text'}
|
||||||
|
placeholder={'Filter:'}
|
||||||
|
className={`${styles.compositeInput} ${styles.left}`}
|
||||||
|
onKeyUp={() => {
|
||||||
|
if (!filterField.current.value) {
|
||||||
|
removeFilter('fulltext');
|
||||||
|
}
|
||||||
|
addFilter('fulltext', filterField.current.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={`${styles.bold} ${styles.compositeInput} ${styles.right}`}
|
||||||
|
onClick={() => {
|
||||||
|
filterField.current.value = '';
|
||||||
|
removeFilter('fulltext');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
<span className={styles.grey}>{' | '}</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'INULA_DEV_TOOLS',
|
||||||
|
payload: {
|
||||||
|
type: otherTypes.SET_PERSISTENT,
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
persistent: !persistent,
|
||||||
|
},
|
||||||
|
from: DevToolPanel,
|
||||||
|
});
|
||||||
|
|
||||||
|
setPersistent(!persistent);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox value={persistent}></Checkbox> Persistent events
|
||||||
|
</span>
|
||||||
|
{' | '}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
// 重置 events
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'INULA_DEV_TOOLS',
|
||||||
|
payload: {
|
||||||
|
type: otherTypes.RESET_EVENTS,
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
},
|
||||||
|
from: DevToolPanel,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
{eventFilter['message.data.store.id'] ? (
|
||||||
|
<span>
|
||||||
|
{' | '}
|
||||||
|
<b
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setNextStore(eventFilter['message.data.store.id']);
|
||||||
|
}}
|
||||||
|
>{` Displaying: [${eventFilter['message.data.store.id']}] `}</b>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
removeFilter('message.data.store.id');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: '0px', margin: '5px' }}>
|
||||||
|
<button
|
||||||
|
className={`${styles.filterButton} ${log.length ? '' : styles.grey} ${
|
||||||
|
eventFilter['message.type'] ? '' : styles.active
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
removeFilter('message.type');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
All({usedTypes.all})
|
||||||
|
</button>
|
||||||
|
{Object.values(eventTypes).map(eventType => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`${styles.filterButton} ${
|
||||||
|
usedTypes[eventType] ? '' : styles.grey
|
||||||
|
} ${
|
||||||
|
eventFilter['message.type'] === eventType ? styles.active : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
addFilter('message.type', eventType);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{`${eventType.replace('inulax ', '')}(${
|
||||||
|
usedTypes[eventType] || 0
|
||||||
|
})`}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
data={processedData}
|
||||||
|
dataKey={'id'}
|
||||||
|
displayKeys={[
|
||||||
|
['type', 'Event type:'],
|
||||||
|
['storeClick', 'Store:'],
|
||||||
|
['time', 'Time:'],
|
||||||
|
['state', 'State:'],
|
||||||
|
['additionalData', 'Additional data:'],
|
||||||
|
]}
|
||||||
|
displayDataProcessor={data => {
|
||||||
|
const message = data.event.message;
|
||||||
|
return {
|
||||||
|
type: data.type,
|
||||||
|
store: {
|
||||||
|
actions: Object.fromEntries(
|
||||||
|
Object.entries(message.data.store.$config.actions).map(
|
||||||
|
([id, action]) => {
|
||||||
|
return [
|
||||||
|
id,
|
||||||
|
(action as string).replace(/\{.*}/gms, '{...}').replace('function ', ''),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
computed: Object.fromEntries(
|
||||||
|
Object.keys(message.data.store.$c).map(key => [
|
||||||
|
key,
|
||||||
|
message.data.store.expanded[key],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
state: message.data.store.$s,
|
||||||
|
id: message.data.store.id,
|
||||||
|
},
|
||||||
|
// data: omit(data, 'storeClick', 'additionalData'),
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
search={eventFilter.fulltext ? eventFilter.fulltext : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Inula, { useRef, useState } from 'openinula';
|
||||||
|
|
||||||
|
export function Modal({
|
||||||
|
closeModal,
|
||||||
|
then,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
closeModal: () => void;
|
||||||
|
then: (value: any) => void;
|
||||||
|
children?: any[];
|
||||||
|
}) {
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
inputRef.current.focus();
|
||||||
|
inputRef.current.value = '';
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
const tryGatherData = () => {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = eval(inputRef.current.value);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (then) {
|
||||||
|
then(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0 , 0.3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
top: 'calc(50vh - 50px)',
|
||||||
|
left: 'calc(50vw - 125px)',
|
||||||
|
width: '250px',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
border: '1px solid black',
|
||||||
|
position: 'fixed',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>{children}</p>
|
||||||
|
<p>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type={'text'}
|
||||||
|
onKeyPress={({key}) => {
|
||||||
|
if (key === 'Enter') {
|
||||||
|
tryGatherData();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{error ? <p>Variable parsing error</p> : null}
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
tryGatherData();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
closeModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import '../components/assets.less';
|
||||||
|
|
||||||
|
.displayData {
|
||||||
|
background-color: rgb(241, 243, 244);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100%;
|
||||||
|
font-size: @common-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.wrapper {
|
||||||
|
margin: 15px;
|
||||||
|
position: relative;
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.table {
|
||||||
|
display: table;
|
||||||
|
vertical-align: top;
|
||||||
|
width: calc(100%);
|
||||||
|
background-color: white;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.row {
|
||||||
|
display: table-row;
|
||||||
|
|
||||||
|
&:nth-child(2n + 1) {
|
||||||
|
background-color: rgb(241, 243, 244);
|
||||||
|
.default {
|
||||||
|
background-color: rgb(241, 243, 244);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cell {
|
||||||
|
display: table-cell;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.half {
|
||||||
|
width: calc(50% - 8px);
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.header {
|
||||||
|
background-color: rgb(241, 243, 244);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.row.active {
|
||||||
|
background-color: #00a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.tab {
|
||||||
|
border: 1px solid grey;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
&.active {
|
||||||
|
border-bottom: none;
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.highlighted {
|
||||||
|
background-color: #ff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: #a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
color: #00a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
color: #909;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #00a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compositeInput {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid grey;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 5px;
|
||||||
|
&.left {
|
||||||
|
border-right: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
border-left: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterButton {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 0;
|
||||||
|
&.active {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.added {
|
||||||
|
background-color: #afa;
|
||||||
|
&::before {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #0a0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted {
|
||||||
|
background-color: #faa;
|
||||||
|
text-decoration-line: line-through;
|
||||||
|
&::before {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #a00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.changed {
|
||||||
|
background-color: #ffa;
|
||||||
|
&::before {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ca0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.default {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floatingButton {
|
||||||
|
right: 5px;
|
||||||
|
position: absolute;
|
||||||
|
height: 17px;
|
||||||
|
width: 17px;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable {
|
||||||
|
max-height: calc(100vh - 65px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
div.row {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from 'openinula';
|
||||||
|
import EventLog from './EventLog';
|
||||||
|
import Stores from './Stores';
|
||||||
|
import styles from './PanelX.less';
|
||||||
|
|
||||||
|
export default function PanelX() {
|
||||||
|
const [active, setActive] = useState('stores');
|
||||||
|
const [nextStoreId, setNextStoreId] = useState(null);
|
||||||
|
const [eventFilter, setEventFilter] = useState({});
|
||||||
|
|
||||||
|
function showFilterEvents(filter) {
|
||||||
|
setActive('events');
|
||||||
|
setEventFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
id: 'stores',
|
||||||
|
title: 'Stores',
|
||||||
|
getComponent: () => (
|
||||||
|
<Stores
|
||||||
|
nextStoreId={nextStoreId}
|
||||||
|
showFilteredEvents={showFilterEvents}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'events',
|
||||||
|
title: 'Events',
|
||||||
|
getComponents: () => (
|
||||||
|
<EventLog
|
||||||
|
setNextStore={id => {
|
||||||
|
setNextStoreId(id);
|
||||||
|
setActive('stores');
|
||||||
|
}}
|
||||||
|
setEventFilter={setEventFilter}
|
||||||
|
eventFilter={eventFilter}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
{tabs.map(tab =>
|
||||||
|
tab.id === active ? (
|
||||||
|
<button
|
||||||
|
className={`${styles.tab} ${styles.active}`}
|
||||||
|
disabled={true}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className={styles.tab}
|
||||||
|
onClick={() => {
|
||||||
|
setActive(tab.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{tabs.find(item => item.id === active).getComponent()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'openinula';
|
||||||
|
import {
|
||||||
|
initBackgroundConnection,
|
||||||
|
addBackgroundMessageListener,
|
||||||
|
removeBackgroundMessageListener,
|
||||||
|
postMessageToBackground,
|
||||||
|
} from '../panelConnection';
|
||||||
|
import { Table } from './Table';
|
||||||
|
import { omit, sendMessage } from './utils';
|
||||||
|
import styles from './PanelX.less';
|
||||||
|
import { Highlight, RemoveHighlight } from '../utils/constants';
|
||||||
|
import { ActionRunner } from './ActionRunner';
|
||||||
|
import { Tree } from './Tree';
|
||||||
|
|
||||||
|
export default function Stores({ nextStoreId, showFilteredEvents }) {
|
||||||
|
const [stores, setStores] = useState([]);
|
||||||
|
const [initialized, setInitialized] = useState(false);
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
setTimeout(() => {
|
||||||
|
sendMessage({
|
||||||
|
type: 'inulax getStores',
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = message => {
|
||||||
|
if (message.payload.type.startsWith('inulax')) {
|
||||||
|
// 过滤 inula 消息
|
||||||
|
if (message.payload.type === 'inulax stores') {
|
||||||
|
// Stores 更新
|
||||||
|
setStores(message.payload.stores);
|
||||||
|
setInitialized(true);
|
||||||
|
} else if (message.payload.type === 'inulax flush stores') {
|
||||||
|
// Flush store
|
||||||
|
sendMessage({
|
||||||
|
type: 'inulax getStores',
|
||||||
|
tabId: chrome.devtools.inspectedWindow.tabId,
|
||||||
|
});
|
||||||
|
} else if (message.payload.type === 'inulax observed components') {
|
||||||
|
// observed components 更新
|
||||||
|
setStores(
|
||||||
|
stores.map(store => {
|
||||||
|
store.observedComponents = message.payload.data[store.id] || [];
|
||||||
|
return store;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
initBackgroundConnection('panel');
|
||||||
|
addBackgroundMessageListener(listener);
|
||||||
|
return () => {
|
||||||
|
removeBackgroundMessageListener(listener);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from 'openinula';
|
||||||
|
import {Tree} from './Tree';
|
||||||
|
import styles from './PanelX.less';
|
||||||
|
|
||||||
|
type displayKeysType = [string, string][];
|
||||||
|
|
||||||
|
export function Table({
|
||||||
|
data,
|
||||||
|
dataKey = 'id',
|
||||||
|
displayKeys,
|
||||||
|
activate,
|
||||||
|
displayDataProcessor,
|
||||||
|
search = '',
|
||||||
|
}: {
|
||||||
|
data;
|
||||||
|
dataKey: string;
|
||||||
|
displayKeys: displayKeysType;
|
||||||
|
activate?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
displayDataProcessor: (data: { [key: string]: any }) => {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
search: string;
|
||||||
|
}) {
|
||||||
|
const [keyToDisplay, setKeyToDisplay] = useState(false);
|
||||||
|
const [manualOverride, setManualOverride] = useState(false);
|
||||||
|
let displayRow = null;
|
||||||
|
|
||||||
|
if (!manualOverride && activate) {
|
||||||
|
data.forEach(item => {
|
||||||
|
if (displayRow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let notMatch = false;
|
||||||
|
Object.entries(activate).forEach(([key, value]) => {
|
||||||
|
if (notMatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item[key] !== value) {
|
||||||
|
notMatch = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (notMatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
displayRow = item;
|
||||||
|
});
|
||||||
|
} else if (manualOverride && keyToDisplay) {
|
||||||
|
data.forEach(item => {
|
||||||
|
if (displayRow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item[dataKey] === keyToDisplay) {
|
||||||
|
displayRow = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayRow) {
|
||||||
|
const [attr, title] = displayKeys[0];
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={`${styles.table} ${styles.half}`}>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={`${styles.cell} ${styles.header}`}>{title}</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.scrollable}>
|
||||||
|
<span></span>
|
||||||
|
{data.map(row => (
|
||||||
|
<div
|
||||||
|
className={`${styles.row} ${
|
||||||
|
keyToDisplay === row[dataKey] ? styles.active : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setManualOverride(true);
|
||||||
|
setKeyToDisplay(
|
||||||
|
keyToDisplay === row[dataKey] ? null : row[dataKey]
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles.cell}>{row?.[attr] || ''}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`${styles.table} ${styles.half} ${styles.displayData}`}>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.cell}>
|
||||||
|
<b>Data:</b>
|
||||||
|
<button
|
||||||
|
className={styles.floatingButton}
|
||||||
|
onClick={() => {
|
||||||
|
setKeyToDisplay(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.scrollable}>
|
||||||
|
<span></span>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.cell}>
|
||||||
|
<Tree
|
||||||
|
data={
|
||||||
|
displayDataProcessor
|
||||||
|
? displayDataProcessor(displayRow)
|
||||||
|
: displayRow
|
||||||
|
}
|
||||||
|
indent={displayRow[displayKeys[0][0]]}
|
||||||
|
expand={true}
|
||||||
|
search={search}
|
||||||
|
forcedExpand={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.table}>
|
||||||
|
<div className={`${styles.row} ${styles.header}`}>
|
||||||
|
{displayKeys.map(([key, title]) => (
|
||||||
|
<div className={styles.cell}>{title}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{data.map(item => (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setManualOverride(true);
|
||||||
|
setKeyToDisplay(item[dataKey]);
|
||||||
|
}}
|
||||||
|
className={styles.row}
|
||||||
|
>
|
||||||
|
{displayKeys.map(([key, title]) => (
|
||||||
|
<div className={styles.cell}>{item[key]}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,230 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from 'openinula';
|
||||||
|
import styles from './PanelX.less';
|
||||||
|
import {Modal} from './Modal';
|
||||||
|
import { displayValue, omit } from './utils';
|
||||||
|
|
||||||
|
export function Tree({
|
||||||
|
data,
|
||||||
|
indent = 0,
|
||||||
|
index = '',
|
||||||
|
expand = false,
|
||||||
|
search = '',
|
||||||
|
forcedExpand = false,
|
||||||
|
onEdit = null,
|
||||||
|
omitAttrs = [],
|
||||||
|
className,
|
||||||
|
forcedLabel = null,
|
||||||
|
}: {
|
||||||
|
data: any;
|
||||||
|
indent?: number;
|
||||||
|
index?: string | number;
|
||||||
|
expand?: boolean;
|
||||||
|
search?: string;
|
||||||
|
forcedExpand?: boolean;
|
||||||
|
className?: string | undefined
|
||||||
|
omitAttrs?: string[];
|
||||||
|
onEdit?: (path: any[], value: any) => void | null;
|
||||||
|
forcedLabel?: string | number | null;
|
||||||
|
}) {
|
||||||
|
const [expanded, setExpanded] = useState(expand);
|
||||||
|
const [modal, setModal] = useState(false);
|
||||||
|
const isArray = Array.isArray(data);
|
||||||
|
const isObject = data && !isArray && typeof data === 'object';
|
||||||
|
const isSet = isObject && data?._type === 'Set';
|
||||||
|
const isWeakSet = isObject && data?._type === 'WeakSet';
|
||||||
|
const isMap = isObject && data?._type === 'Map';
|
||||||
|
const isWeakMap = isObject && data?._type === 'WeakMap';
|
||||||
|
const isVNode = isObject && data.vtype;
|
||||||
|
const canBeExpanded = isArray || (isObject && !isWeakSet && !isWeakMap);
|
||||||
|
|
||||||
|
if (isObject && omitAttrs?.length) {
|
||||||
|
data = omit(data, ...omitAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canBeExpanded ? (
|
||||||
|
<div
|
||||||
|
style={{ fontFamily: 'monoSpace' }}
|
||||||
|
className={`${expanded ? 'expanded' : 'not-expanded'} ${className}`}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{new Array(Math.max(indent, 0)).fill(<span> </span>)}
|
||||||
|
{forcedExpand || isVNode ? null : expanded ? (
|
||||||
|
<span>▼</span>
|
||||||
|
) : (
|
||||||
|
<span>▶</span>
|
||||||
|
)}
|
||||||
|
{index === 0 || index ? (
|
||||||
|
<>
|
||||||
|
<b className={styles.purple}>{displayValue(index, search)}: </b>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{forcedLabel
|
||||||
|
? forcedLabel
|
||||||
|
: expanded
|
||||||
|
? isVNode
|
||||||
|
? null
|
||||||
|
: Array.isArray(data)
|
||||||
|
? `Array(${data.length})`
|
||||||
|
: isMap
|
||||||
|
? `Map(${data.entries.length})`
|
||||||
|
: isSet
|
||||||
|
? `Set(${data.values.length})`
|
||||||
|
: '{ ... }'
|
||||||
|
: isWeakMap
|
||||||
|
? 'WeakMap()'
|
||||||
|
: isWeakSet
|
||||||
|
? 'WeakSet()'
|
||||||
|
: isMap
|
||||||
|
? `Map(${data.entries.length})`
|
||||||
|
: isSet
|
||||||
|
? `Set(${data.values.length})`
|
||||||
|
: Array.isArray(data)
|
||||||
|
? `Array(${data.length})`
|
||||||
|
: '{ ... }'}
|
||||||
|
</span>
|
||||||
|
{expanded || isVNode ? (
|
||||||
|
isArray ? (
|
||||||
|
<>
|
||||||
|
{data.map((value, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tree
|
||||||
|
data={value}
|
||||||
|
indent={indent + 4}
|
||||||
|
index={index}
|
||||||
|
search={search}
|
||||||
|
className={className}
|
||||||
|
onEdit={
|
||||||
|
onEdit
|
||||||
|
? (path, val) => {
|
||||||
|
onEdit(path.concat([index]), val);
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
) : isVNode ? (
|
||||||
|
data
|
||||||
|
) : isMap ? (
|
||||||
|
<div>
|
||||||
|
{data.entries.map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<Tree
|
||||||
|
data={{key, value}}
|
||||||
|
indent={indent + 4}
|
||||||
|
search={search}
|
||||||
|
className={className}
|
||||||
|
// TODO: editable sets
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : isSet ? (
|
||||||
|
data.values.map(item => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tree
|
||||||
|
data={item}
|
||||||
|
indent={indent + 4}
|
||||||
|
search={search}
|
||||||
|
className={className}
|
||||||
|
// TODO: editable sets
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
Object.entries(data).map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tree
|
||||||
|
data={value}
|
||||||
|
indent={indent + 4}
|
||||||
|
index={key}
|
||||||
|
search={search}
|
||||||
|
className={className}
|
||||||
|
onEdit={
|
||||||
|
onEdit
|
||||||
|
? (path, val) => {
|
||||||
|
onEdit(path.concat([key]), val);
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={'not-expanded'}>
|
||||||
|
{new Array(indent).fill(<span> </span>)}
|
||||||
|
<span className={`${className}`}>
|
||||||
|
{typeof index !== 'undefined' ? (
|
||||||
|
<>
|
||||||
|
<b className={styles.purple}>{displayValue(index, search)}: </b>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{displayValue(data, search)}
|
||||||
|
{onEdit && !isWeakSet && !isWeakMap ? ( // TODO: editable weak set and map
|
||||||
|
<>
|
||||||
|
<b
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
setModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
☼
|
||||||
|
</b>
|
||||||
|
{onEdit && modal ? (
|
||||||
|
<Modal
|
||||||
|
closeModal={() => {
|
||||||
|
setModal(false);
|
||||||
|
}}
|
||||||
|
then={data => {
|
||||||
|
onEdit([], data);
|
||||||
|
setModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3>Edit value:</h3> {index}
|
||||||
|
</Modal>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PanelX from './PanelX';
|
||||||
|
|
||||||
|
export default PanelX;
|
|
@ -0,0 +1,49 @@
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
~
|
||||||
|
~ openInula is licensed under Mulan PSL v2.
|
||||||
|
~ You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
~ You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
~
|
||||||
|
~ http://license.coscl.org.cn/MulanPSL2
|
||||||
|
~
|
||||||
|
~ THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
~ EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
~ MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
~ See the Mulan PSL v2 for more details.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html style="display: flex">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8">
|
||||||
|
<meta http-equiv="Content-Security-Policy"
|
||||||
|
content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' ">
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="inula.development.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="panelX.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Inula from 'openinula';
|
||||||
|
import styles from './PanelX.less';
|
||||||
|
import { DevToolPanel } from '../utils/constants';
|
||||||
|
|
||||||
|
export function highlight(source, search) {
|
||||||
|
if (!search || !source?.split) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = source.split(search);
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i= 0; i < parts.length * 2 - 1; i++) {
|
||||||
|
if (i % 2) {
|
||||||
|
result.push(<span className={styles.highlighted}>{search}</span>);
|
||||||
|
} else {
|
||||||
|
result.push(parts[i / 2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function displayValue(val: any, search = '') {
|
||||||
|
if (typeof val === 'boolean') {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{highlight(val ? 'true' : 'false', search)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val === '') {
|
||||||
|
return <span className={styles.red}>{'""'}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof val === 'undefined') {
|
||||||
|
return <span className={styles.grey}>{highlight('undefined', search)}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val === 'null') {
|
||||||
|
return <span className={styles.grey}>{highlight('null', search)}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
if (val.match(/^function\s?\(/)) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<i>ƒ</i>
|
||||||
|
{highlight(
|
||||||
|
val.match(/^function\s?\([\w,]*\)/g)[0].replace(/^function\s?/, ''),
|
||||||
|
search
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <span className={styles.red}>"{highlight(val, search)}"</span>;
|
||||||
|
}
|
||||||
|
if (typeof val === 'number') {
|
||||||
|
return <span className={styles.blue}>{highlight('' + val, search)}</span>;
|
||||||
|
}
|
||||||
|
if (typeof val === 'function') {
|
||||||
|
const args = val.toString().match(/^function\s?\([\w,]*\)/g)[0].replace(/^function\s?/, '');
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<i>ƒ</i>
|
||||||
|
{highlight(args, search)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof val === 'object') {
|
||||||
|
if (val?._type === 'WeakSet') {
|
||||||
|
return <span>WeakSet()</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val?._type === 'WeakMap') {
|
||||||
|
return <span>WeakMap()</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fullTextSearch(value, search) {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.some(val => fullTextSearch(val, search));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if (value?._type === 'Set') {
|
||||||
|
return value.values.some(val => fullTextSearch(val, search));
|
||||||
|
}
|
||||||
|
if (value?._type === 'Map') {
|
||||||
|
return value.entries.some(
|
||||||
|
(key, val) => fullTextSearch(key, search) || fullTextSearch(val, search)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Object.values(value).some(val => fullTextSearch(val, search));
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toString().includes(search);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function omit(obj, ...attrs) {
|
||||||
|
const res = { ...obj };
|
||||||
|
attrs.forEach(attr => delete res[attr]);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringify(data) {
|
||||||
|
if (typeof data === 'string' && data.startsWith('function(')) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<i>ƒ</i>
|
||||||
|
{data.match(/^function\([\w,]*\)/g)[0].substring(8)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return displayValue(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return `Array(${data.length})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
return `{${Object.entries(data).map(([key, value]) => {
|
||||||
|
if (typeof value === 'string' && value.startsWith('function(')) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>
|
||||||
|
<span>
|
||||||
|
<i>ƒ</i>
|
||||||
|
{value.match(/^function\([\w,]*\)/g)[0].substring(8)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!value) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>:{displayValue(value)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>:{' '}
|
||||||
|
{`Array(${value.length})`}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if ((value as any)?._type === 'WeakSet') {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>: {'WeakSet()'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ((value as any)?._type === 'WeakMap') {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>: {'WeakMap'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ((value as any)?._type === 'Set') {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>:{' '}
|
||||||
|
{`Set(${(value as Set<any>).size})`}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ((value as any)?._type === 'Map') {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>:{' '}
|
||||||
|
{`Map(${(value as Map<any, any>).size})`}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// object
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>: {'{...}'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className={styles.purple}>{key}</span>: {displayValue(value)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}}`;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendMessage(payload) {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'INULA_DEV_TOOLS',
|
||||||
|
payload,
|
||||||
|
from: DevToolPanel,
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue