增加属性关联node,点击后tree高亮显示

This commit is contained in:
xu_yanfeng 2025-01-07 17:52:21 +08:00
parent 82215235d9
commit 16e1fdf89f
9 changed files with 353 additions and 209 deletions

@ -1,7 +1,7 @@
// eval 注入脚本的代码,变量尽量使用var,后来发现在import之后,let会自动变为var
import { uniq } from "lodash";
import { debugLog, Msg, PluginEvent, RequestLogData, RequestNodeInfoData, RequestSetPropertyData, ResponseNodeInfoData, ResponseSetPropertyData, ResponseSupportData, ResponseTreeInfoData } from "../../core/types";
import { ArrayData, BoolData, ColorData, DataType, EngineData, Group, ImageData, Info, InvalidData, NodeInfoData, NumberData, ObjectData, Property, StringData, TreeData, Vec2Data, Vec3Data } from "../../views/devtools/data";
import { ArrayData, BoolData, ColorData, DataType, EngineData, Group, ImageData, Info, InvalidData, NodeInfoData, NumberData, ObjectData, Property, StringData, TreeData, Vec2Data, Vec3Data, Vec4Data } from "../../views/devtools/data";
import { InjectEvent } from "./event";
import { getValue, trySetValueWithConfig } from "./setValue";
import { BuildArrayOptions, BuildImageOptions, BuildObjectOptions, BuildVecOptions } from "./types";
@ -284,6 +284,15 @@ export class Inspector extends InjectEvent {
return null;
}
private getUrl() {
const url = window.location.href;
const arr = url.split("/");
if (arr[arr.length - 1].indexOf(".html") > -1) {
arr.pop();
}
const ret = arr.join("/");
return ret;
}
_buildImageData(options: BuildImageOptions) {
const ctor: Function = options.ctor;
const value: Object = options.value;
@ -294,7 +303,7 @@ export class Inspector extends InjectEvent {
// 2.4.6 没有了这个属性
if (value.hasOwnProperty("_textureFilename")) {
//@ts-ignore
data.data = `${window.location.origin}/${value._textureFilename}`;
data.data = `${this.getUrl()}/${value._textureFilename}`;
} else {
data.data = null;
}
@ -303,104 +312,125 @@ export class Inspector extends InjectEvent {
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) {
_genInfoData(node: any, key: string | number, path: Array<string>): Info | null {
const propertyValue = node[key];
const make = (info: Info | null) => {
info.readonly = this._isReadonly(node, key);
info.path = path;
} else {
console.error(`暂不支持的属性值`, propertyValue);
return info;
};
let invalidType = this._isInvalidValue(propertyValue);
if (invalidType) {
const info = new InvalidData(invalidType);
return make(info);
}
return info;
switch (typeof propertyValue) {
case "boolean": {
const info = new BoolData(propertyValue);
return make(info);
}
case "number": {
const info = new NumberData(propertyValue);
return make(info);
}
case "string": {
const info = new StringData(propertyValue);
return make(info);
}
}
if (propertyValue instanceof cc.Color) {
let hex = propertyValue.toHEX();
const info = new ColorData(`#${hex}`);
return make(info);
}
if (Array.isArray(propertyValue)) {
let keys: number[] = [];
for (let i = 0; i < propertyValue.length; i++) {
keys.push(i);
}
const info = this._buildArrayData({
data: new ArrayData(),
path: path,
value: propertyValue,
keys: keys,
});
return make(info);
}
let info: Info = this._buildVecData({
ctor: cc.Size,
path: path,
data: new Vec2Data(),
keys: ["width", "height"],
value: propertyValue,
});
if (info) {
return make(info);
}
info = this._buildVecData({
// @ts-ignore
ctor: cc.Vec3,
path: path,
data: new Vec3Data(),
keys: ["x", "y", "z"],
value: propertyValue,
});
if (info) {
return make(info);
}
info = this._buildVecData({
// @ts-ignore
ctor: cc.Vec2,
path: path,
data: new Vec2Data(),
keys: ["x", "y"],
value: propertyValue,
});
if (info) {
return make(info);
}
info = this._buildImageData({
ctor: cc.SpriteFrame,
data: new ImageData(),
path: path,
value: propertyValue,
});
if (info) {
return make(info);
}
let ctorName = propertyValue.__classname__;
if (ctorName) {
const engine = new EngineData();
engine.engineType = ctorName;
engine.engineName = propertyValue.name;
if (propertyValue instanceof cc.Node) {
engine.engineNode = engine.engineNode = propertyValue.uuid;
} else if (propertyValue instanceof cc.Asset) {
engine.engineUUID = propertyValue._uuid;
engine.engineNode = "";
} else if (propertyValue instanceof cc.Component) {
engine.engineUUID = propertyValue.uuid;
if (propertyValue.node) {
engine.engineNode = propertyValue.node.uuid;
} else {
engine.engineNode = "";
}
}
return make(engine);
}
if (typeof propertyValue === "object") {
// 空{} MaterialVariant 2.4.0
info = this._buildObjectData({
data: new ObjectData(),
path: path,
value: propertyValue,
});
if (info) {
return make(info);
}
}
return null;
}
_buildArrayData({ value, path, data, keys }: BuildArrayOptions) {
@ -416,21 +446,18 @@ export class Inspector extends InjectEvent {
return data;
}
_buildObjectItemData({ value, path, data, filterKey }: BuildObjectOptions): Property[] {
_buildObjectData({ value, path, data }: BuildObjectOptions) {
let keys = Object.keys(value);
if (filterKey) {
keys = this.filterKeys(keys); // 不再进行开发者定义的数据
}
let ret: Property[] = [];
keys = this.filterKeys(keys);
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);
let itemData = this._genInfoData(value, key, propPath);
if (itemData) {
ret.push(new Property(key, itemData));
data.add(new Property(key, itemData));
}
}
return ret;
return data;
}
filterKeys(keys: string[]) {
@ -453,42 +480,10 @@ export class Inspector extends InjectEvent {
} 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 验证通过
@ -535,12 +530,14 @@ export class Inspector extends InjectEvent {
}
});
// 序列化成对的属性
let info: Vec2Data | Vec3Data | null = null;
let info: Vec2Data | Vec3Data | Vec4Data | null = null;
let pairValues = pair.values;
if (pairValues.length === 2) {
info = new Vec2Data();
} else if (pairValues.length === 3) {
info = new Vec3Data();
} else if (pairValues.length === 4) {
info = new Vec4Data();
}
// todo path
pairValues.forEach((el: string) => {
@ -570,7 +567,6 @@ export class Inspector extends InjectEvent {
return nodeGroup;
}
// 获取节点信息只获取一级key即可后续
getNodeInfo(uuid: string) {
let node = this.inspectorGameMemoryStorage[uuid];
if (node) {

@ -4,7 +4,6 @@ export interface BuildObjectOptions {
path: string[];
value: Object;
data: ObjectData;
filterKey: boolean;
}
export interface BuildArrayOptions {

@ -4,11 +4,140 @@ export enum CompType {
Label = "cc.Label",
Widget = "cc.Widget",
Camera = "cc.Camera",
Canvas = "cc.Canvas",
Mask = "cc.Mask",
ScrollView = "cc.ScrollView",
UITransform = "cc.UITransform",
ParticleSystem = "cc.ParticleSystem",
EditBox = "cc.EditBox",
TiledTile = "cc.TiledTile",
Light = "cc.Light",
VideoPlayer = "cc.VideoPlayer",
MeshRenderer = "cc.MeshRenderer",
ProgressBar = "cc.ProgressBar",
RichText = "cc.RichText",
Slider = "cc.Slider",
PageView = "cc.PageView",
Webview = "cc.WebView",
ToggleGroup = "cc.ToggleGroup",
ToggleContainer = "cc.ToggleContainer",
Toggle = "cc.Toggle",
Button = "cc.Button",
}
export function getSimpleProperties(typeName: string): string[] {
const config = {};
config[CompType.Button] = [
"target", //
"interactable",
"enableAutoGrayEffect",
"transition",
"duration",
"zoomScale",
"normalColor",
"normalSprite",
"pressedColor",
"pressedSprite",
"disabledColor",
"disabledSprite",
"hoverColor",
"hoverSprite",
"duration",
];
config[CompType.Toggle] = [
"target",
"interactable",
"enableAutoGrayEffect",
"transition", //
"duration",
"zoomScale",
"isChecked",
"checkMark",
"toggleGroup",
];
config[CompType.ToggleContainer] = ["allowSwitchOff"];
config[CompType.ToggleGroup] = ["allowSwitchOff"];
config[CompType.Webview] = ["url"];
config[CompType.PageView] = [
"content",
"sizeMode",
"direction",
"scrollThreshold",
"autoPageTurningThreshold", //
"inertia",
"brake",
"elastic",
"bounceDuration",
"indicator",
"pageTurningSpeed",
"pageTurningEventTiming",
"cancelInnerEvents",
];
config[CompType.Slider] = ["handle", "direction", "progress"];
config[CompType.RichText] = [
"string",
"horizontalAlign",
"fontSize",
"font",
"fontFamily",
"useSystemFont",
"cacheMode",
"maxWidth",
"lineHeight",
"imageAtlas",
"handleTouchEvent", //
];
config[CompType.ProgressBar] = [
"barSprite",
"mode",
"totalLength",
"progress",
"reverse", //
];
config[CompType.MeshRenderer] = [
"materials", //
"mesh",
"receiveShadows",
"shadowCastingMode",
"enableAutoBatch",
];
config[CompType.VideoPlayer] = [
"resourceType",
"clip",
"currentTime",
"volume",
"mute",
"keepAspectRatio",
"isFullscreen",
"stayOnBottom", //
];
config[CompType.Light] = [
"type",
"color",
"intensity", //
"range",
"shadowType",
"range",
"spotAngle",
"spotExp",
];
config[CompType.TiledTile] = ["x", "y", "grid"];
config[CompType.EditBox] = [
"string",
"placeholder", //
"background",
"textLabel",
"placeholderLabel",
"keyboardReturnType",
"inputFlag",
"inputMode",
"maxLength",
"tabIndex",
];
config[CompType.ParticleSystem] = [
"playOnLoad", //
"autoRemoveOnFinish",
];
config[CompType.Node] = [
"position", //
"rotation",
@ -69,6 +198,29 @@ export function getSimpleProperties(typeName: string): string[] {
"trim",
"type",
];
config[CompType.ScrollView] = [
"bounceDuration",
"content", //
"horizontal",
"vertical",
"inertia",
"brake",
"elastic",
"bounceDuration",
"verticalScrollBar",
];
config[CompType.Mask] = [
"alphaThreshold",
"inverted",
"segments", //
"spriteFrame",
"type",
];
config[CompType.Canvas] = [
"fitWidth",
"fitHeight", //
"designResolution",
];
return config[typeName] || [];
}
export const VisibleProp = {

@ -121,7 +121,14 @@ export interface FrameDetails {
*/
export class EngineData extends Info {
public engineType: string = "";
/**
* uuid
*/
public engineUUID: string = "";
/**
* 便
*/
public engineNode: string = "";
public engineName: string = "";
constructor() {
@ -133,12 +140,14 @@ export class EngineData extends Info {
this.engineType = data.engineType;
this.engineUUID = data.engineUUID;
this.engineName = data.engineName;
this.engineNode = data.engineNode;
return this;
}
init(name: string, type: string, uuid: string) {
init(name: string, type: string, uuid: string, node: string = "") {
this.engineName = name;
this.engineType = type;
this.engineUUID = uuid;
this.engineNode = node || uuid;
return this;
}
public isEngine(): boolean {

@ -28,8 +28,8 @@ export default defineComponent({
components: { CCButtonGroup, CCInput, CCTree, CCDock },
setup() {
const funcShowPlace = (data: EngineData) => {
console.log(toRaw(data));
_expand(data.engineUUID);
console.log(data);
_expand(data.engineNode);
};
const funcEnableSchedule = (b: boolean) => {
if (b) {
@ -67,33 +67,9 @@ export default defineComponent({
timer.clean();
});
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();
}
}
}
if (elTree.value) {
elTree.value.handExpand(uuid, { highlight: true });
}
circle(treeData);
expandKeys.forEach((key) => {
if (!expandedKeys.value.find((el) => el === key)) {
expandedKeys.value.push(key);
}
});
// uuid
}
function updateTree() {
console.log("update tree info");

@ -1,6 +1,6 @@
<template>
<div id="devtools">
<Test> </Test>
<Test v-if="false"> </Test>
<div class="head" v-show="iframes.length > 1">
<div class="label">inspect target:</div>
<CCSelect v-model:value="frameID" @change="onChangeFrame" :data="getFramesData()"> </CCSelect>

@ -117,15 +117,23 @@ export class TestServer {
.buildChild("arr[obj]")
.buildComponent("group-arr[obj]") //
.buildProperty("arr", new ArrayData().testObject()); //
this.testData.buildChild("engine").buildComponent("group4").buildProperty("node", new EngineData().init("name", "cc_Node", "uuid")).buildProperty("sprite", new EngineData().init("name", "cc_Sprite", "uuid")).buildProperty("label", new EngineData().init("name", "cc_Label", "uuid")).buildProperty("un_known", new EngineData().init("name", "un_known", "uuid"));
const c = this.testData.buildChild("str1");
c.active = false;
c.buildComponent("group51").buildProperty("str1", new StringData("str1"));
c.buildComponent("group52").buildProperty("num", new NumberData(200));
const node = this.testData.buildChild("str1");
node.active = false;
const comp = node.buildComponent("group51");
comp.buildProperty("str1", new StringData("str1"));
node.buildComponent("group52").buildProperty("num", new NumberData(200));
this.testData.buildChild("str2").buildComponent("group6").buildProperty("str2", new StringData("str2"));
this.testData
.buildChild("engine")
.buildComponent("group4") //
.buildProperty("node", new EngineData().init(node.name, "cc_Node", node.id))
.buildProperty("sprite", new EngineData().init(node.name, "cc_Sprite", node.id))
.buildProperty("label", new EngineData().init(node.name, "cc_Label", node.id))
.buildProperty("un_known", new EngineData().init(comp.name, "un_known", comp.id, node.id));
this.testData
.buildChild("Invalid")
.buildComponent("group7")

@ -1,16 +1,13 @@
<template>
<div class="property-engine">
<div class="property-engine" @click="onPlaceInTree">
<i class="icon iconfont" :class="getEngineTypeIcon()"></i>
<div class="type">{{ data.engineType }}</div>
<div class="name">{{ data.engineName }}</div>
<div class="place" @click="onPlaceInTree">
<i class="iconfont icon_place"></i>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { defineComponent, PropType, toRaw } from "vue";
import { Bus, BusMsg } from "../bus";
import { EngineData } from "../data";
export default defineComponent({
@ -25,7 +22,7 @@ export default defineComponent({
setup(props, context) {
return {
onPlaceInTree() {
Bus.emit(BusMsg.ShowPlace, props.data);
Bus.emit(BusMsg.ShowPlace, toRaw(props.data));
},
getEngineTypeIcon() {
switch (props.data.engineType) {
@ -49,6 +46,7 @@ export default defineComponent({
<style scoped lang="less">
@my-height: 20px;
.property-engine {
cursor: pointer;
height: @my-height;
box-sizing: border-box;
flex: 1;
@ -64,46 +62,37 @@ export default defineComponent({
align-content: center;
display: flex;
flex-direction: row;
margin-right: 2px;
color: white;
&:hover {
color: #414141;
}
&:active {
color: black;
}
.icon {
font-size: 14px;
width: @my-height;
margin: 0 5px;
margin: 0 1px 0 2px;
}
.type {
text-align: left;
flex: 1;
color: white;
font-size: 12px;
display: flex;
align-content: center;
height: @my-height;
padding: 0 5px;
background-color: rgb(48, 158, 0);
align-items: center;
min-width: 80px;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.name {
user-select: none;
font-size: 12px;
color: white;
flex: 1;
height: @my-height;
background-color: rgb(184, 157, 5);
background-color: #d4873d;
display: flex;
padding: 0 5px;
align-items: center;
align-content: center;
}
.place {
padding: 0 5px;
cursor: pointer;
&:hover {
color: #6d6d6d;
}
&:active {
color: #000000;
}
}
}
</style>

@ -1,17 +1,17 @@
<template>
<div v-if="data.isImage()" class="property-image">
<div class="box">
<img :src="data.data" alt="图片" class="img" />
<img :src="data.data" alt="图片" @click="onClickImg" class="img" />
</div>
<div class="url" :title="data.data">{{ data.data }}</div>
<div class="url" :title="data.data"></div>
<div style="flex: 1"></div>
<i class="print iconfont icon_print" @click="onShowValueInConsole"></i>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { defineComponent, PropType, toRaw } from "vue";
import { ImageData } from "../data";
export default defineComponent({
@ -24,6 +24,12 @@ export default defineComponent({
},
setup(props) {
return {
onClickImg() {
const url = toRaw(props.data.data);
if (url && url.startsWith("http")) {
window.open(url);
}
},
onShowValueInConsole() {
if (Array.isArray(props.data.path)) {
let uuid = props.data.path[0];
@ -47,7 +53,13 @@ export default defineComponent({
align-content: center;
align-items: center;
height: 30px;
box-sizing: border-box;
border: 1px solid #409eff;
border-radius: 2px;
margin-right: 2px;
.box {
overflow: hidden;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
@ -55,8 +67,11 @@ export default defineComponent({
width: 80px;
justify-content: center;
.img {
cursor: pointer;
padding: 2px 0;
height: 30px;
width: 30px;
box-sizing: border-box;
object-fit: contain;
}
}