2024-12-12 20:13:50 +08:00

585 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="devtools">
<Test @valid-game="testValidGame"> </Test>
<div class="head" v-show="iframes.length > 1">
<div class="label">inspect target:</div>
<CCSelect v-model:value="frameID" @change="onChangeFrame" :data="getFramesData()"> </CCSelect>
</div>
<div v-show="isShowDebug" class="find">
<div v-if="false">
<CCButton type="success" @click="onMemoryTest">内存测试</CCButton>
<span>JS堆栈限制: {{ memory.performance.jsHeapSizeLimit }}</span>
<span>JS堆栈大小: {{ memory.performance.totalJSHeapSize }}</span>
<span>JS堆栈使用: {{ memory.performance.usedJSHeapSize }}</span>
</div>
<div class="left">
<div class="tool-btn">
<div class="text">Node Tree</div>
<CCButtonGroup :items="buttonGroup" :recover="true"></CCButtonGroup>
</div>
<CCInput style="flex: none" placeholder="enter keywords to filter" :data="filterText" v-if="false">
<slot>
<i class="matchCase iconfont icon_font_size" @click.stop="onChangeCase" title="match case" :style="{ color: matchCase ? 'red' : '' }"></i>
</slot>
</CCInput>
<CCTree style="flex: 1" ref="elTree" :expand-keys="expandedKeys" :default-expand-all="false" :value="treeData" @node-expand="onNodeExpand" @node-collapse="onNodeCollapse" @node-click="handleNodeClick"></CCTree>
</div>
<CCDivider></CCDivider>
<div class="right">
<Properties v-if="treeItemData" :data="treeItemData"></Properties>
</div>
</div>
<div v-show="!isShowDebug" class="no-find">
<span>no games created by cocos creator found!</span>
<i class="fresh iconfont icon_refresh" @click="onBtnClickUpdatePage"></i>
</div>
<CCDialog></CCDialog>
<CCFootBar :version="version"></CCFootBar>
</div>
</template>
<script lang="ts">
import ccui from "@xuyanfeng/cc-ui";
import { ButtonGroupItem } from "@xuyanfeng/cc-ui/types/cc-button-group/const";
import { Option } from "@xuyanfeng/cc-ui/types/cc-select/const";
import { ITreeData } from "@xuyanfeng/cc-ui/types/cc-tree/const";
import { _UnwrapAll, Store, storeToRefs } from "pinia";
import { defineComponent, nextTick, onMounted, PropType, reactive, Ref, ref, toRaw, watch } from "vue";
import PluginConfig from "../../../cc-plugin.config";
import { Msg, Page, PluginEvent } from "../../core/types";
import Bus, { BusMsg } from "./bus";
import { connectBackground } from "./connectBackground";
import { EngineData, FrameDetails, Info, NodeInfoData, ObjectData, ObjectItemRequestData, TreeData } from "./data";
import { appStore, RefreshType } from "./store";
import Test from "./test/test.vue";
import Properties from "./ui/propertys.vue";
import SettingsVue from "./ui/settings.vue";
ccui.components.CCAd;
const { CCTree, CCFootBar, CCDialog, CCInput, CCButton, CCInputNumber, CCSelect, CCButtonGroup, CCCheckBox, CCColor, CCDivider } = ccui.components;
interface FrameInfo {
label: string;
value: number;
}
export default defineComponent({
components: { Test, CCFootBar, CCDialog, CCTree, CCDivider, CCButtonGroup, Properties, SettingsVue, CCInput, CCButton, CCInputNumber, CCSelect, CCCheckBox, CCColor },
name: "devtools",
props: {},
setup(props, ctx) {
appStore().init();
const { config } = storeToRefs(appStore());
const treeItemData = ref<NodeInfoData | null>({ uuid: "", group: [] });
const isShowDebug = ref<boolean>(true);
const frameID = ref<number>(0);
const iframes = ref<Array<FrameInfo>>([]);
const btnRefresh: ButtonGroupItem = reactive<ButtonGroupItem>({
icon: "icon_refresh",
click: () => {
onBtnClickUpdateTree();
},
visible: true,
});
const buttonGroup = ref<ButtonGroupItem[]>([
btnRefresh,
{
icon: "icon_settings",
click: () => {
ccui.dialog.showDialog({
comp: SettingsVue,
title: "Settings",
});
},
},
]);
function _checkSelectedUUID() {
if (selectedUUID) {
const b = _findUuidInTree(toRaw(treeData.value), selectedUUID);
if (b) {
return true;
}
}
selectedUUID = null;
treeItemData.value = null;
return false;
}
function _findUuidInTree(data: TreeData[], targetUUID: string) {
function circle(tree: TreeData[]) {
for (let i = 0; i < tree.length; i++) {
let item: TreeData = tree[i];
if (item.id === targetUUID) {
return true;
}
if (circle(item.children || [])) {
return true;
}
}
return false;
}
return circle(data);
}
/**
* 请求属性的列表,如果一个属性请求失败,会阻断后续的相同请求,因为都已经失败了,就没必要再响应请求了
*/
const requestList: Array<{ id: string; cb: Function }> = [];
function _expand(uuid: string) {
let expandKeys: Array<string> = [];
function circle(array: any) {
for (let i = 0; i < array.length; i++) {
let item = array[i];
expandKeys.push(item.uuid);
if (item.uuid === uuid) {
return true;
} else {
let find = circle(item.children);
if (find) {
return true;
} else {
expandKeys.pop();
}
}
}
}
circle(treeData);
expandKeys.forEach((key) => {
if (!expandedKeys.value.find((el) => el === key)) {
expandedKeys.value.push(key);
}
});
// 高亮uuid
}
// 问题没有上下文的权限只能操作DOM
function _executeScript(para: Object) {
// chrome.tabs.executeScript()//v2版本使用的函数
const tabID = chrome.devtools.inspectedWindow.tabId;
chrome.scripting.executeScript({ files: ["js/execute.js"], target: { tabId: tabID } }, (results: chrome.scripting.InjectionResult[]) => {});
}
function _inspectedCode() {
let injectCode = "";
chrome.devtools.inspectedWindow.eval(injectCode, (result, isException) => {
if (isException) {
console.error(isException);
} else {
console.log(`执行结果:${result}`);
}
});
}
const elTree = ref<HTMLElement>();
function _initChromeRuntimeConnect() {
const msgFunctionMap: Record<string, Function> = {};
msgFunctionMap[Msg.TreeInfo] = (data: Array<TreeData>) => {
isShowDebug.value = true;
if (!Array.isArray(data)) {
data = [data];
}
treeData.value = data;
if (_checkSelectedUUID()) {
updateNodeInfo();
nextTick(() => {
if (elTree.value) {
elTree.value.setCurrentKey(selectedUUID);
}
});
}
};
msgFunctionMap[Msg.Support] = (isCocosGame: boolean) => {
isShowDebug.value = isCocosGame;
if (isCocosGame) {
syncSettings();
onBtnClickUpdateTree();
} else {
_clearTimer();
treeData.value.length = 0;
treeItemData.value = null;
selectedUUID = null;
}
};
msgFunctionMap[Msg.NodeInfo] = (eventData: NodeInfoData) => {
isShowDebug.value = true;
treeItemData.value = eventData;
};
msgFunctionMap[Msg.MemoryInfo] = (eventData: any) => {
memory.value = eventData;
};
msgFunctionMap[Msg.UpdateProperty] = (data: Info) => {
const uuid = data.path[0];
const key = data.path[1];
const value = data.data;
let treeArray: Array<TreeData> = [];
function circle(array: Array<TreeData>) {
array.forEach((item) => {
treeArray.push(item);
circle(item.children);
});
}
// 更新指定uuid节点的tree的name
circle(treeData.value);
let ret = treeArray.find((el) => el.id === uuid);
if (ret) {
if (key === "name") {
ret.text = value;
}
if (key === "active") {
ret.active = !!value;
}
}
};
msgFunctionMap[Msg.UpdateFrames] = (details: FrameDetails[]) => {
// 先把iframes里面无效的清空了
iframes.value = iframes.value.filter((item) => {
details.find((el) => el.frameID === item.value);
});
// 同步配置
details.forEach((item) => {
let findItem = iframes.value.find((el) => el.value === item.frameID);
if (findItem) {
findItem.label = item.url;
} else {
iframes.value.push({
label: item.url,
value: item.frameID,
});
}
});
// 第一次获取到frame配置后自动获取frame数据
if (frameID === null && iframes.value.length > 0 && !iframes.value.find((el) => el.value === frameID.value)) {
frameID.value = iframes[0].value;
onChangeFrame();
}
};
msgFunctionMap[Msg.GetObjectItemData] = (requestData: ObjectItemRequestData) => {
if (requestData.id !== null) {
let findIndex = requestList.findIndex((el) => el.id === requestData.id);
if (findIndex > -1) {
let del = requestList.splice(findIndex, 1)[0];
del.cb(requestData.data);
}
}
};
// 接收来自background.js的消息数据
connectBackground.onBackgroundMessage((data: PluginEvent, sender: any) => {
if (!data) {
return;
}
if (data.target === Page.Devtools) {
console.log("[Devtools]", data);
PluginEvent.finish(data);
const { msg } = data;
if (msg) {
const func = msgFunctionMap[msg];
if (func) {
func(data.data);
} else {
console.warn(`没有${msg}消息的函数`);
}
}
}
});
}
_initChromeRuntimeConnect();
window.addEventListener(
"message",
(event) => {
console.log("on vue:" + JSON.stringify(event));
},
false
);
Bus.on(BusMsg.ShowPlace, (data: EngineData) => {
console.log(data);
_expand(data.engineUUID);
});
Bus.on(BusMsg.RequestObjectData, (data: ObjectData, cb: Function) => {
if (!data.id || requestList.find((el) => el.id === data.id)) {
return;
}
requestList.push({ id: data.id, cb });
connectBackground.sendMsgToContentScript(Msg.GetObjectItemData, data);
});
Bus.on(BusMsg.UpdateSettings, () => {
syncSettings();
});
Bus.on(BusMsg.LogData, (data: string[]) => {
connectBackground.sendMsgToContentScript(Msg.LogData, data);
});
onMounted(() => {
syncSettings();
});
const treeData = ref<TreeData[]>([]);
const expandedKeys = ref<Array<string>>([]);
const memory = ref<{
performance: {
jsHeapSizeLimit?: number;
totalJSHeapSize?: number;
usedJSHeapSize?: number;
};
console: Object;
}>({
performance: {},
console: {},
});
// el-tree的渲染key
const defaultProps = ref<{ children: string; label: string }>({
children: "children",
label: "name",
});
const filterText = ref<string>("");
watch(filterText, (val) => {
// TODO: 过滤树
updateFilterText(val);
});
const matchCase = ref<boolean>(false);
function onEnableTreeWatch(watch: boolean, time = 300) {
if (watch) {
_clearTimer();
timerID = setInterval(() => {
onBtnClickUpdateTree();
}, time);
} else {
_clearTimer();
}
}
let timerID: NodeJS.Timer | null = null;
function _clearTimer() {
if (timerID !== null) {
clearInterval(timerID);
timerID = null;
}
}
function syncSettings() {
const { refreshType, refreshTime } = config.value;
switch (refreshType) {
case RefreshType.Auto: {
btnRefresh.visible = false;
onEnableTreeWatch(true, refreshTime);
break;
}
case RefreshType.Manual: {
btnRefresh.visible = true;
onEnableTreeWatch(false);
}
}
}
function updateNodeInfo() {
if (selectedUUID) {
connectBackground.sendMsgToContentScript(Msg.NodeInfo, selectedUUID);
}
}
let selectedUUID: string | null = null;
function updateFilterText(val: any) {
(elTree.value as any)?.filter(val);
}
function onBtnClickUpdateTree() {
connectBackground.sendMsgToContentScript(Msg.TreeInfo, frameID);
}
function onChangeFrame() {
connectBackground.sendMsgToContentScript(Msg.UseFrame, frameID);
}
const elLeft = ref<HTMLDivElement>();
const version = ref(PluginConfig.manifest.version);
return {
version,
buttonGroup,
elTree,
memory,
defaultProps,
filterText,
matchCase,
iframes,
isShowDebug,
expandedKeys,
treeData,
treeItemData,
frameID,
testValidGame(b: boolean) {
isShowDebug.value = !!b;
},
getFramesData(): Option[] {
const frames: FrameInfo[] = toRaw(iframes.value);
const options: Option[] = [];
frames.forEach((frame) => {
options.push({
label: frame.label,
value: frame.value,
});
});
return options;
},
onChangeCase() {
matchCase.value = !matchCase.value;
updateFilterText(filterText);
},
handleNodeClick(data: TreeData) {
selectedUUID = data.id;
updateNodeInfo();
},
// TODO: 暂时这个版本先不实现
filterNode(value: any, data: any) {
if (!value) {
return true;
} else {
if (matchCase) {
// 严格匹配大写
return data?.name?.indexOf(value) !== -1;
} else {
return data?.name?.toLowerCase().indexOf(value.toLowerCase()) !== -1;
}
}
},
onBtnClickUpdatePage() {
connectBackground.sendMsgToContentScript(Msg.Support);
},
onMemoryTest() {
connectBackground.sendMsgToContentScript(Msg.MemoryInfo);
},
onChangeFrame,
onNodeExpand(data: TreeData) {
if (data.id) {
expandedKeys.value.push(data.id);
}
},
onNodeCollapse(data: TreeData) {
if (data.id) {
const keys = toRaw(expandedKeys.value);
const index = keys.findIndex((el) => el === data.id);
if (index !== -1) {
keys.splice(index, 1);
}
expandedKeys.value = keys;
}
},
};
},
});
</script>
<style scoped lang="less">
#devtools {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #5c5c5c;
color: white;
.head {
display: flex;
flex-direction: row;
align-items: center;
padding: 2px 0;
border-bottom: solid 1px grey;
.label {
color: white;
font-size: 12px;
margin: 0 3px;
margin-right: 5px;
user-select: none;
}
}
.no-find {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
justify-content: center;
user-select: none;
span {
margin-right: 20px;
color: white;
font-size: 20px;
user-select: none;
}
.fresh {
cursor: pointer;
color: white;
font-size: 20px;
&:hover {
color: #cef57b;
}
&:active {
color: #ffaa00;
}
}
}
.find {
display: flex;
flex: 1;
flex-direction: row;
overflow: auto;
.left {
display: flex;
flex-direction: column;
min-width: 200px;
width: 300px;
.tool-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.text {
padding-left: 5px;
flex: 1;
user-select: none;
font-size: 12px;
color: white;
}
}
.matchCase {
width: 30px;
height: 26px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
.right {
flex: 1;
overflow-x: hidden;
overflow-y: overlay;
&::-webkit-scrollbar {
width: 6px;
background: #999;
border-radius: 2px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background-color: #333;
border-radius: 2px;
}
}
}
}
</style>