This commit is contained in:
xu_yanfeng 2024-01-09 12:02:47 +08:00
parent a082bda2ae
commit 83798ff401
16 changed files with 1219 additions and 1157 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ cc-inspector/node_modules/
cc-inspector/yalc.lock cc-inspector/yalc.lock
cc-inspector/chrome/ cc-inspector/chrome/
cc-inspector/dist/ cc-inspector/dist/
cc-inspector/yarn-error.log

14
cc-inspector/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "modifications",
"files.autoSave": "onFocusChange",
"[typescript]": {
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.refactoring.autoSave": false
}

View File

@ -26,6 +26,7 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"uuid": "^8.3.2", "uuid": "^8.3.2",
"tiny-emitter": "2.1.0",
"lodash": "^4.17.21" "lodash": "^4.17.21"
} }
} }

View File

@ -1,4 +1,4 @@
import Vue from "vue" import { TinyEmitter } from 'tiny-emitter';
export enum BusMsg { export enum BusMsg {
ShowPlace = "ShowPlace", ShowPlace = "ShowPlace",
@ -7,4 +7,4 @@ export enum BusMsg {
FoldAllGroup = "FoldAllGroup" FoldAllGroup = "FoldAllGroup"
} }
export default new Vue(); export default new TinyEmitter();

View File

@ -1,4 +1,4 @@
import {PluginEvent, Page, Msg} from "@/core/types"; import {PluginEvent, Page, Msg} from "../../core/types";
class ConnectBackground { class ConnectBackground {
connect: chrome.runtime.Port | null = null; connect: chrome.runtime.Port | null = null;

View File

@ -1,21 +1,20 @@
// @ts-ignore import { v4 } from "uuid"
import {v4} from "uuid"
export enum DataType { export enum DataType {
Number, Number = 'Number',
String, String = 'String',
Text, Text = 'Text',
Vec2, Vec2 = 'Vec2',
Vec3, Vec3 = 'Vec3',
Enum, Enum = 'Enum',
Bool, Bool = 'Bool',
Color, Color = 'Color',
Invalid, Invalid = 'Invalid',
Array, // 暂时在控制台打印下 Array = 'Array', // 暂时在控制台打印下
Object, Object = 'Object',
ObjectItem, ObjectItem = 'ObjectItem',
Image, // 图片 Image = 'Image', // 图片
Engine,// 引擎的类型cc.Node, cc.Sprite, cc.Label等。。。 Engine = 'Engine',// 引擎的类型cc.Node, cc.Sprite, cc.Label等。。。
} }
export class Info { export class Info {
@ -27,6 +26,9 @@ export class Info {
constructor() { constructor() {
this.id = v4(); this.id = v4();
} }
public isEnum(): boolean {
return false;
}
} }
export class TextData extends Info { export class TextData extends Info {
@ -169,10 +171,14 @@ export class ImageData extends Info {
} }
export class EnumData extends Info { export class EnumData extends Info {
public values: Array<{ name: string, value: any }> = [];
constructor() { constructor() {
super(); super();
this.type = DataType.Enum; this.type = DataType.Enum;
} }
public isEnum(): boolean {
return this.type === DataType.Enum;
}
} }
export class TreeData { export class TreeData {
@ -197,9 +203,9 @@ export class Group {
public name: string = "group"; public name: string = "group";
public data: Array<Property> = []; public data: Array<Property> = [];
constructor(name: string,id?:string) { constructor(name: string, id?: string) {
this.name = name; this.name = name;
this.id=id||''; this.id = id || '';
} }
addProperty(property: Property) { addProperty(property: Property) {

View File

@ -1,20 +1,656 @@
<template> <template>
<div class="devtools">devtools</div> <div id="devtools">
</template> <!-- <el-drawer title="settings" direction="btt" @close="onCloseSettings" :visible.sync="showSettings">
<script lang="ts"> <div>
import { defineComponent, onMounted, ref, provide, nextTick } from "vue"; <SettingsVue></SettingsVue>
export default defineComponent({ </div>
name: "devtools", </el-drawer> -->
components: {}, <div class="head" v-show="iframes.length > 1">
setup(props, ctx) { <div class="label">inspect target:</div>
return {}; <el-select
v-model="frameID"
placeholder="please select ..."
@change="onChangeFrame"
style="flex: 1"
>
<el-option
v-for="item in iframes"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div v-show="isShowDebug" class="find">
<div v-if="false">
<el-button type="success" @click="onMemoryTest">内存测试</el-button>
</div>
<div v-if="false">
<span>JS堆栈限制: {{ memory.performance.jsHeapSizeLimit }}</span>
<span>JS堆栈大小: {{ memory.performance.totalJSHeapSize }}</span>
<span>JS堆栈使用: {{ memory.performance.usedJSHeapSize }}</span>
</div>
<div ref="left" class="left">
<div class="tool-btn">
<div style="padding-left: 15px; flex: 1">Node Tree</div>
<el-button
v-show="isShowRefreshBtn"
type="success"
class="el-icon-refresh"
size="mini"
@click="onBtnClickUpdateTree"
></el-button>
<el-button
@click="onClickSettings"
class="el-icon-s-tools"
></el-button>
</div>
<!-- <el-input placeholder="enter keywords to filter" v-model="filterText">
<template slot="append">
<div class="matchCase">
<div
class="iconfont el-icon-third-font-size"
@click.stop="onChangeCase"
title="match case"
:style="{ color: matchCase ? 'red' : '' }"
></div>
</div>
</template>
</el-input> -->
<div class="treeList">
<!-- <el-tree
:data="treeData"
ref="tree"
style="display: inline-block"
:props="defaultProps"
:highlight-current="true"
:default-expand-all="false"
:default-expanded-keys="expandedKeys"
:filter-node-method="filterNode"
:expand-on-click-node="false"
node-key="uuid"
@node-expand="onNodeExpand"
@node-collapse="onNodeCollapse"
@node-click="handleNodeClick"
>
<span
slot-scope="{ node, data }"
class="leaf"
:class="data.active ? 'leaf-show' : 'leaf-hide'"
>
<span>{{ node.label }}</span>
</span>
</el-tree> -->
</div>
</div>
<ui-divider ref="divider" @move="onDividerMove"></ui-divider>
<div ref="right" 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>
<el-button
type="success"
class="el-icon-refresh"
@click="onBtnClickUpdatePage"
>刷新</el-button
>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, onMounted, watch } from "vue";
import properties from "./ui/propertys.vue";
import { Msg, Page, PluginEvent } from "../../core/types";
import { connectBackground } from "./connectBackground";
import {
EngineData,
FrameDetails,
Info,
NodeInfoData,
ObjectData,
ObjectItemRequestData,
TreeData,
} from "./data";
import Bus, { BusMsg } from "./bus";
import SettingsVue from "./ui/settings.vue";
import { RefreshType, settings } from "./settings";
import UiDivider from "./ui/ui-divider.vue";
export default defineComponent({
components: { UiDivider, properties, SettingsVue },
name: "devtools",
props: {
isShowDebug: { type: Boolean, default: false },
treeItemData: { type: Object as PropType<NodeInfoData>, default: () => {} },
isShowRefreshBtn: { type: Boolean, default: false },
showSettings: { type: Boolean, default: false },
iframes: {
type: Array as PropType<Array<{ label: string; value: number }>>,
default: () => [],
}, },
}); frameID: { type: Number, default: 0 },
</script> },
<style scoped lang="less"> setup(props, ctx) {
.devtools { function _checkSelectedUUID() {
widows: 10px; if (this.selectedUUID) {
height: 10px; const b = this._findUuidInTree(this.treeData, this.selectedUUID);
background-color: rebeccapurple; if (b) {
return true;
}
}
this.selectedUUID = null;
this.treeItemData = 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.uuid === targetUUID) {
return true;
}
if (circle(item.children)) {
return true;
}
}
return false;
}
return circle(data);
}
function sendMsgToContentScript(msg: Msg, data?: any) {
if (!chrome || !chrome.devtools) {
console.log("环境异常,无法执行函数");
return;
}
connectBackground.postMessageToBackground(msg, 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(this.treeData);
expandKeys.forEach((key) => {
if (!this.expandedKeys.find((el) => el === key)) {
this.expandedKeys.push(key);
}
});
// uuid
}
function _onMsgNodeInfo(eventData: NodeInfoData) {
this.isShowDebug = true;
this.treeItemData = eventData;
}
function _onMsgMemoryInfo(eventData: any) {
this.memory = eventData;
}
function _onMsgSupport(isCocosGame: boolean) {
this.isShowDebug = isCocosGame;
if (isCocosGame) {
syncSettings();
this.onBtnClickUpdateTree();
} else {
this._clearTimer();
this.treeData = [];
this.treeItemData = null;
this.selectedUUID = null;
}
}
function _onMsgGetObjectItemData(requestData: ObjectItemRequestData) {
if (requestData.id !== null) {
let findIndex = this.requestList.findIndex(
(el) => el.id === requestData.id
);
if (findIndex > -1) {
let del = this.requestList.splice(findIndex, 1)[0];
del.cb(requestData.data);
}
}
}
function _onMsgUpdateFrames(details: FrameDetails[]) {
// iframes
this.iframes = this.iframes.filter((item) => {
details.find((el) => el.frameID === item.value);
});
//
details.forEach((item) => {
let findItem = this.iframes.find((el) => el.value === item.frameID);
if (findItem) {
findItem.label = item.url;
} else {
this.iframes.push({
label: item.url,
value: item.frameID,
});
}
});
// frameframe
if (
this.frameID === null &&
this.iframes.length > 0 &&
!this.iframes.find((el) => el.value === this.frameID)
) {
this.frameID = this.iframes[0].value;
this.onChangeFrame();
}
}
function _onMsgUpdateProperty(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);
});
}
// uuidtreename
circle(this.treeData);
let ret = treeArray.find((el) => el.uuid === uuid);
if (ret) {
if (key === "name") {
ret.name = value;
}
if (key === "active") {
ret.active = !!value;
}
}
}
// DOM
function _executeScript(para: Object) {
let tabID = chrome.devtools.inspectedWindow.tabId;
//@ts-ignore
chrome.tabs.executeScript(
tabID,
{ code: `var CCInspectorPara='${JSON.stringify(para)}';` },
() => {
//@ts-ignore
chrome.tabs.executeScript(tabID, { file: "js/execute.js" });
}
);
}
function _inspectedCode() {
let injectCode = "";
chrome.devtools.inspectedWindow.eval(
injectCode,
(result, isException) => {
if (isException) {
console.error(isException);
} else {
console.log(`执行结果:${result}`);
}
}
);
}
function _onMsgTreeInfo(treeData: Array<TreeData>) {
this.isShowDebug = true;
if (!Array.isArray(treeData)) {
treeData = [treeData];
}
this.treeData = treeData;
if (this._checkSelectedUUID()) {
this.updateNodeInfo();
this.$nextTick(() => {
//@ts-ignore
this.$refs.tree.setCurrentKey(this.selectedUUID);
});
}
}
function _initChromeRuntimeConnect() {
const msgFunctionMap: Record<string, Function> = {};
msgFunctionMap[Msg.TreeInfo] = this._onMsgTreeInfo;
msgFunctionMap[Msg.Support] = this._onMsgSupport;
msgFunctionMap[Msg.NodeInfo] = this._onMsgNodeInfo;
msgFunctionMap[Msg.MemoryInfo] = this._onMsgMemoryInfo;
msgFunctionMap[Msg.UpdateProperty] = this._onMsgUpdateProperty;
msgFunctionMap[Msg.UpdateFrames] = this._onMsgUpdateFrames;
msgFunctionMap[Msg.GetObjectItemData] = this._onMsgGetObjectItemData;
// 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}消息的函数`);
}
}
}
}
);
}
if (chrome && chrome.runtime) {
_initChromeRuntimeConnect();
}
window.addEventListener(
"message",
function (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 });
sendMsgToContentScript(Msg.GetObjectItemData, data);
});
Bus.on(BusMsg.LogData, (data: string[]) => {
sendMsgToContentScript(Msg.LogData, data);
});
onMounted(() => {
syncSettings();
});
const treeData = ref<Array<TreeData>>([]);
const expandedKeys = ref<Array<string>>([]);
const memory = ref<{
performance: {
jsHeapSizeLimit?: number;
totalJSHeapSize?: number;
usedJSHeapSize?: number;
};
console: Object;
}>({
performance: {},
console: {},
});
// el-treekey
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) {
this._clearTimer();
this.timerID = setInterval(() => {
this.onBtnClickUpdateTree();
}, time);
} else {
this._clearTimer();
}
}
let timerID: number | null = null;
function _clearTimer() {
if (this.timerID !== null) {
clearInterval(this.timerID);
this.timerID = null;
}
}
function syncSettings() {
if (settings.data) {
const { refreshType, refreshTime } = settings.data;
switch (refreshType) {
case RefreshType.Auto: {
this.isShowRefreshBtn = false;
this.onEnableTreeWatch(true, refreshTime);
break;
}
case RefreshType.Manual: {
this.isShowRefreshBtn = true;
this.onEnableTreeWatch(false);
}
}
}
}
function updateNodeInfo() {
if (this.selectedUUID) {
sendMsgToContentScript(Msg.NodeInfo, this.selectedUUID);
}
}
let selectedUUID: string | null = null;
function updateFilterText(val: any) {
(this.$refs?.tree as any)?.filter(val);
}
return {
memory,
defaultProps,
filterText,
matchCase,
expandedKeys,
treeData,
onChangeCase() {
this.matchCase = !this.matchCase;
updateFilterText(this.filterText);
},
handleNodeClick(data: TreeData) {
this.selectedUUID = data.uuid;
this.updateNodeInfo();
},
filterNode(value: any, data: any) {
if (!value) {
return true;
} else {
if (this.matchCase) {
//
return data?.name?.indexOf(value) !== -1;
} else {
return (
data?.name?.toLowerCase().indexOf(value.toLowerCase()) !== -1
);
}
}
},
onDividerMove(event: MouseEvent) {
const leftDiv: HTMLDivElement = this.$refs.left as HTMLDivElement;
if (leftDiv) {
let width = leftDiv.clientWidth;
width += event.movementX;
if (width >= 300 && width < document.body.clientWidth - 100) {
leftDiv.style.width = `${width}px`;
}
}
},
onBtnClickUpdateTree() {
sendMsgToContentScript(Msg.TreeInfo, this.frameID);
},
onBtnClickUpdatePage() {
sendMsgToContentScript(Msg.Support);
},
onCloseSettings() {
syncSettings();
},
onMemoryTest() {
sendMsgToContentScript(Msg.MemoryInfo);
},
onClickSettings() {
this.showSettings = true;
},
onChangeFrame() {
sendMsgToContentScript(Msg.UseFrame, this.frameID);
},
onNodeExpand(data: TreeData) {
if (data.hasOwnProperty("uuid") && data.uuid) {
this.expandedKeys.push(data.uuid);
}
},
onNodeCollapse(data: TreeData) {
if (data.hasOwnProperty("uuid")) {
let index = this.expandedKeys.findIndex((el) => el === data.uuid);
if (index !== -1) {
this.expandedKeys.splice(index, 1);
}
}
},
};
},
});
</script>
<style scoped lang="less">
@import "../../index.less";
#devtools {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
.head {
display: flex;
flex-direction: row;
align-items: center;
padding: 1px 0;
border-bottom: solid 1px grey;
.label {
margin: 0 3px;
}
} }
</style>
.no-find {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
justify-content: center;
span {
margin-right: 20px;
}
}
.find {
display: flex;
flex: 1;
flex-direction: row;
overflow: auto;
.left {
display: flex;
flex-direction: column;
width: 300px;
.tool-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.matchCase {
width: 30px;
height: 26px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.treeList {
margin-top: 3px;
height: 100%;
border-radius: 4px;
min-height: 20px;
overflow: auto;
width: 100%;
.leaf {
width: 100%;
}
.leaf-show {
color: black;
}
.leaf-hide {
color: #c7bbbb;
text-decoration: line-through;
}
&::-webkit-scrollbar {
width: 6px;
height: 6px;
background: #999;
border-radius: 2px;
}
&::-webkit-scrollbar-thumb {
background-color: #333;
border-radius: 2px;
}
}
}
.right {
flex: 1;
background: #e5e9f2;
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>

View File

@ -1,33 +1,31 @@
import Manifest from '../manifest.json' import { ChromeConst } from "cc-plugin/src/chrome/const";
import {connectBackground} from "@/devtools/connectBackground"; import { connectBackground } from "./connectBackground";
import {PluginEvent, Msg, Page} from "@/core/types"; import { PluginEvent, Msg, Page } from "../../core/types";
export function init() { export function init() {
if (chrome && chrome.devtools) { if (chrome && chrome.devtools) {
// 对应的是Elements面板的边栏 // 对应的是Elements面板的边栏
chrome.devtools.panels.elements.createSidebarPane('Cocos', function (sidebar) { chrome.devtools.panels.elements.createSidebarPane('Cocos', function (sidebar) {
sidebar.setObject({some_data: "some data to show!"}); sidebar.setObject({ some_data: "some data to show!" });
}); });
// 创建devtools-panel // 创建devtools-panel
chrome.devtools.panels.create("Cocos", "icons/48.png", Manifest.devtools_page, (panel: chrome.devtools.panels.ExtensionPanel) => { chrome.devtools.panels.create("Cocos", "icons/48.png", ChromeConst.html.devtools, (panel: chrome.devtools.panels.ExtensionPanel) => {
console.log("[CC-Inspector] Dev Panel Created!"); console.log("[CC-Inspector] Dev Panel Created!");
panel.onShown.addListener((window) => { panel.onShown.addListener((window) => {
// 面板显示查询是否是cocos游戏 // 面板显示查询是否是cocos游戏
console.log("panel show"); console.log("panel show");
// connectBackground.postMessageToBackground(Msg.Support, null) // connectBackground.postMessageToBackground(Msg.Support, null)
}); });
panel.onHidden.addListener(() => { panel.onHidden.addListener(() => {
// 面板隐藏 // 面板隐藏
console.log("panel hide"); console.log("panel hide");
}); });
panel.onSearch.addListener(function (action, query) { panel.onSearch.addListener(function (action, query) {
// ctrl+f 查找触发 // ctrl+f 查找触发
console.log("panel search!"); console.log("panel search!");
}); });
} }
); );
} }
}
}

View File

@ -1,7 +1,9 @@
const Key = "settings"; const Key = "settings";
export const RefreshManual = "manual"; export const enum RefreshType{
export const RefreshAuto = "auto"; Auto = "auto",
Manual = "manual",
}
interface SettingsData { interface SettingsData {
refreshType: string; refreshType: string;
@ -10,7 +12,7 @@ interface SettingsData {
let defaultData: SettingsData = { let defaultData: SettingsData = {
refreshTime: 500, refreshTime: 500,
refreshType: RefreshManual, refreshType: RefreshType.Manual,
} }
class Settings { class Settings {
@ -33,7 +35,7 @@ class Settings {
} }
isManualRefresh() { isManualRefresh() {
return this.data?.refreshType === RefreshManual; return this.data?.refreshType === RefreshType.Manual;
} }
save() { save() {

View File

@ -1,631 +0,0 @@
<template>
<div id="devtools">
<el-drawer
title="settings"
direction="btt"
@close="onCloseSettings"
:visible.sync="showSettings">
<div>
<settings-vue></settings-vue>
</div>
</el-drawer>
<div class="head" v-show="iframes.length>1">
<div class="label">inspect target:</div>
<el-select v-model="frameID" placeholder="please select ..." @change="onChangeFrame" style="flex:1;">
<el-option v-for="item in iframes" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div v-show="isShowDebug" class="find">
<div v-if="false">
<el-button type="success" @click="onMemoryTest">内存测试</el-button>
</div>
<div v-if="false">
<span>JS堆栈限制: {{ memory.performance.jsHeapSizeLimit }}</span>
<span>JS堆栈大小: {{ memory.performance.totalJSHeapSize }}</span>
<span>JS堆栈使用: {{ memory.performance.usedJSHeapSize }}</span>
</div>
<div ref="left" class="left">
<div class="tool-btn">
<div style="padding-left: 15px;flex:1;">Node Tree</div>
<el-button v-show="isShowRefreshBtn" type="success" class="el-icon-refresh"
size="mini"
@click="onBtnClickUpdateTree"></el-button>
<el-button @click="onClickSettings" class="el-icon-s-tools"></el-button>
</div>
<el-input placeholder="enter keywords to filter" v-model="filterText">
<template slot="append">
<div class="matchCase ">
<div class="iconfont el-icon-third-font-size" @click.stop="onChangeCase"
title="match case"
:style="{'color':matchCase?'red':''}">
</div>
</div>
</template>
</el-input>
<div class="treeList">
<el-tree :data="treeData"
ref="tree"
style="display: inline-block;"
:props="defaultProps"
:highlight-current="true"
:default-expand-all="false"
:default-expanded-keys="expandedKeys"
:filter-node-method="filterNode"
:expand-on-click-node="false"
node-key="uuid"
@node-expand="onNodeExpand"
@node-collapse="onNodeCollapse"
@node-click="handleNodeClick">
<!-- :render-content="renderContent"-->
<span slot-scope="{node,data}" class="leaf" :class="data.active?'leaf-show':'leaf-hide'">
<span>{{ node.label }}</span>
<!-- <el-button v-if="!!data||true"> 显示</el-button>-->
</span>
</el-tree>
</div>
</div>
<ui-divider ref="divider" @move="onDividerMove"></ui-divider>
<div ref="right" 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>
<el-button type="success" class="el-icon-refresh" @click="onBtnClickUpdatePage">刷新</el-button>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import {Component, Watch} from "vue-property-decorator";
import properties from "./propertys.vue";
import {Msg, Page, PluginEvent} from "@/core/types"
import {connectBackground} from "@/devtools/connectBackground";
import {
EngineData,
FrameDetails,
Info,
NodeInfoData,
ObjectData,
ObjectItemRequestData,
TreeData
} from "@/devtools/data";
import Bus, {BusMsg} from "@/devtools/bus";
import settingsVue from "./settings.vue"
import {RefreshAuto, RefreshManual, settings} from "@/devtools/settings";
import UiDivider from "@/devtools/ui/ui-divider.vue";
@Component({
components: {
UiDivider,
properties,
settingsVue,
}
})
export default class Index extends Vue {
private isShowDebug: boolean = false;
treeItemData: NodeInfoData | null = null;
treeData: Array<TreeData> = []
expandedKeys: Array<string> = [];
selectedUUID: string | null = null;
filterText: string | null = null;
iframes: Array<{ label: string, value: number }> = []
frameID: number | null = null;
@Watch("filterText")
updateFilterText(val: any) {
(this.$refs?.tree as any)?.filter(val);
}
private matchCase = false;
onChangeCase() {
this.matchCase = !this.matchCase;
this.updateFilterText(this.filterText);
}
private showSettings = false;
onClickSettings() {
this.showSettings = true;
}
onDividerMove(event: MouseEvent) {
const leftDiv: HTMLDivElement = this.$refs.left as HTMLDivElement;
if (leftDiv) {
let width = leftDiv.clientWidth;
width += event.movementX;
if (width >= 300 && width < document.body.clientWidth - 100) {
leftDiv.style.width = `${width}px`;
}
}
}
private syncSettings() {
if (settings.data) {
const {refreshType, refreshTime} = settings.data;
switch (refreshType) {
case RefreshAuto: {
this.isShowRefreshBtn = false;
this.onEnableTreeWatch(true, refreshTime)
break;
}
case RefreshManual: {
this.isShowRefreshBtn = true;
this.onEnableTreeWatch(false)
}
}
}
}
private isShowRefreshBtn = false;
onCloseSettings() {
this.syncSettings();
}
// el-treekey
defaultProps = {
children: "children",
label: "name"
};
memory = {
performance: {},
console: {},
}
timerID: number | null = null;
private requestList: Array<{ id: string, cb: Function }> = [];
created() {
if (chrome && chrome.runtime) {
this._initChromeRuntimeConnect();
}
window.addEventListener("message", function (event) {
console.log("on vue:" + JSON.stringify(event));
}, false);
Bus.$on(BusMsg.ShowPlace, (data: EngineData) => {
console.log(data)
this._expand(data.engineUUID);
})
Bus.$on(BusMsg.RequestObjectData, (data: ObjectData, cb: Function) => {
if (!data.id || this.requestList.find(el => el.id === data.id)) {
return
}
this.requestList.push({id: data.id, cb});
this.sendMsgToContentScript(Msg.GetObjectItemData, data)
});
Bus.$on(BusMsg.LogData, (data: string[]) => {
this.sendMsgToContentScript(Msg.LogData, data);
})
}
filterNode(value: any, data: any) {
if (!value) {
return true;
} else {
if (this.matchCase) {
//
return data?.name?.indexOf(value) !== -1;
} else {
return data?.name?.toLowerCase().indexOf(value.toLowerCase()) !== -1;
}
}
}
onChangeFrame() {
this.sendMsgToContentScript(Msg.UseFrame, this.frameID)
}
_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(this.treeData)
expandKeys.forEach(key => {
if (!this.expandedKeys.find(el => el === key)) {
this.expandedKeys.push(key)
}
})
// uuid
}
renderContent(h: Function, options: any) {
let {node, data, store} = options;
return h("span", {class: ""}, data.name)
// return(<span>1111</span>)
}
_onMsgTreeInfo(treeData: Array<TreeData>) {
this.isShowDebug = true;
if (!Array.isArray(treeData)) {
treeData = [treeData]
}
this.treeData = treeData;
if (this._checkSelectedUUID()) {
this.updateNodeInfo();
this.$nextTick(() => {
//@ts-ignore
this.$refs.tree.setCurrentKey(this.selectedUUID);
})
}
}
_checkSelectedUUID() {
if (this.selectedUUID) {
const b = this._findUuidInTree(this.treeData, this.selectedUUID)
if (b) {
return true;
}
}
this.selectedUUID = null;
this.treeItemData = null;
return false;
}
_findUuidInTree(data: TreeData[], targetUUID: string) {
function circle(tree: TreeData[]) {
for (let i = 0; i < tree.length; i++) {
let item: TreeData = tree[i];
if (item.uuid === targetUUID) {
return true;
}
if (circle(item.children)) {
return true;
}
}
return false;
}
return circle(data)
}
_onMsgNodeInfo(eventData: NodeInfoData) {
this.isShowDebug = true;
this.treeItemData = eventData;
}
_onMsgMemoryInfo(eventData: any) {
this.memory = eventData;
}
_onMsgSupport(isCocosGame: boolean) {
this.isShowDebug = isCocosGame;
if (isCocosGame) {
this.syncSettings();
this.onBtnClickUpdateTree();
} else {
this._clearTimer();
this.treeData = [];
this.treeItemData = null;
this.selectedUUID = null;
}
}
mounted() {
this.syncSettings();
}
_initChromeRuntimeConnect() {
const msgFunctionMap: Record<string, Function> = {};
msgFunctionMap[Msg.TreeInfo] = this._onMsgTreeInfo;
msgFunctionMap[Msg.Support] = this._onMsgSupport;
msgFunctionMap[Msg.NodeInfo] = this._onMsgNodeInfo;
msgFunctionMap[Msg.MemoryInfo] = this._onMsgMemoryInfo;
msgFunctionMap[Msg.UpdateProperty] = this._onMsgUpdateProperty;
msgFunctionMap[Msg.UpdateFrames] = this._onMsgUpdateFrames;
msgFunctionMap[Msg.GetObjectItemData] = this._onMsgGetObjectItemData;
// 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}消息的函数`)
}
}
}
});
}
_onMsgGetObjectItemData(requestData: ObjectItemRequestData) {
if (requestData.id !== null) {
let findIndex = this.requestList.findIndex(el => el.id === requestData.id)
if (findIndex > -1) {
let del = this.requestList.splice(findIndex, 1)[0];
del.cb(requestData.data);
}
}
}
_onMsgUpdateFrames(details: FrameDetails[]) {
// iframes
this.iframes = this.iframes.filter(item => {
details.find(el => el.frameID === item.value)
})
//
details.forEach(item => {
let findItem = this.iframes.find(el => el.value === item.frameID);
if (findItem) {
findItem.label = item.url;
} else {
this.iframes.push({
label: item.url,
value: item.frameID,
})
}
})
// frameframe
if (this.frameID === null && this.iframes.length > 0 && !this.iframes.find(el => el.value === this.frameID)) {
this.frameID = this.iframes[0].value;
this.onChangeFrame();
}
}
_onMsgUpdateProperty(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);
})
}
// uuidtreename
circle(this.treeData)
let ret = treeArray.find(el => el.uuid === uuid);
if (ret) {
if (key === "name") {
ret.name = value;
}
if (key === "active") {
ret.active = !!value;
}
}
}
handleNodeClick(data: TreeData) {
this.selectedUUID = data.uuid;
this.updateNodeInfo();
}
private updateNodeInfo() {
if (this.selectedUUID) {
this.sendMsgToContentScript(Msg.NodeInfo, this.selectedUUID);
}
}
onEnableTreeWatch(watch: boolean, time = 300) {
if (watch) {
this._clearTimer();
this.timerID = setInterval(() => {
this.onBtnClickUpdateTree();
}, time);
} else {
this._clearTimer();
}
}
private _clearTimer() {
if (this.timerID !== null) {
clearInterval(this.timerID);
this.timerID = null;
}
}
sendMsgToContentScript(msg: Msg, data?: any) {
if (!chrome || !chrome.devtools) {
console.log("环境异常,无法执行函数");
return;
}
connectBackground.postMessageToBackground(msg, data);
}
// DOM
_executeScript(para: Object) {
let tabID = chrome.devtools.inspectedWindow.tabId;
//@ts-ignore
chrome.tabs.executeScript(tabID, {code: `var CCInspectorPara='${JSON.stringify(para)}';`}, () => {
//@ts-ignore
chrome.tabs.executeScript(tabID, {file: "js/execute.js"})
});
}
_inspectedCode() {
let injectCode = "";
chrome.devtools.inspectedWindow.eval(injectCode, (result, isException) => {
if (isException) {
console.error(isException);
} else {
console.log(`执行结果:${result}`)
}
});
}
onBtnClickUpdateTree() {
this.sendMsgToContentScript(Msg.TreeInfo, this.frameID);
}
onBtnClickUpdatePage() {
this.sendMsgToContentScript(Msg.Support);
}
onMemoryTest() {
this.sendMsgToContentScript(Msg.MemoryInfo);
}
onNodeExpand(data: TreeData) {
if (data.hasOwnProperty("uuid") && data.uuid) {
this.expandedKeys.push(data.uuid)
}
}
onNodeCollapse(data: TreeData) {
if (data.hasOwnProperty("uuid")) {
let index = this.expandedKeys.findIndex(el => el === data.uuid);
if (index !== -1) {
this.expandedKeys.splice(index, 1)
}
}
}
}
</script>
<style scoped lang="less">
@import "../../index.less";
#devtools {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
.head {
display: flex;
flex-direction: row;
align-items: center;
padding: 1px 0;
border-bottom: solid 1px grey;
.label {
margin: 0 3px;
}
}
.no-find {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
justify-content: center;
span {
margin-right: 20px;
}
}
.find {
display: flex;
flex: 1;
flex-direction: row;
overflow: auto;
.left {
display: flex;
flex-direction: column;
width: 300px;
.tool-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.matchCase {
width: 30px;
height: 26px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.treeList {
margin-top: 3px;
height: 100%;
border-radius: 4px;
min-height: 20px;
overflow: auto;
width: 100%;
.leaf {
width: 100%;
}
.leaf-show {
color: black;
}
.leaf-hide {
color: #c7bbbb;
text-decoration: line-through;
}
&::-webkit-scrollbar {
width: 6px;
height: 6px;
background: #999;
border-radius: 2px;
}
&::-webkit-scrollbar-thumb {
background-color: #333;
border-radius: 2px;
}
}
}
.right {
flex: 1;
background: #e5e9f2;
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>

View File

@ -1,67 +1,75 @@
<template> <template>
<div class="property-group"> <div class="property-group">
<div class="header" @click="onClickHeader" <div
@mouseenter="showLogBtn=true" class="header"
@mouseleave="showLogBtn=false"> @click="onClickHeader"
<div style="margin: 0 5px;"> @mouseenter="showLogBtn = true"
@mouseleave="showLogBtn = false"
>
<div style="margin: 0 5px">
<i v-if="fold" class="el-icon-caret-right"></i> <i v-if="fold" class="el-icon-caret-right"></i>
<i v-if="!fold" class="el-icon-caret-bottom"></i> <i v-if="!fold" class="el-icon-caret-bottom"></i>
</div> </div>
<div style="flex:1;"> <div style="flex: 1">
{{ group.name }} {{ group.name }}
</div> </div>
<el-button style="margin-right: 10px;" <el-button
v-show="showLogBtn" style="margin-right: 10px"
type="success" icon="el-icon-chat-dot-round" @click.stop="onLog"> v-show="showLogBtn"
type="success"
icon="el-icon-chat-dot-round"
@click.stop="onLog"
>
</el-button> </el-button>
</div> </div>
<div class="content" v-show="!fold"> <div class="content" v-show="!fold">
<ui-prop v-for="(item, index) in group.data" :key="index" <ui-prop
:name="item.name" :value="item.value"> v-for="(item, index) in group.data"
:key="index"
:name="item.name"
:value="item.value"
>
</ui-prop> </ui-prop>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import { defineComponent, ref, PropType } from "vue";
import Component from "vue-class-component"; import { Group } from "../data";
import {Prop} from "vue-property-decorator"; import UiProp from "./ui-prop.vue";
import {Group} from "@/devtools/data"; import Bus, { BusMsg } from "../bus";
import UiProp from "@/devtools/ui/ui-prop.vue";
import Bus, {BusMsg} from "@/devtools/bus";
@Component({ export default defineComponent({
name: "property-group", name: "property-group",
components: {UiProp} components: { UiProp },
}) props: {
export default class PropertyGroup extends Vue { group: {
private fold = false; type: Object as PropType<Group>,
private showLogBtn = false; default: () => {
@Prop({ return new Group("test");
default: () => { },
return new Group("test") },
} },
}) setup(props, context) {
group!: Group; Bus.on(BusMsg.FoldAllGroup, (b: boolean) => {
fold.value = b;
});
const fold = ref(false);
const showLogBtn = ref(false);
return {
showLogBtn,
fold,
onLog() {
Bus.emit(BusMsg.LogData, [props.group.id]);
},
created() { onClickHeader() {
Bus.$on(BusMsg.FoldAllGroup, (b: boolean) => { fold.value = !fold.value;
this.fold = b; },
}) };
} },
});
mounted() {
}
onLog() {
Bus.$emit(BusMsg.LogData, [this.group.id]);
}
onClickHeader() {
this.fold = !this.fold;
}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,54 +1,50 @@
<template> <template>
<div id="prop"> <div id="prop">
<property-group v-for="(group, index) in data.group" :key="index" :group="group"></property-group> <PropertyGroup
v-for="(group, index) in data.group"
:key="index"
:group="group"
></PropertyGroup>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue" import { defineComponent, PropType, watch } from "vue";
import UiProp from "./ui-prop.vue";
import { Group, NodeInfoData } from "../data";
import PropertyGroup from "../ui/property-group.vue";
import Bus, { BusMsg } from "../bus";
import {Component, Prop, Watch} from "vue-property-decorator" export default defineComponent({
import UiProp from "./ui-prop.vue" components: { PropertyGroup, UiProp },
import {Group, NodeInfoData} from "@/devtools/data"; props: {
import PropertyGroup from "@/devtools/ui/property-group.vue"; data: {
import Bus, {BusMsg} from "@/devtools/bus"; type: Object as PropType<NodeInfoData>,
default: () => {
@Component({ return {};
components: {PropertyGroup, UiProp}, },
}) },
export default class properties extends Vue { },
@Prop({ setup(props, context) {
default: () => { function _evalCode(code: string) {
return {}; if (chrome && chrome.devtools) {
chrome.devtools.inspectedWindow.eval(code);
} else {
console.log(code);
}
} }
}) watch(props.data, (newValue: NodeInfoData, oldValue: NodeInfoData) => {
data!: NodeInfoData; if (newValue.uuid !== oldValue.uuid) {
// node
Bus.emit(BusMsg.FoldAllGroup, false);
@Watch("data") }
watchData(newValue: NodeInfoData, oldValue: NodeInfoData) { });
if (newValue.uuid !== oldValue.uuid) { return {};
// node },
Bus.$emit(BusMsg.FoldAllGroup, false) });
}
}
created() {
}
_evalCode(code: string) {
if (chrome && chrome.devtools) {
chrome.devtools.inspectedWindow.eval(code);
} else {
console.log(code);
}
}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
#prop { #prop {
} }
</style> </style>

View File

@ -6,24 +6,17 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import { defineComponent } from "vue";
import Component from "vue-class-component";
import {Prop} from "vue-property-decorator";
@Component({ export default defineComponent({
name: "SettingsProp", name: "SettingsProp",
components: {} props: {
}) label: {
export default class SettingsProp extends Vue { type: String,
@Prop() default: "",
label!: string; },
},
created() { });
}
mounted() {
}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,13 +1,23 @@
<template> <template>
<div class="settings"> <div class="settings">
<settings-prop label="refresh"> <settings-prop label="refresh">
<el-select v-model="refreshType" @change="onCommonSave" style="flex:1;"> <el-select v-model="refreshType" @change="onCommonSave" style="flex: 1">
<el-option v-for="item in refreshOptions" :key="item.value" :label="item.label" :value="item.value"> <el-option
v-for="item in refreshOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option> </el-option>
</el-select> </el-select>
</settings-prop> </settings-prop>
<settings-prop label="refresh time: " v-show="isRefreshAuto()"> <settings-prop label="refresh time: " v-show="isRefreshAuto()">
<el-input-number style="flex:1;" :min=100 v-model="refreshTime" @change="onCommonSave"></el-input-number> <el-input-number
style="flex: 1"
:min="100"
v-model="refreshTime"
@change="onCommonSave"
></el-input-number>
<span>ms</span> <span>ms</span>
</settings-prop> </settings-prop>
@ -22,54 +32,42 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import { defineComponent, onMounted, ref, toRaw } from "vue";
import Component from "vue-class-component"; import { RefreshType, settings } from "../settings";
import {Prop} from "vue-property-decorator"; import SettingsProp from "./settings-prop.vue";
import {RefreshAuto, RefreshManual, settings} from "@/devtools/settings";
import SettingsProp from "@/devtools/ui/settings-prop.vue";
export default defineComponent({
@Component({ name: "settings",
name: "Settings", components: { SettingsProp },
components: {SettingsProp} props: {},
}) setup(props, ctx) {
export default class Settings extends Vue { const refreshOptions = ref<Array<{ label: string; value: RefreshType }>>([
name: string = "settings"; { label: "auto", value: RefreshType.Auto },
refreshOptions = [ { label: "manual", value: RefreshType.Manual },
{label: "auto", value: RefreshAuto}, ]);
{label: "manual", value: RefreshManual} const refreshType = ref(settings.data?.refreshType || "");
] const refreshTime = ref(settings.data?.refreshTime || 500);
refreshType = ""; return {
refreshTime = 500; refreshType,
refreshTime,
isRefreshAuto() { refreshOptions,
return this.refreshType === RefreshAuto; isRefreshAuto() {
} return refreshType.value === RefreshType.Auto;
},
created() { onChangeRefreshType() {},
this.refreshType = settings.data?.refreshType || ""; onCommonSave() {
this.refreshTime = settings.data?.refreshTime || 500; if (settings.data) {
} settings.data.refreshType = toRaw(refreshType.value);
settings.data.refreshTime = toRaw(refreshTime.value);
onChangeRefreshType() { settings.save();
}
} },
};
onCommonSave() { },
if (settings.data) { });
settings.data.refreshType = this.refreshType;
settings.data.refreshTime = this.refreshTime;
settings.save();
}
}
mounted() {
}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.settings { .settings {
} }
</style> </style>

View File

@ -1,47 +1,38 @@
<template> <template>
<div class="ui-divider" :class="{'ui-divider-move':isMove}" <div
@mousedown="onDividerMouseDown"> class="ui-divider"
</div> :class="{ 'ui-divider-move': isMove }"
@mousedown="onDividerMouseDown"
></div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import { defineComponent, ref } from "vue";
import Component from "vue-class-component";
import {Prop, Emit} from "vue-property-decorator";
@Component({ export default defineComponent({
name: "ui-divider", name: "ui-divider",
components: {} emits: ["move"],
}) setup(props, { emit }) {
export default class UiDivider extends Vue { const isMove = ref(false);
private isMove = false; function onDividerMove(event: MouseEvent) {
emit("move", event);
created() {
}
mounted() {
}
@Emit("move")
onDividerMove(event: MouseEvent) {
return event;
}
onDividerMouseDown(event: MouseEvent) {
this.isMove = true;
document.addEventListener("mousemove", this.onDividerMove);
const self = this;
function onMouseUp() {
self.isMove = false;
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mousemove", self.onDividerMove);
} }
return {
isMove,
onDividerMouseDown(event: MouseEvent) {
isMove.value = true;
document.addEventListener("mousemove", onDividerMove);
function onMouseUp() {
isMove.value = false;
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mousemove", onDividerMove);
}
document.addEventListener("mouseup", onMouseUp) document.addEventListener("mouseup", onMouseUp);
} },
} };
},
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,78 +1,116 @@
<template> <template>
<div id="ui-prop"> <div id="ui-prop">
<div class="normal-data" style="display: flex;flex-direction: row;align-items: center;min-height: 30px;margin: 0;"> <div
<div @mousedown="onPropNameMouseDown" class="key" class="normal-data"
@click="onClickFold" style="
:style="{'cursor':isArrayOrObject()?'pointer':''}" display: flex;
flex-direction: row;
align-items: center;
min-height: 30px;
margin: 0;
"
>
<div
@mousedown="onPropNameMouseDown"
class="key"
@click="onClickFold"
:style="{ cursor: isArrayOrObject() ? 'pointer' : '' }"
> >
<i class="data-arrow" <i
v-if="arrow" class="data-arrow"
:class="fold?'el-icon-caret-right':'el-icon-caret-bottom'" v-if="arrow"
:style="{'visibility':isArrayOrObject()?'visible':'hidden','margin-left':indent*10+'px'}"> :class="fold ? 'el-icon-caret-right' : 'el-icon-caret-bottom'"
:style="{
visibility: isArrayOrObject() ? 'visible' : 'hidden',
'margin-left': indent * 10 + 'px',
}"
>
</i> </i>
<div class="text" ref="propText"> <div class="text" ref="propText">
<el-popover placement="top" trigger="hover" :disabled="!isShowTooltip()"> <el-popover
placement="top"
trigger="hover"
:disabled="!isShowTooltip()"
>
<div>{{ name }}</div> <div>{{ name }}</div>
<span slot="reference">{{ name }}</span> <span>{{ name }}</span>
</el-popover> </el-popover>
</div> </div>
</div> </div>
<div class="value"> <div class="value">
<div v-if="isInvalid()" class="invalid"> <div v-if="isInvalid()" class="invalid">
{{ value.data }} {{ value.data }}
</div> </div>
<el-input v-if="isString()" v-model="value.data" <el-input
:disabled="value.readonly" v-if="isString()"
@change="onChangeValue"> v-model="value.data"
:disabled="value.readonly"
@change="onChangeValue"
>
</el-input> </el-input>
<el-input v-if="isText()" <el-input
type="textarea" v-if="isText()"
:autosize="{minRows:3,maxRows:5}" type="textarea"
placeholder="请输入内容" :autosize="{ minRows: 3, maxRows: 5 }"
:disabled="value.readonly" placeholder="请输入内容"
@change="onChangeValue" :disabled="value.readonly"
v-model="value.data"> @change="onChangeValue"
v-model="value.data"
>
</el-input> </el-input>
<el-input-number v-if="isNumber()" <el-input-number
style="width: 100%;text-align: left" v-if="isNumber()"
v-model="value.data" style="width: 100%; text-align: left"
:step="step" v-model="value.data"
:disabled="value.readonly" :step="step"
@change="onChangeValue" :disabled="value.readonly"
controls-position="right" @change="onChangeValue"
controls-position="right"
></el-input-number> ></el-input-number>
<div v-if="isVec2()||isVec3()" class="vec"> <div v-if="isVec2() || isVec3()" class="vec">
<ui-prop v-for="(vec, index) in value.data" <ui-prop
:key="index" v-for="(vec, index) in value.data"
:arrow="false" :key="index"
:value="vec.value" :arrow="false"
:name="vec.name"> :value="vec.value"
:name="vec.name"
>
</ui-prop> </ui-prop>
</div> </div>
<el-select v-model="value.data" <el-select
:disabled="value.readonly" v-model="value.data"
v-if="isEnum()" style="width: 100%;" :disabled="value.readonly"
@change="onChangeValue"> v-if="isEnum()"
<el-option v-for="(opt, index) in value.values" style="width: 100%"
:key="index" @change="onChangeValue"
:label="opt.name" >
:value="opt.value"> <el-option
v-for="(opt, index) in value.values"
:key="index"
:label="opt.name"
:value="opt.value"
>
</el-option> </el-option>
</el-select> </el-select>
<el-checkbox v-model="value.data" <el-checkbox
v-if="isBool()" v-model="value.data"
:disabled="value.readonly" v-if="isBool()"
@change="onChangeValue"> :disabled="value.readonly"
@change="onChangeValue"
>
</el-checkbox> </el-checkbox>
<div class="color" v-if="isColor()"> <div class="color" v-if="isColor()">
<el-color-picker style="position: absolute;" <el-color-picker
:disabled="value.readonly" style="position: absolute"
v-model="value.data" @change="onChangeValue"> :disabled="value.readonly"
v-model="value.data"
@change="onChangeValue"
>
</el-color-picker> </el-color-picker>
<div class="hex" :style="{color:colorReverse(value.data)}">{{ value.data }}</div> <div class="hex" :style="{ color: colorReverse(value.data) }">
{{ value.data }}
</div>
</div> </div>
<!-- <div v-if="isArrayOrObject()" class="array-object">--> <!-- <div v-if="isArrayOrObject()" class="array-object">-->
<!-- <div class="text">--> <!-- <div class="text">-->
@ -82,12 +120,24 @@
<div v-if="isImage()" class="image-property"> <div v-if="isImage()" class="image-property">
<el-popover v-if="isImage()" placement="top" trigger="hover"> <el-popover v-if="isImage()" placement="top" trigger="hover">
<div <div
style="width: 100%;height: 100%;display: flex;flex-direction: row;align-items: center;justify-content: center;"> style="
<img :src="value.data" alt="图片" style="max-width: 100px;max-height: 100px;object-fit: contain;"> width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
"
>
<img
:src="value.data"
alt="图片"
style="max-width: 100px; max-height: 100px; object-fit: contain"
/>
</div> </div>
<img :src="value.data" slot="reference" style="height: 36px;" alt="图片"> <img :src="value.data" style="height: 36px" alt="图片" />
</el-popover> </el-popover>
<div style="flex:1;display: flex; flex-direction: row-reverse;"> <div style="flex: 1; display: flex; flex-direction: row-reverse">
<el-button @click="onShowValueInConsole">log</el-button> <el-button @click="onShowValueInConsole">log</el-button>
</div> </div>
</div> </div>
@ -97,26 +147,32 @@
<div class="type">{{ value.engineType }}</div> <div class="type">{{ value.engineType }}</div>
</div> </div>
<div class="name">{{ value.engineName }}</div> <div class="name">{{ value.engineName }}</div>
<el-button @click="onPlaceInTree" type="primary" icon="el-icon-place"></el-button> <el-button
@click="onPlaceInTree"
type="primary"
icon="el-icon-place"
></el-button>
</div> </div>
<div v-if="isObject()&&fold" class="objectDesc"> <div v-if="isObject() && fold" class="objectDesc">
{{ value.data }} {{ value.data }}
</div> </div>
<div v-if="isArray()" class="array"> <div v-if="isArray()" class="array">Array({{ value.data.length }})</div>
Array({{ value.data.length }})
</div>
<div class="slot" v-if="false"> <div class="slot" v-if="false">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
</div> </div>
<div v-if="isArrayOrObject()"> <div v-if="isArrayOrObject()">
<div v-show="!fold&&subData" style="display: flex;flex-direction: column;"> <div
<ui-prop v-for="(arr,index) in subData" v-show="!fold && subData"
:key="index" style="display: flex; flex-direction: column"
:indent="indent+1" >
:value="arr.value" <ui-prop
:name="getName(isArray(),arr)" v-for="(arr, index) in subData"
:key="index"
:indent="indent + 1"
:value="arr.value"
:name="getName(isArray(), arr)"
> >
</ui-prop> </ui-prop>
</div> </div>
@ -125,224 +181,219 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {
defineComponent,
ref,
toRaw,
watch,
onUnmounted,
onMounted,
PropType,
} from "vue";
import { DataType, EngineData, EnumData, Info, Property } from "../data";
import { connectBackground } from "../connectBackground";
import { Msg } from "../../../core/types";
import Bus, { BusMsg } from "../bus";
import Vue from "vue" export default defineComponent({
import {Component, Prop, Watch} from "vue-property-decorator"
import {DataType, EngineData, Info, Property} from "../data"
import {connectBackground} from "@/devtools/connectBackground";
import {Msg} from "@/core/types";
import Bus, {BusMsg} from "../bus"
@Component({
name: "UiProp", name: "UiProp",
components: {} props: {
}) name: {
export default class UiProp extends Vue { type: String,
@Prop({default: ""}) default: "",
name: string | undefined; },
indent: {
type: Number,
default: 0,
},
arrow: {
type: Boolean,
default: true,
},
value: {
type: Object as PropType<Info | EngineData | EnumData>,
default: () => {},
},
step: {
type: Number,
default: 1,
},
},
@Prop({default: 0}) setup(props, ctx) {
indent!: number; let clientX: number = 0;
@Prop({default: true}) onMounted(() => {
arrow!: boolean; watchValue();
});
@Prop() function watchValue() {
value!: Info; this.fold = true;
if (this.isArray()) {
@Watch("value") this.subData = this.value.data;
watchValue() { } else {
this.fold = true; this.subData = null;
if (this.isArray()) {
this.subData = this.value.data;
} else {
this.subData = null;
}
}
isInvalid() {
return this.value && (this.value.type === DataType.Invalid);
}
isString() {
return this.value && (this.value.type === DataType.String);
}
isText() {
return this.value && (this.value.type === DataType.Text);
}
isNumber() {
return this.value && (this.value.type === DataType.Number);
}
isVec2() {
return this.value && (this.value.type === DataType.Vec2);
}
isVec3() {
return this.value && (this.value.type === DataType.Vec3);
}
isEnum() {
return this.value && (this.value.type === DataType.Enum);
}
isBool() {
return this.value && (this.value.type === DataType.Bool);
}
isColor() {
return this.value && (this.value.type === DataType.Color);
}
isArrayOrObject() {
return this.value && (this.value.type === DataType.Array || this.value.type === DataType.Object)
}
isObject() {
return this.value && (this.value.type === DataType.Object)
}
isArray() {
return this.value && (this.value.type === DataType.Array)
}
isImage() {
return this.value && (this.value.type === DataType.Image)
}
isImageValid() {
return !!this.value.data;
}
isEngine() {
return this.value && (this.value.type === DataType.Engine)
}
onPlaceInTree() {
Bus.$emit(BusMsg.ShowPlace, this.value);
}
created() {
}
mounted() {
this.watchValue();
}
isShowTooltip() {
const el: HTMLDivElement = this.$refs.propText as HTMLDivElement;
if (el) {
if (el.scrollWidth > el.offsetWidth) {
//
return true;
} }
} }
return false; const fold = ref(true);
} watch(props.value, () => {
watchValue();
});
const subData = ref<Property[]>([]);
getEngineTypeIcon() { return {
const value = this.value as EngineData; fold,
switch (value.engineType) { subData,
case "cc_Sprite": { isInvalid() {
return "el-icon-picture-outline"; return this.value && this.value.type === DataType.Invalid;
} },
case "cc_Label": { isString() {
return "el-icon-third-text"; return this.value && this.value.type === DataType.String;
} },
case "cc_Node": { isText() {
return "el-icon-third-node" return this.value && this.value.type === DataType.Text;
} },
} isNumber() {
return "el-icon-third-unknow"; return this.value && this.value.type === DataType.Number;
} },
isVec2() {
return this.value && this.value.type === DataType.Vec2;
},
isVec3() {
return this.value && this.value.type === DataType.Vec3;
},
isEnum() {
return this.value && this.value.type === DataType.Enum;
},
isBool() {
return this.value && this.value.type === DataType.Bool;
},
isColor() {
return this.value && this.value.type === DataType.Color;
},
isArrayOrObject() {
return (
this.value &&
(this.value.type === DataType.Array ||
this.value.type === DataType.Object)
);
},
isObject() {
return this.value && this.value.type === DataType.Object;
},
isArray() {
return this.value && this.value.type === DataType.Array;
},
isImage() {
return this.value && this.value.type === DataType.Image;
},
isImageValid() {
return !!this.value.data;
},
isEngine() {
return this.value && this.value.type === DataType.Engine;
},
onPlaceInTree() {
Bus.emit(BusMsg.ShowPlace, this.value);
},
isShowTooltip() {
const el: HTMLDivElement = this.$refs.propText as HTMLDivElement;
if (el) {
if (el.scrollWidth > el.offsetWidth) {
//
return true;
}
}
return false;
},
getEngineTypeIcon() {
const value = this.value as EngineData;
switch (value.engineType) {
case "cc_Sprite": {
return "el-icon-picture-outline";
}
case "cc_Label": {
return "el-icon-third-text";
}
case "cc_Node": {
return "el-icon-third-node";
}
}
return "el-icon-third-unknow";
},
getName(isArray: boolean, arr: Property) {
const type = arr.value.type;
if (isArray) {
return `[${arr.name}]`;
} else {
return arr.name;
}
},
onClickFold() {
if (this.isObject() && this.fold && !this.subData) {
// objectitem
Bus.emit(BusMsg.RequestObjectData, this.value, (info: Property[]) => {
this.fold = false;
this.subData = info;
});
} else {
this.fold = !this.fold;
}
},
getName(isArray: boolean, arr: UiProp) { onShowValueInConsole() {
const type = arr.value.type; if (Array.isArray(this.value.path)) {
if (isArray) { let uuid = this.value.path[0];
return `[${arr.name}]` let key = this.value.path[1]; // todo key
} else { if (uuid && key) {
return arr.name; chrome.devtools.inspectedWindow.eval(
} `window.CCInspector.logValue('${uuid}','${key}')`
} );
}
}
},
private fold = true; onChangeValue() {
if (!this.value.readonly) {
connectBackground.postMessageToBackground(
Msg.SetProperty,
this.value
);
}
},
onPropNameMouseDown(event: MouseEvent) {
document.addEventListener("mousemove", this._onMouseMove);
document.addEventListener("mouseup", this._onMouseUp);
document.addEventListener("onselectstart", this._onSelect);
},
private subData: Property[] | null = null; colorReverse(OldColorValue: string) {
OldColorValue = "0x" + OldColorValue.replace(/#/g, "");
var str = "000000" + (0xffffff - parseInt(OldColorValue)).toString(16);
return "#" + str.substring(str.length - 6, str.length);
},
_onMouseMove(event: MouseEvent) {
let x = event.clientX;
let calcStep = this.step || 0;
if (x > this.clientX) {
calcStep = Math.abs(calcStep);
} else {
calcStep = -Math.abs(calcStep);
}
this.$emit("movestep", calcStep);
this.clientX = x;
},
onClickFold() { _onMouseUp(event: MouseEvent) {
if (this.isObject() && this.fold && !this.subData) { document.removeEventListener("mousemove", this._onMouseMove);
// objectitem document.removeEventListener("mouseup", this._onMouseUp);
Bus.$emit(BusMsg.RequestObjectData, this.value, (info: Property[]) => { document.removeEventListener("onselectstart", this._onSelect);
},
this.fold = false; _onSelect() {
this.subData = info; return false;
}) },
} else { };
this.fold = !this.fold; },
} });
}
onShowValueInConsole() {
if (Array.isArray(this.value.path)) {
let uuid = this.value.path[0];
let key = this.value.path[1]; // todo key
if (uuid && key) {
chrome.devtools.inspectedWindow.eval(`window.CCInspector.logValue('${uuid}','${key}')`)
}
}
}
onChangeValue() {
if (!this.value.readonly) {
connectBackground.postMessageToBackground(Msg.SetProperty, this.value);
}
}
@Prop({default: 1})
step: number | undefined;
clientX: number = 0;
onPropNameMouseDown(event: MouseEvent) {
document.addEventListener("mousemove", this._onMouseMove);
document.addEventListener("mouseup", this._onMouseUp);
document.addEventListener("onselectstart", this._onSelect);
}
colorReverse(OldColorValue: string) {
OldColorValue = "0x" + OldColorValue.replace(/#/g, "");
var str = "000000" + (0xFFFFFF - parseInt(OldColorValue)).toString(16);
return "#" + str.substring(str.length - 6, str.length);
}
_onSelect() {
return false;
}
_onMouseMove(event: MouseEvent) {
let x = event.clientX;
let calcStep = this.step || 0;
if (x > this.clientX) {
calcStep = Math.abs(calcStep);
} else {
calcStep = -Math.abs(calcStep);
}
this.$emit("movestep", calcStep);
this.clientX = x;
}
_onMouseUp(event: MouseEvent) {
document.removeEventListener("mousemove", this._onMouseMove);
document.removeEventListener("mouseup", this._onMouseUp);
document.removeEventListener("onselectstart", this._onSelect);
}
};
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -362,7 +413,6 @@ export default class UiProp extends Vue {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
.key { .key {
flex: 1; flex: 1;
float: left; float: left;
@ -436,7 +486,7 @@ export default class UiProp extends Vue {
.engine { .engine {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
border: solid #409EFF 1px; border: solid #409eff 1px;
border-radius: 5px; border-radius: 5px;
align-items: center; align-items: center;
align-content: center; align-content: center;
@ -463,7 +513,6 @@ export default class UiProp extends Vue {
} }
} }
.name { .name {
flex: 1; flex: 1;
height: 28px; height: 28px;