mirror of
https://github.com/tidys/cc-inspector-chrome
synced 2025-10-22 13:55:23 +00:00
将popup移植了过去
This commit is contained in:
@@ -14,21 +14,16 @@
|
||||
"fs-extra": "^9.1.0",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^7.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"universal-analytics": "^0.4.23",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-property-decorator": "^9.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/kind-of": "^6.0.0",
|
||||
"@types/lodash": "^4.14.176",
|
||||
|
||||
"babel-eslint": "^10.1.0",
|
||||
"@types/fs-extra": "^9.0.9",
|
||||
"@types/node": "^14.14.37",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@types/chrome": "0.0.133",
|
||||
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
@@ -1,252 +0,0 @@
|
||||
import {Msg, Page, PluginEvent} from "@/core/types";
|
||||
|
||||
// @ts-ignore
|
||||
import * as UA from "universal-analytics"
|
||||
import {v4} from "uuid"
|
||||
import {devtools_page} from "./manifest.json"
|
||||
import {FrameDetails} from "@/devtools/data";
|
||||
|
||||
// 统计服务
|
||||
const userID = localStorage.getItem("userID") || v4()
|
||||
UA("UA-134924925-3", userID);
|
||||
|
||||
console.log("on background")
|
||||
|
||||
class PortMan {
|
||||
public currentUseContentFrameID = 0;
|
||||
public content: Array<chrome.runtime.Port> = []; // 因为iframe的原因,可能对应多个,主iframe的id===0
|
||||
public devtools: chrome.runtime.Port | null = null;
|
||||
public id: number | null = null;// tab.id作为唯一标识
|
||||
public title: string = "";
|
||||
public url: string = "";
|
||||
private mgr: PortManagement | null = null;
|
||||
|
||||
constructor(mgr: PortManagement, {id, url, title}: any) {
|
||||
this.mgr = mgr;
|
||||
this.id = id;
|
||||
this.url = url;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
private onPortConnect(port: chrome.runtime.Port, onMsg: Function, onDisconnect: Function) {
|
||||
console.log(`%c[Connect] ${port.name}`, "color:green");
|
||||
port.onMessage.addListener((data: any, sender: any) => {
|
||||
console.log(`%c[Connect-Message] ${sender.name}\n${JSON.stringify(data)}`, "color:blue;")
|
||||
// 如果多个页面都监听 onMessage 事件,对于某一次事件只有第一次调用 sendResponse() 能成功发出回应,所有其他回应将被忽略。
|
||||
// sender.postMessage(data);
|
||||
onMsg && onMsg(data);
|
||||
});
|
||||
port.onDisconnect.addListener((port: chrome.runtime.Port) => {
|
||||
console.log(`%c[Connect-Dis] ${port.name}`, "color:red");
|
||||
onDisconnect && onDisconnect(port)
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentUseContent(): chrome.runtime.Port | null {
|
||||
return this.content.find(el => el.sender?.frameId !== undefined && el.sender.frameId === this.currentUseContentFrameID) || null;
|
||||
}
|
||||
|
||||
_updateFrames() {
|
||||
let data: FrameDetails[] = this.content.map(item => {
|
||||
return {
|
||||
url: item.sender?.url || "",
|
||||
frameID: item.sender?.frameId || 0,
|
||||
}
|
||||
})
|
||||
let event = new PluginEvent(Page.Background, Page.Devtools, Msg.UpdateFrames, data)
|
||||
this.sendDevtoolMsg(event)
|
||||
}
|
||||
|
||||
dealConnect(port: chrome.runtime.Port) {
|
||||
switch (port.name) {
|
||||
case Page.Content: {
|
||||
this.content.push(port);
|
||||
this._updateFrames();
|
||||
this.onPortConnect(port,
|
||||
(data: PluginEvent) => {
|
||||
if (data.target === Page.Devtools) {
|
||||
this.sendDevtoolMsg(data);
|
||||
}
|
||||
},
|
||||
(disPort: chrome.runtime.Port) => {
|
||||
const index = this.content.findIndex(el =>
|
||||
disPort.sender?.frameId !== undefined
|
||||
&& el.sender?.frameId !== undefined
|
||||
&& el.sender?.frameId === disPort.sender?.frameId
|
||||
);
|
||||
this.content.splice(index, 1);
|
||||
this._updateFrames();
|
||||
this.checkValid();
|
||||
})
|
||||
break;
|
||||
}
|
||||
case Page.Devtools: {
|
||||
this.devtools = port;
|
||||
this._updateFrames(); // 当devtools链接后,主动派发frames数据
|
||||
this.onPortConnect(port,
|
||||
(data: PluginEvent) => {
|
||||
if (data.msg === Msg.UseFrame) {
|
||||
this.currentUseContentFrameID = data.data;
|
||||
// 更新这个frame的tree
|
||||
this.updateCurrentFrameTree();
|
||||
} else {
|
||||
// 从devtools过来的消息统一派发到Content中
|
||||
if (PluginEvent.check(data, Page.Devtools, Page.Background)) {
|
||||
if (data.msg === Msg.TreeInfo) {
|
||||
if (this.currentUseContentFrameID !== data.data) {
|
||||
console.log(`frameID[${data.data}]不一致`);
|
||||
}
|
||||
}
|
||||
PluginEvent.reset(data, Page.Background, Page.Content);
|
||||
this.getCurrentUseContent()?.postMessage(data)
|
||||
}
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.devtools = null;
|
||||
this.checkValid();
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateCurrentFrameTree() {
|
||||
const sendData = new PluginEvent(Page.Background, Page.Content, Msg.Support);
|
||||
this.getCurrentUseContent()?.postMessage(sendData);
|
||||
}
|
||||
|
||||
checkValid() {
|
||||
if (!this.devtools && !this.content.length) {
|
||||
this.mgr?.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
sendContentMsg(data: PluginEvent) {
|
||||
this.getCurrentUseContent()?.postMessage(data);
|
||||
}
|
||||
|
||||
sendDevtoolMsg(data: PluginEvent) {
|
||||
this.devtools?.postMessage(data)
|
||||
}
|
||||
}
|
||||
|
||||
class PortManagement {
|
||||
port: Array<PortMan> = [];
|
||||
|
||||
constructor() {
|
||||
this.initConnect();
|
||||
chrome.runtime.onMessage.addListener((request: PluginEvent, sender: any, sendResponse: any) => {
|
||||
const tabID = sender.tab.id;
|
||||
const portMan: PortMan | undefined = this.find(tabID);
|
||||
if (portMan) {
|
||||
if (PluginEvent.check(request, Page.Content, Page.Background)) {
|
||||
// 监听来自content.js发来的事件,将消息转发到devtools
|
||||
PluginEvent.reset(request, Page.Background, Page.Devtools)
|
||||
console.log(`%c[Message]url:${sender.url}]\n${JSON.stringify(request)}`, "color:green")
|
||||
portMan.sendDevtoolMsg(request);
|
||||
}
|
||||
}
|
||||
})
|
||||
chrome.tabs.onActivated.addListener(({tabId, windowId}) => {
|
||||
})
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
// 页面发生刷新,通知重新生成数据
|
||||
if (changeInfo.status === "complete") {
|
||||
const {id} = tab;
|
||||
// -1为自己
|
||||
if (id && id > -1) {
|
||||
let portMan = this.find(id);
|
||||
if (portMan) {
|
||||
let data = new PluginEvent(Page.Background, Page.Content, Msg.Support);
|
||||
portMan.sendContentMsg(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
isDevtools(port: chrome.runtime.Port) {
|
||||
const devtoolsUrl = `chrome-extension://${port.sender?.id}/${devtools_page}`
|
||||
if (port.sender?.url === devtoolsUrl) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
initConnect() {
|
||||
chrome.runtime.onConnect.addListener((port: chrome.runtime.Port) => {
|
||||
if (port.name === Page.Devtools) {
|
||||
// devtool链接过来没有port.sender.tab
|
||||
chrome.tabs.getSelected((tab: chrome.tabs.Tab) => {
|
||||
this._onConnect(tab, port)
|
||||
})
|
||||
} else {
|
||||
const tab: chrome.tabs.Tab | undefined = port.sender?.tab;
|
||||
if (tab) {
|
||||
this._onConnect(tab, port)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private _onConnect(tab: chrome.tabs.Tab, port: chrome.runtime.Port) {
|
||||
const {id, title, url} = tab;
|
||||
if (id !== undefined && id > -1) {
|
||||
let portMan: PortMan | undefined = this.find(id)
|
||||
if (!portMan) {
|
||||
portMan = new PortMan(this, {id, title, url});
|
||||
this.port.push(portMan);
|
||||
}
|
||||
portMan.dealConnect(port);
|
||||
}
|
||||
}
|
||||
|
||||
find(id: number): PortMan | undefined {
|
||||
return this.port.find(el => el.id === id)
|
||||
}
|
||||
|
||||
remove(item: PortMan) {
|
||||
let index = this.port.findIndex(el => el === item)
|
||||
if (index > -1) {
|
||||
this.port.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).backgroundInstance = new PortManagement();
|
||||
|
||||
function createPluginMenus() {
|
||||
const menus = [];
|
||||
|
||||
let parent = chrome.contextMenus.create({id: "parent", title: "CC-Inspector"});
|
||||
chrome.contextMenus.create({
|
||||
id: "test",
|
||||
title: "测试右键菜单",
|
||||
parentId: parent,
|
||||
// 上下文环境,可选:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"],默认page
|
||||
contexts: ["page"],
|
||||
});
|
||||
chrome.contextMenus.create({
|
||||
id: "notify",
|
||||
parentId: parent,
|
||||
title: "通知"
|
||||
})
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(function (info, tab) {
|
||||
if (info.menuItemId === "test") {
|
||||
alert("您点击了右键菜单!");
|
||||
} else if (info.menuItemId === "notify") {
|
||||
chrome.notifications.create("null", {
|
||||
type: "basic",
|
||||
iconUrl: "icons/48.png",
|
||||
title: "通知",
|
||||
message: "测试通知",
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
chrome.contextMenus.removeAll(function () {
|
||||
createPluginMenus();
|
||||
});
|
||||
|
@@ -1,61 +0,0 @@
|
||||
// content.js 和原始界面共享DOM,具有操作dom的能力
|
||||
// 但是不共享js,要想访问页面js,只能通过注入的方式
|
||||
import {injectScript} from "@/core/util";
|
||||
import {Msg, Page, PluginEvent} from "@/core/types";
|
||||
|
||||
injectScript("js/inject.js");
|
||||
|
||||
class Content {
|
||||
private connect: chrome.runtime.Port | null = null;
|
||||
|
||||
constructor() {
|
||||
// 接受来自inject.js的消息数据,然后中转到background.js
|
||||
window.addEventListener("message", (event) => {
|
||||
let data: PluginEvent = event.data;
|
||||
if (PluginEvent.check(data, Page.Inject, Page.Content)) {
|
||||
console.log("[Window-Message]: ", data);
|
||||
PluginEvent.reset(data, Page.Content, Page.Devtools)
|
||||
this.connect?.postMessage(data)
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
// 和background.js保持长连接通讯,background和content的交互也要通过这个链接进行通讯
|
||||
private connectToBackground() {
|
||||
this.connect = chrome.runtime.connect({name: Page.Content})
|
||||
this.connect.onMessage.addListener((data: PluginEvent, sender) => {
|
||||
if (PluginEvent.check(data, Page.Background, Page.Content)) {
|
||||
// console.log(`%c[Connect-Message] ${JSON.stringify(data)}`, "color:green;")
|
||||
console.log("[Connect-Message]: ", data);
|
||||
PluginEvent.reset(data, Page.Content, Page.Inject)
|
||||
window.postMessage(data, "*");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private sendMessageToBackground(data: PluginEvent) {
|
||||
if (this.connect) {
|
||||
this.connect.postMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.connectToBackground();
|
||||
this.checkGame();
|
||||
}
|
||||
|
||||
private checkGame() {
|
||||
let gameCanvas = document.querySelector("#GameCanvas");
|
||||
if (!gameCanvas) {
|
||||
let sendData = new PluginEvent(Page.Content, Page.Devtools, Msg.Support, {
|
||||
support: false,
|
||||
msg: "未发现GameCanvas,不支持调试游戏!"
|
||||
})
|
||||
this.sendMessageToBackground(sendData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const content = new Content();
|
||||
content.run();
|
@@ -1,79 +0,0 @@
|
||||
export enum Page {
|
||||
Inject = "Inject",
|
||||
Devtools = "Devtools",
|
||||
Background = "Background",
|
||||
Content = "Content",
|
||||
Popup = "Popup",
|
||||
Options = "Options",
|
||||
}
|
||||
|
||||
export enum Msg {
|
||||
NodeInfo = "node-info",// 具体的节点信息
|
||||
TreeInfo = "tree-info",// 节点树信息
|
||||
Support = "game-support",// 游戏支持信息
|
||||
MemoryInfo = "memory-info",//
|
||||
TabsInfo = "tabs_info", // 当前页面信息
|
||||
GetTabID = "GetTabID", // 获取页面ID
|
||||
UpdateFrames = "UpdateFrames", // 更新页面的frame
|
||||
UseFrame = "UseFrame",
|
||||
GetObjectItemData = "GetObjectItemData",
|
||||
LogData = "LogData",
|
||||
SetProperty = "set-property", // 设置node属性
|
||||
UpdateProperty = "update-property", // 更新属性
|
||||
}
|
||||
|
||||
export class PluginEvent {
|
||||
msg: Msg | null = null;
|
||||
data: any = null;
|
||||
|
||||
source: Page | null = null; // 事件发送的源头
|
||||
target: Page | null = null; // 事件要发送的目标
|
||||
|
||||
static check(event: PluginEvent, source: Page, target: Page) {
|
||||
return event && source && target && event.source === source && event.target === target;
|
||||
}
|
||||
|
||||
static reset(event: PluginEvent, source: Page | null, target: Page | null) {
|
||||
if (event && source && target) {
|
||||
event.source = source;
|
||||
event.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
static finish(event: PluginEvent) {
|
||||
event.source = event.target = null;
|
||||
}
|
||||
|
||||
constructor(source: Page, target: Page, msg: Msg, data?: any) {
|
||||
if (PageInclude(target)) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
} else {
|
||||
console.warn(`无效的target: ${target}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function inEnum(enumValues: any, value: Page | Msg) {
|
||||
for (let key in enumValues) {
|
||||
if (enumValues.hasOwnProperty(key)) {
|
||||
//@ts-ignore
|
||||
let itemEnum = enumValues[key] as string;
|
||||
if (itemEnum === value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function PageInclude(page: Page) {
|
||||
return inEnum(Page, page);
|
||||
}
|
||||
|
||||
|
||||
export function MsgInclude(msg: Msg) {
|
||||
return inEnum(Msg, msg)
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
export function injectScript(url: string) {
|
||||
if (chrome && chrome.extension && chrome.extension.getURL) {
|
||||
let content = chrome.extension.getURL(url)
|
||||
console.log(`[cc-inspector]注入脚本:${content}`);
|
||||
const script = document.createElement("script")
|
||||
script.setAttribute("type", "text/javascript")
|
||||
script.setAttribute("src", content)
|
||||
script.onload = function () {
|
||||
document.body.removeChild(script);
|
||||
}
|
||||
document.body.appendChild(script)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface LogOptions {
|
||||
data: any;
|
||||
flag?: string;
|
||||
color?: "red" | "blue";
|
||||
}
|
||||
|
||||
export function log(options: LogOptions) {
|
||||
const data: any = options.data;
|
||||
const time = new Date().toLocaleString()
|
||||
let log = ""
|
||||
if (typeof data === "string") {
|
||||
log = data;
|
||||
} else if (typeof data === "object") {
|
||||
log = JSON.stringify(data)
|
||||
}
|
||||
|
||||
let str = "";
|
||||
if (options.flag) {
|
||||
str = `[${time}][${options.flag}]: ${log} `;
|
||||
} else {
|
||||
str = `[${time}]: ${log} `;
|
||||
}
|
||||
if (options.color) {
|
||||
console.log(`%c${str}`, `color:${options.color};`)
|
||||
} else {
|
||||
console.log(str);
|
||||
}
|
||||
}
|
@@ -1,231 +0,0 @@
|
||||
// @ts-ignore
|
||||
import {v4} from "uuid"
|
||||
|
||||
export enum DataType {
|
||||
Number,
|
||||
String,
|
||||
Text,
|
||||
Vec2,
|
||||
Vec3,
|
||||
Enum,
|
||||
Bool,
|
||||
Color,
|
||||
Invalid,
|
||||
Array, // 暂时在控制台打印下
|
||||
Object,
|
||||
ObjectItem,
|
||||
Image, // 图片
|
||||
Engine,// 引擎的类型:cc.Node, cc.Sprite, cc.Label等。。。
|
||||
}
|
||||
|
||||
export class Info {
|
||||
public id: string | null = null;
|
||||
public type: DataType = DataType.Number;
|
||||
public data: any;
|
||||
public readonly: boolean = false;
|
||||
public path: Array<string> = [];// 属性对应的路径
|
||||
constructor() {
|
||||
this.id = v4();
|
||||
}
|
||||
}
|
||||
|
||||
export class TextData extends Info {
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Text;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObjectItemRequestData {
|
||||
id: string | null;
|
||||
data: Property[];
|
||||
}
|
||||
|
||||
export interface FrameDetails {
|
||||
frameID: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class EngineData extends Info {
|
||||
public engineType: string = "";
|
||||
public engineUUID: string = "";
|
||||
public engineName: string = "";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Engine;
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayData extends Info {
|
||||
data: Array<Property> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Array;
|
||||
}
|
||||
|
||||
add(info: Property) {
|
||||
this.data.push(info);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class ObjectDataItem extends Info {
|
||||
|
||||
}
|
||||
|
||||
export class ObjectData extends Info {
|
||||
data: string = "";// object的简单描述快照,类似chrome的console,{a:'fff',b:xxx}
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Object;
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidData extends Info {
|
||||
data: "undefined" | "null" | "Infinity" | "NaN" | string;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.data = data;
|
||||
this.type = DataType.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
export class ColorData extends Info {
|
||||
constructor(color: string) {
|
||||
super();
|
||||
this.type = DataType.Color;
|
||||
this.data = color;
|
||||
}
|
||||
}
|
||||
|
||||
export class StringData extends Info {
|
||||
constructor(data: string) {
|
||||
super();
|
||||
this.type = DataType.String;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberData extends Info {
|
||||
constructor(data: number) {
|
||||
super();
|
||||
this.type = DataType.Number;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export class BoolData extends Info {
|
||||
constructor(bol: boolean) {
|
||||
super();
|
||||
this.type = DataType.Bool;
|
||||
this.data = bol;
|
||||
}
|
||||
}
|
||||
|
||||
export class Vec2Data extends Info {
|
||||
data: Array<Property> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Vec2
|
||||
this.data = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
add(info: Property) {
|
||||
this.data.push(info);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class Vec3Data extends Info {
|
||||
data: Array<Property> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Vec3;
|
||||
this.data = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
add(info: Property) {
|
||||
this.data.push(info);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageData extends Info {
|
||||
data: string | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Image;
|
||||
this.data = null;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class EnumData extends Info {
|
||||
constructor() {
|
||||
super();
|
||||
this.type = DataType.Enum;
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeData {
|
||||
active: boolean = true;
|
||||
uuid: string = "";
|
||||
name: string = "";
|
||||
children: Array<TreeData> = [];
|
||||
}
|
||||
|
||||
export class Property {
|
||||
public name: string = "property";
|
||||
public value: Info = new Info();
|
||||
|
||||
constructor(name: string, info: Info) {
|
||||
this.name = name;
|
||||
this.value = info;
|
||||
}
|
||||
}
|
||||
|
||||
export class Group {
|
||||
public id: string = "";
|
||||
public name: string = "group";
|
||||
public data: Array<Property> = [];
|
||||
|
||||
constructor(name: string,id?:string) {
|
||||
this.name = name;
|
||||
this.id=id||'';
|
||||
}
|
||||
|
||||
addProperty(property: Property) {
|
||||
this.data.push(property)
|
||||
}
|
||||
|
||||
sort() {
|
||||
let order = ["name", "active", "enabled", "uuid", "position", "rotation", "scale", "anchor", "size", "color", "opacity", "skew", "group"];
|
||||
let orderKeys: Array<Property> = [];
|
||||
let otherKeys: Array<Property> = [];
|
||||
this.data.forEach(property => {
|
||||
if (order.find(el => el === property.name)) {
|
||||
orderKeys.push(property)
|
||||
} else {
|
||||
otherKeys.push(property);
|
||||
}
|
||||
})
|
||||
orderKeys.sort((a, b) => {
|
||||
return order.indexOf(a.name) - order.indexOf(b.name);
|
||||
})
|
||||
otherKeys.sort();
|
||||
this.data = orderKeys.concat(otherKeys);
|
||||
}
|
||||
}
|
||||
|
||||
export interface NodeInfoData {
|
||||
uuid: string;// 节点的uuid
|
||||
group: Group[];
|
||||
}
|
@@ -1,694 +0,0 @@
|
||||
// eval 注入脚本的代码,变量尽量使用var,后来发现在import之后,let会自动变为var
|
||||
import {
|
||||
ArrayData,
|
||||
BoolData,
|
||||
ColorData,
|
||||
DataType,
|
||||
EngineData,
|
||||
Group,
|
||||
ImageData,
|
||||
Info,
|
||||
InvalidData, NodeInfoData,
|
||||
NumberData,
|
||||
ObjectData, ObjectItemRequestData,
|
||||
Property,
|
||||
StringData,
|
||||
TreeData,
|
||||
Vec2Data,
|
||||
Vec3Data
|
||||
} from "@/devtools/data";
|
||||
import {Msg, Page, PluginEvent} from "@/core/types"
|
||||
import {BuildArrayOptions, BuildImageOptions, BuildObjectOptions, BuildVecOptions} from "@/inject/types";
|
||||
// @ts-ignore
|
||||
import {uniq} from "lodash"
|
||||
import {trySetValueWithConfig, getValue} from "@/inject/setValue";
|
||||
import {isHasProperty} from "@/inject/util";
|
||||
|
||||
declare const cc: any;
|
||||
|
||||
|
||||
class CCInspector {
|
||||
inspectorGameMemoryStorage: Record<string, any> = {}
|
||||
|
||||
private watchIsCocosGame() {
|
||||
const timer = setInterval(() => {
|
||||
if (this._isCocosGame()) {
|
||||
clearInterval(timer)
|
||||
// @ts-ignore
|
||||
cc.director.on(cc.Director.EVENT_AFTER_SCENE_LAUNCH, () => {
|
||||
let isCocosGame = this._isCocosGame();
|
||||
this.notifySupportGame(isCocosGame)
|
||||
})
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log("cc-inspector init ~~~");
|
||||
this.watchIsCocosGame();
|
||||
window.addEventListener("message", (event) => {
|
||||
// 接受来自content的事件,有可能也会受到其他插件的
|
||||
if (!event || !event.data) {
|
||||
return
|
||||
}
|
||||
let pluginEvent: PluginEvent = event.data;
|
||||
if (PluginEvent.check(pluginEvent, Page.Content, Page.Inject)) {
|
||||
console.log(`%c[Inject] ${JSON.stringify(pluginEvent)}`, "color:green;");
|
||||
PluginEvent.finish(pluginEvent)
|
||||
switch (pluginEvent.msg) {
|
||||
case Msg.Support: {
|
||||
let isCocosGame = this._isCocosGame();
|
||||
this.notifySupportGame(isCocosGame)
|
||||
break;
|
||||
}
|
||||
case Msg.TreeInfo: {
|
||||
this.updateTreeInfo();
|
||||
break;
|
||||
}
|
||||
case Msg.NodeInfo: {
|
||||
let nodeUUID = pluginEvent.data;
|
||||
this.getNodeInfo(nodeUUID);
|
||||
break;
|
||||
}
|
||||
case Msg.SetProperty: {
|
||||
const data: Info = pluginEvent.data;
|
||||
let value = data.data;
|
||||
if (data.type === DataType.Color) {
|
||||
// @ts-ignore
|
||||
value = cc.color().fromHEX(value);
|
||||
}
|
||||
|
||||
if (this.setValue(data.path, value)) {
|
||||
this.sendMsgToContent(Msg.UpdateProperty, data);
|
||||
} else {
|
||||
console.warn(`设置失败:${data.path}`)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Msg.LogData: {
|
||||
const data: string[] = pluginEvent.data;
|
||||
const value = getValue(this.inspectorGameMemoryStorage, data);
|
||||
// 直接写console.log会被tree shaking
|
||||
const logFunction = console.log;
|
||||
logFunction(value);
|
||||
break;
|
||||
}
|
||||
case Msg.GetObjectItemData: {
|
||||
const data: ObjectData = pluginEvent.data;
|
||||
let val = getValue(this.inspectorGameMemoryStorage, data.path);
|
||||
if (val) {
|
||||
let itemData: Property[] = this._buildObjectItemData({
|
||||
data: data,
|
||||
path: data.path,
|
||||
value: val,
|
||||
filterKey: false,
|
||||
});
|
||||
let result: ObjectItemRequestData = {
|
||||
id: data.id,
|
||||
data: itemData,
|
||||
}
|
||||
this.sendMsgToContent(Msg.GetObjectItemData, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendMsgToContent(msg: Msg, data: any) {
|
||||
// 发送给content.js处理,也会导致发送给了自身,死循环
|
||||
window.postMessage(new PluginEvent(Page.Inject, Page.Content, msg, data), "*");
|
||||
}
|
||||
|
||||
notifySupportGame(b: boolean) {
|
||||
this.sendMsgToContent(Msg.Support, b);
|
||||
}
|
||||
|
||||
updateTreeInfo() {
|
||||
let isCocosCreatorGame = this._isCocosGame();
|
||||
if (isCocosCreatorGame) {
|
||||
//@ts-ignore
|
||||
let scene = cc.director.getScene();
|
||||
if (scene) {
|
||||
let treeData = new TreeData();
|
||||
this.getNodeChildren(scene, treeData)
|
||||
this.sendMsgToContent(Msg.TreeInfo, treeData);
|
||||
} else {
|
||||
console.warn("can't execute api : cc.director.getScene")
|
||||
this.notifySupportGame(false);
|
||||
}
|
||||
} else {
|
||||
this.notifySupportGame(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
draw: cc.Graphics = null;
|
||||
|
||||
_drawRect(node: any) {
|
||||
let draw = this.draw;
|
||||
|
||||
if (!draw) {
|
||||
// @ts-ignore
|
||||
let node = new cc.Node("draw-node");
|
||||
// @ts-ignore
|
||||
cc.director.getScene().addChild(node);
|
||||
// @ts-ignore
|
||||
draw = this.draw = node.addComponent(cc.Graphics);
|
||||
}
|
||||
draw.clear()
|
||||
draw.lineWidth = 10;
|
||||
// @ts-ignore
|
||||
draw.strokeColor = new cc.Color().fromHEX("#ff0000")
|
||||
const {anchorX, anchorY, width, height, x, y} = node;
|
||||
let halfWidth = width / 2;
|
||||
let halfHeight = height / 2;
|
||||
let leftBottom = node.convertToWorldSpaceAR(cc.v2(-halfWidth, -halfHeight))
|
||||
let leftTop = node.convertToWorldSpaceAR(cc.v2(-halfWidth, halfHeight));
|
||||
let rightBottom = node.convertToWorldSpaceAR(cc.v2(halfWidth, -halfHeight));
|
||||
let rightTop = node.convertToWorldSpaceAR(cc.v2(halfWidth, halfHeight));
|
||||
|
||||
function line(began: any, end: any) {
|
||||
draw.moveTo(began.x, began.y);
|
||||
draw.lineTo(end.x, end.y);
|
||||
}
|
||||
|
||||
line(leftBottom, rightBottom)
|
||||
line(rightBottom, rightTop)
|
||||
line(rightTop, leftTop)
|
||||
line(leftTop, leftBottom)
|
||||
this.draw.stroke();
|
||||
}
|
||||
|
||||
// 收集节点信息
|
||||
getNodeChildren(node: any, data: TreeData) {
|
||||
data.uuid = node.uuid;
|
||||
data.name = node.name;
|
||||
// @ts-ignore
|
||||
if (node instanceof cc.Scene) {
|
||||
// 场景不允许获取active,引擎会报错
|
||||
} else {
|
||||
data.active = !!node.active;
|
||||
}
|
||||
this.inspectorGameMemoryStorage[node.uuid] = node;
|
||||
let nodeChildren = node.children;
|
||||
for (let i = 0; i < nodeChildren.length; i++) {
|
||||
let childItem = nodeChildren[i];
|
||||
let treeData = new TreeData();
|
||||
this.getNodeChildren(childItem, treeData);
|
||||
data.children.push(treeData)
|
||||
}
|
||||
}
|
||||
|
||||
_isCocosGame() {
|
||||
// @ts-ignore 检测是否包含cc变量
|
||||
return typeof cc !== "undefined";
|
||||
}
|
||||
|
||||
getAllPropertyDescriptors(obj: Object): string[] {
|
||||
let keys: string[] = [];
|
||||
|
||||
function circle(root: Object) {
|
||||
const descriptors = Object.getOwnPropertyDescriptors(root);
|
||||
for (let descriptorsKey in descriptors) {
|
||||
if (Object.hasOwnProperty.call(descriptors, descriptorsKey)) {
|
||||
const value = descriptors[descriptorsKey];
|
||||
// 不可枚举的属性,并且允许修改get set的才有效
|
||||
if (!value.enumerable && value.configurable) {
|
||||
keys.push(descriptorsKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
const proto = Object.getPrototypeOf(root);
|
||||
if (proto) {
|
||||
circle(proto)
|
||||
}
|
||||
}
|
||||
|
||||
circle(obj)
|
||||
return keys;
|
||||
}
|
||||
|
||||
_getNodeKeys(node: any) {
|
||||
// 3.x变成了getter
|
||||
let excludeProperty = [
|
||||
"children", "quat", "node", "components", "parent",
|
||||
// 生命周期函数
|
||||
"onFocusInEditor", "onRestore", "start", "lateUpdate", "update", "resetInEditor", "onLostFocusInEditor",
|
||||
"onEnable", "onDisable", "onDestroy", "onLoad",
|
||||
];
|
||||
const keyHidden = this.getAllPropertyDescriptors(node);
|
||||
const keyVisible1 = Object.keys(node); // Object不走原型链
|
||||
let keyVisible2: string[] = [];
|
||||
for (let nodeKey in node) {// 走原型链
|
||||
keyVisible2.push(nodeKey)
|
||||
}
|
||||
let allKeys: string[] = uniq(keyHidden.concat(keyVisible1, keyVisible2)).sort();
|
||||
allKeys = allKeys.filter(key => {
|
||||
return !key.startsWith("_") && !excludeProperty.includes(key);
|
||||
});
|
||||
|
||||
allKeys = allKeys.filter(key => {
|
||||
try {
|
||||
return typeof node[key] !== "function"
|
||||
} catch (e) {
|
||||
// console.warn(`属性${key}出现异常:\n`, e);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
return allKeys;
|
||||
}
|
||||
|
||||
_getPairProperty(key: string): null | { key: string, values: string[] } {
|
||||
let pairProperty: Record<string, any> = {
|
||||
rotation: ["rotationX", "rotationY"],
|
||||
anchor: ["anchorX", "anchorY"],
|
||||
size: ["width", "height"],
|
||||
skew: ["skewX", "skewY"],
|
||||
position: ["x", "y", "z"], // position比较特殊,过来的key就是position也需要能处理
|
||||
scale: ["scaleX", "scaleY", "scaleZ"],
|
||||
};
|
||||
for (let pairPropertyKey in pairProperty) {
|
||||
if (pairProperty.hasOwnProperty(pairPropertyKey)) {
|
||||
let pair = pairProperty[pairPropertyKey];
|
||||
if (pair.includes(key) || key === pairPropertyKey) {
|
||||
return {key: pairPropertyKey, values: pair};
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_buildVecData(options: BuildVecOptions) {
|
||||
const ctor: Function = options.ctor;
|
||||
const keys: Array<string> = options.keys;
|
||||
const value: Object = options.value;
|
||||
const data: Vec3Data | Vec2Data = options.data;
|
||||
const path: Array<string> = options.path;
|
||||
|
||||
if (ctor && value instanceof ctor) {
|
||||
let hasUnOwnProperty = keys.find(key => !value.hasOwnProperty(key))
|
||||
if (!hasUnOwnProperty) {
|
||||
for (let key in keys) {
|
||||
let propName = keys[key];
|
||||
if (value.hasOwnProperty(propName)) {
|
||||
let propPath = path.concat(propName);
|
||||
let itemData = this._genInfoData(value, propName, propPath)
|
||||
if (itemData) {
|
||||
data.add(new Property(propName, itemData))
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_buildImageData(options: BuildImageOptions) {
|
||||
const ctor: Function = options.ctor;
|
||||
const value: Object = options.value;
|
||||
const data: ImageData = options.data;
|
||||
const path: Array<string> = options.path;
|
||||
if (ctor && value instanceof ctor) {
|
||||
data.path = path;
|
||||
// 2.4.6 没有了这个属性
|
||||
if (value.hasOwnProperty("_textureFilename")) {
|
||||
//@ts-ignore
|
||||
data.data = `${window.location.origin}/${value._textureFilename}`;
|
||||
} else {
|
||||
data.data = null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_genInfoData(node: any, key: string | number, path: Array<string>, filterKey = true): Info | null {
|
||||
let propertyValue = node[key];
|
||||
let info = null;
|
||||
let invalidType = this._isInvalidValue(propertyValue);
|
||||
if (invalidType) {
|
||||
info = new InvalidData(invalidType);
|
||||
} else {
|
||||
switch (typeof propertyValue) {
|
||||
case "boolean":
|
||||
info = new BoolData(propertyValue);
|
||||
break;
|
||||
case "number":
|
||||
info = new NumberData(propertyValue);
|
||||
break;
|
||||
case "string":
|
||||
info = new StringData(propertyValue);
|
||||
break;
|
||||
default:
|
||||
//@ts-ignore
|
||||
if (propertyValue instanceof cc.Color) {
|
||||
let hex = propertyValue.toHEX();
|
||||
info = new ColorData(`#${hex}`);
|
||||
} else if (Array.isArray(propertyValue)) {
|
||||
let keys: number[] = [];
|
||||
for (let i = 0; i < propertyValue.length; i++) {
|
||||
keys.push(i);
|
||||
}
|
||||
info = this._buildArrayData({
|
||||
data: new ArrayData(),
|
||||
path: path,
|
||||
value: propertyValue,
|
||||
keys: keys,
|
||||
})
|
||||
} else {
|
||||
!info && (info = this._buildVecData({
|
||||
// @ts-ignore
|
||||
ctor: cc.Vec3,
|
||||
path: path,
|
||||
data: new Vec3Data(),
|
||||
keys: ["x", "y", "z"],
|
||||
value: propertyValue,
|
||||
}))
|
||||
!info && (info = this._buildVecData({
|
||||
// @ts-ignore
|
||||
ctor: cc.Vec2,
|
||||
path: path,
|
||||
data: new Vec2Data(),
|
||||
keys: ["x", "y"],
|
||||
value: propertyValue
|
||||
}))
|
||||
!info && (info = this._buildImageData({
|
||||
//@ts-ignore
|
||||
ctor: cc.SpriteFrame,
|
||||
data: new ImageData(),
|
||||
path: path,
|
||||
value: propertyValue,
|
||||
}))
|
||||
if (!info) {
|
||||
if (typeof propertyValue === "object") {
|
||||
let ctorName = propertyValue.constructor?.name;
|
||||
if (ctorName) {
|
||||
if (ctorName.startsWith("cc_") ||
|
||||
// 2.4.0
|
||||
ctorName === "CCClass") {
|
||||
info = new EngineData();
|
||||
info.engineType = ctorName;
|
||||
info.engineName = propertyValue.name;
|
||||
info.engineUUID = propertyValue.uuid;
|
||||
}
|
||||
}
|
||||
if (!info) {
|
||||
// 空{}
|
||||
// MaterialVariant 2.4.0
|
||||
info = this._buildObjectData({
|
||||
data: new ObjectData(),
|
||||
path: path,
|
||||
value: propertyValue,
|
||||
filterKey: filterKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (info) {
|
||||
info.readonly = this._isReadonly(node, key)
|
||||
info.path = path;
|
||||
} else {
|
||||
console.error(`暂不支持的属性值`, propertyValue);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
_buildArrayData({value, path, data, keys}: BuildArrayOptions) {
|
||||
keys = keys.filter(key => !key.toString().startsWith("_"));
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
let propPath = path.concat(key.toString());
|
||||
let itemData = this._genInfoData(value, key, propPath);
|
||||
if (itemData) {
|
||||
data.add(new Property(key.toString(), itemData))
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
_buildObjectItemData({value, path, data, filterKey}: BuildObjectOptions): Property[] {
|
||||
let keys = Object.keys(value);
|
||||
if (filterKey) {
|
||||
keys = this.filterKeys(keys);// 不再进行开发者定义的数据
|
||||
}
|
||||
let ret: Property[] = []
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
let propPath = path.concat(key.toString());
|
||||
let itemData = this._genInfoData(value, key, propPath, filterKey);
|
||||
if (itemData) {
|
||||
ret.push(new Property(key, itemData))
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
filterKeys(keys: string[]) {
|
||||
// 剔除_开头的属性
|
||||
return keys.filter(key => !key.toString().startsWith("_"));
|
||||
}
|
||||
|
||||
_isInvalidValue(value: any) {
|
||||
// !!Infinity=true
|
||||
if ((value && value !== Infinity) || value === 0 || value === false || value === "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
return "null"
|
||||
} else if (value === Infinity) {
|
||||
return "Infinity"
|
||||
} else if (value === undefined) {
|
||||
return "undefined"
|
||||
} else if (Number.isNaN(value)) {
|
||||
return "NaN";
|
||||
} else {
|
||||
debugger
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_buildObjectData({value, path, data, filterKey}: BuildObjectOptions) {
|
||||
let keys = Object.keys(value);
|
||||
if (filterKey) {
|
||||
keys = this.filterKeys(keys)
|
||||
}
|
||||
// 只返回一级key,更深层级的key需要的时候,再获取,防止circle object导致的死循环
|
||||
let desc: Record<string, any> = {};
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
let propPath = path.concat(key.toString());
|
||||
let propValue = (value as any)[key];
|
||||
let keyDesc = "";
|
||||
if (Array.isArray(propValue)) {
|
||||
// 只收集一级key
|
||||
propValue.forEach(item => {
|
||||
|
||||
})
|
||||
keyDesc = `(${propValue.length}) [...]`
|
||||
} else if (this._isInvalidValue(propValue)) { // 不能改变顺序
|
||||
keyDesc = propValue;
|
||||
} else if (typeof propValue === "object") {
|
||||
keyDesc = `${propValue.constructor.name} {...}`;
|
||||
} else {
|
||||
keyDesc = propValue;
|
||||
}
|
||||
desc[key] = keyDesc;
|
||||
}
|
||||
data.data = JSON.stringify(desc);
|
||||
return data;
|
||||
}
|
||||
|
||||
private getCompName(comp: any): string {
|
||||
const nameKeys = [
|
||||
"__classname__", // 2.4.0 验证通过
|
||||
];
|
||||
for (let i = 0; i < nameKeys.length; i++) {
|
||||
let key = nameKeys[i];
|
||||
// 一般来说,这里的name是不会出现假值
|
||||
if (comp[key]) {
|
||||
return comp[key];
|
||||
}
|
||||
}
|
||||
return comp.constructor.name;
|
||||
}
|
||||
|
||||
// 校验keys的有效性,3.x有position,没有x,y,z
|
||||
_checkKeysValid(obj: any, keys: string[]) {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (!isHasProperty(obj, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_getGroupData(node: any) {
|
||||
const name = this.getCompName(node);
|
||||
let nodeGroup = new Group(name, node.uuid);
|
||||
let keys = this._getNodeKeys(node);
|
||||
for (let i = 0; i < keys.length;) {
|
||||
let key = keys[i];
|
||||
let pair = this._getPairProperty(key);
|
||||
if (pair && this._checkKeysValid(node, pair.values)) {
|
||||
let bSplice = false;
|
||||
// 把这个成对的属性剔除掉
|
||||
pair.values.forEach((item: string) => {
|
||||
let index = keys.findIndex(el => el === item);
|
||||
if (index !== -1) {
|
||||
keys.splice(index, 1);
|
||||
if (pair && item === pair.key) {
|
||||
// 切掉了自己,才能步进+1
|
||||
bSplice = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 序列化成对的属性
|
||||
let info: Vec2Data | Vec3Data | null = null;
|
||||
let pairValues = pair.values;
|
||||
if (pairValues.length === 2) {
|
||||
info = new Vec2Data();
|
||||
} else if (pairValues.length === 3) {
|
||||
info = new Vec3Data();
|
||||
}
|
||||
// todo path
|
||||
pairValues.forEach((el: string) => {
|
||||
let propertyPath = [node.uuid, el];
|
||||
let vecData = this._genInfoData(node, el, propertyPath);
|
||||
if (vecData) {
|
||||
info && info.add(new Property(el, vecData));
|
||||
}
|
||||
});
|
||||
if (info) {
|
||||
let property = new Property(pair.key, info);
|
||||
nodeGroup.addProperty(property);
|
||||
}
|
||||
if (!bSplice) {
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
let propertyPath = [node.uuid, key];
|
||||
let info = this._genInfoData(node, key, propertyPath);
|
||||
if (info) {
|
||||
nodeGroup.addProperty(new Property(key, info));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
nodeGroup.sort();
|
||||
return nodeGroup;
|
||||
}
|
||||
|
||||
// 获取节点信息,只获取一级key即可,后续
|
||||
getNodeInfo(uuid: string) {
|
||||
let node = this.inspectorGameMemoryStorage[uuid];
|
||||
if (node) {
|
||||
let groupData = [];
|
||||
// 收集节点信息
|
||||
let nodeGroup = this._getGroupData(node);
|
||||
groupData.push(nodeGroup);
|
||||
// 收集组件信息
|
||||
const nodeComp = node._components;
|
||||
for (let i = 0; i < nodeComp.length; i++) {
|
||||
let itemComp = nodeComp[i];
|
||||
this.inspectorGameMemoryStorage[itemComp.uuid] = itemComp;
|
||||
let compGroup = this._getGroupData(itemComp);
|
||||
groupData.push(compGroup);
|
||||
}
|
||||
const data: NodeInfoData = {
|
||||
uuid: uuid,
|
||||
group: groupData,
|
||||
}
|
||||
this.sendMsgToContent(Msg.NodeInfo, data);
|
||||
} else {
|
||||
// 未获取到节点数据
|
||||
console.log("未获取到节点数据");
|
||||
}
|
||||
}
|
||||
|
||||
logValue(uuid: string, key: string) {
|
||||
let nodeOrComp = this.inspectorGameMemoryStorage[uuid];
|
||||
if (nodeOrComp) {
|
||||
console.log(nodeOrComp[key]);
|
||||
}
|
||||
}
|
||||
|
||||
_isReadonly(base: Object, key: string | number): boolean {
|
||||
let ret = Object.getOwnPropertyDescriptor(base, key)
|
||||
if (ret) {
|
||||
return !(ret.set || ret.writable);
|
||||
} else {
|
||||
let proto = Object.getPrototypeOf(base);
|
||||
if (proto) {
|
||||
return this._isReadonly(proto, key)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setValue(pathArray: Array<string>, value: string): boolean {
|
||||
let target = this.inspectorGameMemoryStorage;
|
||||
// 尝试设置creator3.x的数据
|
||||
if (trySetValueWithConfig(pathArray, target, value)) {
|
||||
return true;
|
||||
}
|
||||
for (let i = 0; i < pathArray.length; i++) {
|
||||
let path = pathArray[i];
|
||||
if (i === pathArray.length - 1) {
|
||||
// 到最后的key了
|
||||
if (this._isReadonly(target, path)) {
|
||||
console.warn(`值不允许修改`);
|
||||
} else {
|
||||
target[path] = value;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// creator3.x的enumerable导致无法判断
|
||||
if (target.hasOwnProperty(path) || target[path]) {
|
||||
target = target[path];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
onMemoryInfo() {
|
||||
this.sendMsgToContent(Msg.MemoryInfo, {
|
||||
performance: {
|
||||
// @ts-ignore
|
||||
jsHeapSizeLimit: window.performance.memory.jsHeapSizeLimit,
|
||||
// @ts-ignore
|
||||
totalJSHeapSize: window.performance.memory.totalJSHeapSize,
|
||||
// @ts-ignore
|
||||
usedJSHeapSize: window.performance.memory.usedJSHeapSize,
|
||||
},
|
||||
console: {
|
||||
jsHeapSizeLimit: console.memory.jsHeapSizeLimit,
|
||||
totalJSHeapSize: console.memory.totalJSHeapSize,
|
||||
usedJSHeapSize: console.memory.usedJSHeapSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let inspector = new CCInspector();
|
||||
inspector.init();
|
||||
//@ts-ignore
|
||||
window.CCInspector = inspector;
|
||||
|
||||
|
||||
|
||||
|
@@ -1,99 +0,0 @@
|
||||
import {isVersion3} from "@/inject/util";
|
||||
|
||||
interface ConfigItem {
|
||||
path: string[],
|
||||
func: Function;
|
||||
}
|
||||
|
||||
const config: ConfigItem[] = [
|
||||
{
|
||||
path: ["position", "x"],
|
||||
func: (target: any, value: any) => {
|
||||
let pos = target.getPosition();
|
||||
pos.x = value;
|
||||
target.setPosition(pos);
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ["position", "y"],
|
||||
func: (target: any, value: any) => {
|
||||
let pos = target.getPosition();
|
||||
pos.y = value;
|
||||
target.setPosition(pos);
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ["position", "z"],
|
||||
func: (target: any, value: any) => {
|
||||
let pos = target.getPosition();
|
||||
pos.z = value;
|
||||
target.setPosition(pos);
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ["scale", "x"],
|
||||
func: ((target: any, value: any) => {
|
||||
let scale = target.getScale();
|
||||
scale.x = value;
|
||||
target.setScale(scale);
|
||||
})
|
||||
},
|
||||
{
|
||||
path: ["scale", "y"],
|
||||
func: ((target: any, value: any) => {
|
||||
let scale = target.getScale();
|
||||
scale.y = value;
|
||||
target.setScale(scale);
|
||||
})
|
||||
},
|
||||
{
|
||||
path: ["scale", "z"],
|
||||
func: ((target: any, value: any) => {
|
||||
let scale = target.getScale();
|
||||
scale.z = value;
|
||||
target.setScale(scale);
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
// 3.x不允许直接设置xyz,需要走setPosition
|
||||
export function trySetValueWithConfig(pathArray: string[], targetObject: any, targetValue: any) {
|
||||
if (isVersion3()) {
|
||||
let fullPath: string = pathArray.toString()
|
||||
let item = config.find(el => {
|
||||
return fullPath.endsWith(el.path.toString())
|
||||
});
|
||||
if (item) {
|
||||
// 将多余的path去掉
|
||||
let leftPathArray = [];
|
||||
let max = pathArray.length - item.path.length;
|
||||
for (let i = 0; i < max; i++) {
|
||||
leftPathArray.push(pathArray[i])
|
||||
}
|
||||
|
||||
let pathObjectValue = getValue(targetObject, leftPathArray);
|
||||
if (pathObjectValue) {
|
||||
try {
|
||||
item.func(pathObjectValue, targetValue);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getValue(target: any, path: string[]) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
let key = path[i];
|
||||
if (target[key] !== undefined || target.hasOwnProperty(key)) {
|
||||
target = target[key]
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
import {ArrayData, ImageData, ObjectData, Vec2Data, Vec3Data} from "@/devtools/data";
|
||||
|
||||
export interface BuildObjectOptions {
|
||||
path: string[];
|
||||
value: Object;
|
||||
data: ObjectData;
|
||||
filterKey:boolean;
|
||||
}
|
||||
|
||||
export interface BuildArrayOptions {
|
||||
path: string[];
|
||||
value: Object;
|
||||
data: ArrayData;
|
||||
keys: number[];
|
||||
}
|
||||
|
||||
export interface BuildVecOptions {
|
||||
path: string[];
|
||||
keys: string[];
|
||||
ctor: Function;
|
||||
value: Object;
|
||||
data: Vec3Data | Vec2Data;
|
||||
}
|
||||
|
||||
export interface BuildImageOptions {
|
||||
path: string[];
|
||||
ctor: Function;
|
||||
value: Object;
|
||||
data: ImageData;
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
declare const cc: any;
|
||||
|
||||
export function isVersion3() {
|
||||
if (typeof cc.ENGINE_VERSION === "string") {
|
||||
const version: string = cc.ENGINE_VERSION;
|
||||
return version.startsWith("3.")
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isHasProperty(base: Object, key: string): boolean {
|
||||
let ret = Object.getOwnPropertyDescriptor(base, key)
|
||||
if (ret) {
|
||||
return true;
|
||||
} else {
|
||||
let proto = Object.getPrototypeOf(base);
|
||||
if (proto) {
|
||||
return isHasProperty(proto, key)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
import Vue from "vue";
|
||||
import App from "./index.vue";
|
||||
import "element-ui/lib/theme-chalk/index.css"
|
||||
import ElementUI from "element-ui";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.use(ElementUI, {size: "mini"});
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
@@ -1,159 +0,0 @@
|
||||
<template>
|
||||
<div id="popup">
|
||||
<div class="head">
|
||||
<div class="name">{{ title }}</div>
|
||||
<div style="flex: 1"></div>
|
||||
<el-button class="el-icon-setting btn" @click="onClickOptions"></el-button>
|
||||
</div>
|
||||
|
||||
<div class="wechat">
|
||||
<div class="money">
|
||||
<img class="png" src="./res/money.png" alt=""/>
|
||||
<div class="tips">请我喝杯奶茶</div>
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
<div class="friends">
|
||||
<img class="png" src="./res/friend.png" alt=""/>
|
||||
<div class="tips">交个朋友</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="foot">
|
||||
<a href="https://tidys.gitee.io/doc/#" target="_blank">
|
||||
<img class="icon" src="./res/tiezi.png" alt="">
|
||||
</a>
|
||||
<a href="https://github.com/tidys/CocosCreatorPlugins/tree/master/CocosCreatorInspector" target="_blank">
|
||||
<img class="icon" src="./res/github.png" alt="">
|
||||
</a>
|
||||
<a href="https://jq.qq.com/?_wv=1027&k=5SdPdy2" target="_blank">
|
||||
<img class="icon" src="./res/qq.png" alt="">
|
||||
</a>
|
||||
<div class="space"></div>
|
||||
<div v-if="version">ver:{{ version }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Vue} from "vue-property-decorator";
|
||||
import Manifest from "../manifest.json"
|
||||
import {version} from "../../package.json"
|
||||
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class App extends Vue {
|
||||
longConn: chrome.runtime.Port | null = null
|
||||
|
||||
data() {
|
||||
return {
|
||||
title: "cc-inspector",
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
|
||||
created() {
|
||||
this._initLongConn();
|
||||
}
|
||||
|
||||
onBtnClickGitHub() {
|
||||
console.log("onBtnClickGitHub");
|
||||
}
|
||||
|
||||
onClickOptions() {
|
||||
if (chrome && chrome.tabs) {
|
||||
let {page} = Manifest.options_ui;
|
||||
if (page) {
|
||||
chrome.tabs.create({url: page})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initLongConn() {
|
||||
if (!this.longConn) {
|
||||
console.log("[popup] 初始化长连接");
|
||||
if (chrome && chrome.runtime) {
|
||||
this.longConn = chrome.runtime.connect({name: "popup"});
|
||||
this.longConn.onMessage.addListener((data: any, sender: any) => {
|
||||
this._onLongConnMsg(data, sender);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onLongConnMsg(data: string, sender: any) {
|
||||
// console.log(this.title);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import "../index.less";
|
||||
|
||||
#popup {
|
||||
width: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
|
||||
.name {
|
||||
user-select: none;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn {
|
||||
}
|
||||
}
|
||||
|
||||
.wechat {
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.png {
|
||||
width: auto;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 15px;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
color: #6d6d6d
|
||||
}
|
||||
}
|
||||
|
||||
.foot {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
|
||||
.space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin: 0 3px;
|
||||
width: auto;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Binary file not shown.
Before Width: | Height: | Size: 621 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 167 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
4
source/src/shims-chrome.d.ts
vendored
4
source/src/shims-chrome.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
declare module "*.chrome" {
|
||||
import chrome from "chrome";
|
||||
export default chrome;
|
||||
}
|
13
source/src/shims-tsx.d.ts
vendored
13
source/src/shims-tsx.d.ts
vendored
@@ -1,13 +0,0 @@
|
||||
import Vue, { VNode } from "vue";
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
interface Element extends VNode {}
|
||||
// tslint:disable no-empty-interface
|
||||
interface ElementClass extends Vue {}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
4
source/src/shims-vue.d.ts
vendored
4
source/src/shims-vue.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
import Vue from "vue";
|
||||
import App from "./index.vue";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
@@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<div id="app">test</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Vue} from "vue-property-decorator";
|
||||
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class Index extends Vue {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user