!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