终于把属性对接完了

This commit is contained in:
xu_yanfeng 2024-12-11 14:56:50 +08:00
parent 004b8f8241
commit b7dbec512a
7 changed files with 390 additions and 319 deletions

View File

@ -4,7 +4,8 @@ export enum BusMsg {
ShowPlace = "ShowPlace",
RequestObjectData = "RequestObjectData",
LogData = "LogData",
FoldAllGroup = "FoldAllGroup"
FoldAllGroup = "FoldAllGroup",
UpdateSettings = "UpdateSettings",
}
export default new TinyEmitter();

View File

@ -47,6 +47,7 @@ class ConnectBackground implements TestClient {
}
}
postMessageToBackground(msg: Msg, data?: any) {
if (CCP.Adaptation.Env.isChromeDevtools) {
if (this.connect) {
let sendData = new PluginEvent(Page.Devtools, Page.Background, msg, data)
this.connect.postMessage(sendData)
@ -55,6 +56,9 @@ class ConnectBackground implements TestClient {
this._initConnect();
this.postMessageToBackground(msg, data)
}
} else {
testServer.recv(msg, data);
}
}
}

View File

@ -6,6 +6,7 @@ export enum DataType {
Text = 'Text',
Vec2 = 'Vec2',
Vec3 = 'Vec3',
Vec4 = 'Vec4',
Enum = 'Enum',
Bool = 'Bool',
Color = 'Color',
@ -29,6 +30,7 @@ export class Info {
public isEnum(): boolean { return false; }
public isVec2(): boolean { return false; }
public isVec3(): boolean { return false; }
public isVec4(): boolean { return false; }
public isBool(): boolean { return false; }
public isText(): boolean { return false; }
public isString(): boolean { return false; }
@ -43,15 +45,19 @@ export class Info {
}
export class TextData extends Info {
constructor() {
constructor(data: string = "") {
super();
this.type = DataType.Text;
this.data = data;
}
public isText(): boolean { return true; }
}
export interface ObjectItemRequestData {
id: string | null;
/**
*
*/
data: Property[];
}
@ -60,6 +66,9 @@ export interface FrameDetails {
url: string;
}
/**
* `@property(cc.Label)`
*/
export class EngineData extends Info {
public engineType: string = "";
public engineUUID: string = "";
@ -85,6 +94,7 @@ export class ArrayData extends Info {
return this;
}
public isArray(): boolean { return true; }
public isArrayOrObject(): boolean { return true; }
}
export class ObjectDataItem extends Info {
@ -98,6 +108,7 @@ export class ObjectData extends Info {
this.type = DataType.Object;
}
public isObject(): boolean { return true; }
public isArrayOrObject(): boolean { return true; }
}
export class InvalidData extends Info {
@ -184,7 +195,24 @@ export class Vec3Data extends Info {
return true;
}
}
export class Vec4Data extends Vec2Data {
data: Array<Property> = [];
constructor() {
super();
this.type = DataType.Vec4;
this.data = [];
return this;
}
add(info: Property) {
this.data.push(info);
return this;
}
public isVec4(): boolean {
return true;
}
}
export class ImageData extends Info {
data: string | null = null;
@ -226,6 +254,9 @@ export class Property {
}
export class Group {
/**
* UUID
*/
public id: string = "";
public name: string = "group";
public data: Array<Property> = [];

View File

@ -137,7 +137,7 @@ export default defineComponent({
if (item.id === targetUUID) {
return true;
}
if (circle(item.children)) {
if (circle(item.children || [])) {
return true;
}
}
@ -147,6 +147,9 @@ export default defineComponent({
return circle(data);
}
/**
* 请求属性的列表如果一个属性请求失败会阻断后续的相同请求因为都已经失败了就没必要再响应请求了
*/
const requestList: Array<{ id: string; cb: Function }> = [];
function _expand(uuid: string) {
@ -205,7 +208,6 @@ export default defineComponent({
if (!Array.isArray(data)) {
data = [data];
}
console.log(data);
treeData.value = data;
if (_checkSelectedUUID()) {
updateNodeInfo();

View File

@ -1,5 +1,5 @@
import { Msg, Page, PluginEvent } from "../../../core/types";
import { NodeInfoData, TreeData } from "../data";
import { ArrayData, BoolData, ColorData, EngineData, EnumData, Group, Info, NodeInfoData, NumberData, ObjectData, ObjectItemRequestData, Property, StringData, TextData, TreeData, Vec2Data, Vec3Data, Vec4Data } from "../data";
export class TestClient {
recv(event: PluginEvent) {
@ -13,26 +13,134 @@ export class TestServer {
recv(msg: string, data: any) {
switch (msg) {
case Msg.NodeInfo: {
console.log(data);
const id: string = data as string;
const group = new Group("test");
{
const text = new TextData("text1");
group.addProperty(new Property("text", text))
}
{
const number = new NumberData(100);
group.addProperty(new Property("number", number))
}
{
const str = new StringData("str");
group.addProperty(new Property("str", str))
}
{
const v2 = new Vec2Data();
v2.add(new Property("x", new NumberData(100)));
v2.add(new Property("y", new NumberData(200)));
group.addProperty(new Property("v2", v2))
}
{
const v3 = new Vec3Data();
v3.add(new Property("x", new NumberData(100)));
v3.add(new Property("y", new NumberData(200)));
v3.add(new Property("z", new NumberData(300)));
group.addProperty(new Property("v3", v3))
}
{
const v4 = new Vec4Data();
v4.add(new Property("x", new NumberData(100)));
v4.add(new Property("y", new NumberData(200)));
v4.add(new Property("z", new NumberData(300)));
v4.add(new Property("w", new NumberData(400)));
group.addProperty(new Property("v4", v4))
}
{
const b = new BoolData(true);
group.addProperty(new Property("bool", b))
}
{
const e = new EnumData();
e.values.push({ name: "a", value: 1 });
e.values.push({ name: "b", value: 2 });
group.addProperty(new Property("enum", e))
}
{
const c = new ColorData('#f00');
group.addProperty(new Property("color", c))
}
{
const arr = new ArrayData();
arr.add(new Property("item1", new TextData("text")));
arr.add(new Property("item2", new BoolData(true)));
group.addProperty(new Property("arr", arr))
}
{
const obj = new ObjectData();
obj.data = JSON.stringify({ fack: 'test' });
group.addProperty(new Property("obj", obj));
}
{
const engine = new EngineData();
engine.engineName = "engineName";
engine.engineType = "engineType";
engine.engineUUID = "engineUUID";
group.addProperty(new Property("engine", engine))
}
{
const engine = new EngineData();
engine.engineName = "engineName";
engine.engineType = "cc_Node";
engine.engineUUID = "engineUUID";
group.addProperty(new Property("engine", engine))
}
{
const engine = new EngineData();
engine.engineName = "engineName";
engine.engineType = "cc_Srpite";
engine.engineUUID = "engineUUID";
group.addProperty(new Property("engine", engine))
}
{
const engine = new EngineData();
engine.engineName = "engineName";
engine.engineType = "cc_Label";
engine.engineUUID = "engineUUID";
group.addProperty(new Property("engine", engine))
}
const ret: NodeInfoData = {
uuid: "1",
group: []
uuid: id,
group: [group,]
};
const event = new PluginEvent(Page.Background, Page.Devtools, Msg.NodeInfo, ret);
this.send(event);
break;
}
case Msg.TreeInfo: {
const data: TreeData = {
id: "1",
const ret: TreeData = {
id: "root",
text: "root",
active: true,
children: [],
};
const event = new PluginEvent(Page.Inject, Page.Devtools, Msg.TreeInfo, data);
const event = new PluginEvent(Page.Inject, Page.Devtools, Msg.TreeInfo, ret);
this.send(event);
break;
}
case Msg.SetProperty: {
console.log(data);
break;
}
case Msg.GetObjectItemData: {
const d = data as ObjectData;
const property = [];
property.push(new Property("fake", new TextData('test')));
const ret: ObjectItemRequestData = {
id: d.id,
data: property,
}
const event = new PluginEvent(Page.Inject, Page.Devtools, Msg.GetObjectItemData, ret);
this.send(event)
break;
}
case Msg.LogData: {
console.log(data);
break;
}
default:
break;
}

View File

@ -0,0 +1,111 @@
<template>
<div class="property-engine">
<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 ccui from "@xuyanfeng/cc-ui";
import { defineComponent, PropType, ref } from "vue";
import Bus, { BusMsg } from "../bus";
import { EngineData, NumberData, Property } from "../data";
export default defineComponent({
name: "property-engine",
components: {},
props: {
data: {
type: Object as PropType<EngineData>,
default: () => new EngineData(),
},
},
setup(props, context) {
return {
onPlaceInTree() {
Bus.emit(BusMsg.ShowPlace, props.data);
},
getEngineTypeIcon() {
console.log(props.data.engineType);
switch (props.data.engineType) {
case "cc_Sprite": {
return "icon_picture";
}
case "cc_Label": {
return "icon_text";
}
case "cc_Node": {
return "icon_node";
}
}
return "icon_unknown";
},
};
},
});
</script>
<style scoped lang="less">
@my-height: 20px;
.property-engine {
height: @my-height;
box-sizing: border-box;
flex: 1;
display: flex;
flex-direction: row;
border: solid #409eff 1px;
border-radius: 3px;
align-items: center;
align-content: center;
background-color: cornflowerblue;
height: @my-height;
align-items: center;
align-content: center;
display: flex;
flex-direction: row;
.icon {
font-size: 14px;
width: @my-height;
margin: 0 5px;
}
.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;
}
.name {
font-size: 12px;
color: white;
flex: 1;
height: @my-height;
background-color: rgb(184, 157, 5);
display: flex;
padding: 0 5px;
align-items: center;
align-content: center;
}
.place {
padding: 0 5px;
cursor: pointer;
&:hover {
color: #6d6d6d;
}
&:active {
color: #000000;
}
}
}
</style>

View File

@ -1,48 +1,19 @@
<template>
<div id="ui-prop">
<div class="normal-data" style="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"
v-if="arrow"
:class="fold ? 'iconfont icon_arrow_right' : 'iconfont icon_arrow_down'"
:style="{
visibility: isArrayOrObject() ? 'visible' : 'hidden',
'margin-left': indent * 10 + 'px',
}"
>
</i>
<div class="text" ref="propText">
<!-- 使用CCProp -->
<!-- <el-popover
placement="top"
trigger="hover"
:disabled="!isShowTooltip()"
>
<div>{{ name }}</div>
<span>{{ name }}</span>
</el-popover> -->
</div>
</div>
<div class="value">
<div class="ui-prop">
<CCProp :name="name" :icon="icon" :head-width="headWidth" v-model:expand="expand" :arrow="value.isArrayOrObject()" :slide="value.isNumber()" :indent="indent * 10" @change-expand="onClickFold">
<div class="prop-value">
<div v-if="value.isInvalid()" class="invalid">
{{ value.data }}
</div>
<CCInput v-if="value.isString()" v-model="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCInput>
<CCInput v-if="value.isText()" type="textarea" :autosize="{ minRows: 3, maxRows: 5 }" placeholder="请输入内容" :disabled="value.readonly" @change="onChangeValue" v-model="value.data"> </CCInput>
<CCInputNumber v-if="value.isNumber()" style="width: 100%; text-align: left" v-model="value.data" :step="step" :disabled="value.readonly" @change="onChangeValue" controls-position="right"></CCInputNumber>
<div v-if="value.isVec2() || value.isVec3()" class="vec">
<ui-prop v-for="(vec, index) in value.data" :key="index" :arrow="false" :value="vec.value" :name="vec.name"> </ui-prop>
</div>
<CCSelect v-model="value.data" :disabled="value.readonly" :data="getEnumValues(value)" v-if="value.isEnum()" style="width: 100%" @change="onChangeValue"> </CCSelect>
<CCCheckBox v-model="value.data" v-if="value.isBool()" :disabled="value.readonly" @change="onChangeValue"> </CCCheckBox>
<div class="color" v-if="value.isColor()">
<CCColor style="position: absolute" :disabled="value.readonly" v-model="value.data" @change="onChangeValue"> </CCColor>
<div class="hex" :style="{ color: colorReverse(value.data) }">
{{ value.data }}
</div>
<CCInput v-if="value.isString()" v-model:value="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCInput>
<CCTextarea v-if="value.isText()" v-model:value="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCTextarea>
<CCInputNumber v-if="value.isNumber()" v-model:value="value.data" :step="step" :disabled="value.readonly" @change="onChangeValue"></CCInputNumber>
<div v-if="value.isVec2() || value.isVec3() || value.isVec4()" class="vec">
<UiProp v-for="(vec, index) in value.data" :icon="!!index" head-width="auto" :key="index" :arrow="false" :value="vec.value" :name="vec.name"> </UiProp>
</div>
<CCSelect v-if="value.isEnum()" v-model:value="value.data" :disabled="value.readonly" :data="getEnumValues(value)" @change="onChangeValue"> </CCSelect>
<CCCheckBox v-if="value.isBool()" v-model:value="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCCheckBox>
<CCColor v-if="value.isColor()" :show-color-text="true" :disabled="value.readonly" v-model:color="value.data" @change="onChangeValue"> </CCColor>
<div v-if="value.isImage()" class="image-property">
<!-- TODO: 适配 -->
<div v-if="value.isImage() || true" placement="top" trigger="hover">
@ -55,28 +26,14 @@
<CCButton @click="onShowValueInConsole">log</CCButton>
</div>
</div>
<div v-if="value.isEngine()" class="engine">
<div class="head">
<i class="icon" :class="getEngineTypeIcon()"></i>
<div class="type">{{ value["engineType"] }}</div>
<Engine v-if="value.isEngine()" v-model:data="(value as EngineData)"> </Engine>
<div v-if="value.isObject() && !expand" class="objectDesc">{{ value.data }}</div>
<div v-if="value.isArray()" class="array">Array[{{ value.data.length }}]</div>
</div>
<div class="name">{{ value["engineName"] }}</div>
<CCButton @click="onPlaceInTree" type="primary">
<i class="iconfont icon_place"></i>
</CCButton>
</div>
<div v-if="value.isObject() && fold" class="objectDesc">
{{ value.data }}
</div>
<div v-if="value.isArray()" class="array">Array({{ value.data.length }})</div>
<div class="slot" v-if="false">
<slot></slot>
</div>
</div>
</div>
<div v-if="isArrayOrObject()">
<div v-show="!fold && subData" style="display: flex; flex-direction: column">
<ui-prop v-for="(arr, index) in subData" :key="index" :indent="indent + 1" :value="arr.value" :name="getName(value.isArray(), arr)"> </ui-prop>
</CCProp>
<div v-if="value.isArrayOrObject()">
<div v-show="expand && subData">
<UiProp v-for="(arr, index) in subData" :key="index" :indent="indent + 1" :value="arr.value" :name="getName(value.isArray(), arr)"> </UiProp>
</div>
</div>
</div>
@ -89,38 +46,33 @@ import { defineComponent, onMounted, onUnmounted, PropType, ref, toRaw, watch }
import { Msg } from "../../../core/types";
import Bus, { BusMsg } from "../bus";
import { connectBackground } from "../connectBackground";
import { DataType, EngineData, EnumData, Info, Property } from "../data";
const { CCInput, CCButton, CCInputNumber, CCSelect, CCCheckBox, CCColor } = ccui.components;
import { DataType, EngineData, EnumData, Info, NumberData, Property, StringData, TextData, Vec2Data, Vec3Data } from "../data";
import Engine from "./property-engine.vue";
const { CCInput, CCTextarea, CCProp, CCButton, CCInputNumber, CCSelect, CCCheckBox, CCColor } = ccui.components;
export default defineComponent({
name: "UiProp",
components: {
CCInput,
CCButton,
CCInputNumber,
CCSelect,
CCCheckBox,
CCColor,
},
name: "ui-prop",
components: { CCProp, Engine, CCTextarea, CCInput, CCButton, CCInputNumber, CCSelect, CCCheckBox, CCColor },
props: {
name: { type: String, default: "" },
indent: { type: Number, default: 0 },
arrow: { type: Boolean, default: true },
icon: { type: Boolean, default: true },
headWidth: { type: String, default: "120px" },
arrow: { type: Boolean, default: false },
step: { type: Number, default: 1 },
value: {
type: Object as PropType<Info | EngineData | EnumData>,
type: Object as PropType<Info | EngineData | EnumData | NumberData | StringData | TextData | Vec2Data | Vec3Data>,
default: () => {},
},
},
setup(props, { emit }) {
const { value, step } = props;
const fold = ref(true);
let clientX: number = 0;
const expand = ref(false);
onMounted(() => {
watchValue();
});
function watchValue() {
fold.value = true;
// fold.value = true;
if (value.isArray()) {
subData.value = value.data;
} else {
@ -130,32 +82,10 @@ export default defineComponent({
watch(props.value, () => {
watchValue();
});
const subData = ref<Property[]>([]);
const propText = ref<HTMLDivElement>();
const subData = ref<Array<Property> | null>(null);
function _onMouseMove(event: MouseEvent) {
let x = event.clientX;
let calcStep = step || 0;
if (x > clientX) {
calcStep = Math.abs(calcStep);
} else {
calcStep = -Math.abs(calcStep);
}
emit("movestep", calcStep);
clientX = x;
}
function _onMouseUp(event: MouseEvent) {
document.removeEventListener("mousemove", _onMouseMove);
document.removeEventListener("mouseup", _onMouseUp);
document.removeEventListener("onselectstart", _onSelect);
}
function _onSelect() {
return false;
}
return {
fold,
propText,
expand,
subData,
getEnumValues(data: any): Option[] {
const value: EnumData = data;
@ -168,56 +98,30 @@ export default defineComponent({
});
return ret;
},
isArrayOrObject() {
return value && (value.isArray() || value.isObject());
},
isImageValid() {
return !!value.data;
},
onPlaceInTree() {
Bus.emit(BusMsg.ShowPlace, value);
},
isShowTooltip() {
const el: HTMLDivElement = propText.value as HTMLDivElement;
if (el) {
if (el.scrollWidth > el.offsetWidth) {
//
return true;
}
}
return false;
},
getEngineTypeIcon() {
switch ((value as EngineData).engineType) {
case "cc_Sprite": {
return "icon_picture";
}
case "cc_Label": {
return "icon_text";
}
case "cc_Node": {
return "icon_node";
}
}
return "icon_unknown";
},
getName(isArray: boolean, arr: Property) {
const type = arr.value.type;
if (isArray) {
return `[${arr.name}]`;
return arr.name;
} else {
return arr.name;
}
},
onClickFold() {
if (value.isObject() && fold && !subData) {
onClickFold(v: boolean) {
if (value.isArray()) {
return;
}
const s = toRaw(subData.value);
const e = toRaw(expand.value);
if (value.isObject() && s === null && e === true) {
// objectitem
Bus.emit(BusMsg.RequestObjectData, value, (info: Property[]) => {
fold.value = false;
Bus.emit(BusMsg.RequestObjectData, toRaw(value), (info: Property[]) => {
subData.value = info;
});
} else {
fold.value = !fold.value;
}
},
onShowValueInConsole() {
@ -231,35 +135,93 @@ export default defineComponent({
},
onChangeValue() {
if (!value.readonly) {
connectBackground.postMessageToBackground(Msg.SetProperty, value);
connectBackground.postMessageToBackground(Msg.SetProperty, toRaw(value));
}
},
onPropNameMouseDown(event: MouseEvent) {
document.addEventListener("mousemove", _onMouseMove);
document.addEventListener("mouseup", _onMouseUp);
document.addEventListener("onselectstart", _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);
},
_onMouseMove,
_onMouseUp,
_onSelect,
};
},
});
</script>
<style scoped lang="less">
#ui-prop {
.ui-prop {
min-height: 30px;
margin: 1px 0;
display: flex;
flex-direction: column;
justify-content: center;
.prop-value {
flex: 3;
text-align: left;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: flex-start;
.invalid {
color: grey;
}
.objectDesc {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
font-size: 12px;
color: #d2d2d2;
}
.array {
display: flex;
flex-direction: column;
color: #d2d2d2;
font-size: 12px;
}
.vec {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
.ui-prop {
flex: 1;
.cc-prop {
overflow: hidden;
}
}
// #ui-prop:first-child {
// margin-left: 0;
// }
// #ui-prop:last-child {
// margin-right: 0;
// }
}
.array-object {
flex: 1;
max-width: 100%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
}
.image-property {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
height: 36px;
}
.slot {
display: flex;
width: 100%;
}
}
.normal-data {
margin: 0;
min-height: 30px;
@ -278,13 +240,6 @@ export default defineComponent({
align-items: center;
min-width: 90px;
.data-arrow {
width: 20px;
height: 16px;
font-size: 16px;
cursor: pointer;
}
.text {
flex: 1;
white-space: nowrap;
@ -301,147 +256,6 @@ export default defineComponent({
}
}
}
.value {
flex: 3;
text-align: left;
height: 100%;
overflow: hidden;
min-width: 400px;
.color {
position: relative;
height: 30px;
.hex {
line-height: 30px;
position: relative;
text-align: center;
user-select: none;
pointer-events: none;
}
}
.invalid {
color: grey;
}
.objectDesc {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
}
.array {
display: flex;
flex-direction: column;
color: red;
}
.engine {
display: flex;
flex-direction: row;
border: solid #409eff 1px;
border-radius: 5px;
align-items: center;
align-content: center;
.head {
background-color: cornflowerblue;
height: 28px;
align-items: center;
align-content: center;
display: flex;
flex-direction: row;
.icon {
font-size: 20px;
width: 20px;
margin-left: 5px;
}
.type {
display: flex;
align-content: center;
align-items: center;
margin: 0 5px;
}
}
.name {
flex: 1;
height: 28px;
padding-left: 5px;
background-color: gold;
display: flex;
align-items: center;
align-content: center;
}
}
.vec {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
#ui-prop {
margin: 0 10px;
flex: 1;
.normal-data {
.value {
min-width: 50px;
}
.key {
min-width: unset;
display: block;
margin-right: 5px;
flex: unset;
}
}
}
#ui-prop:first-child {
margin-left: 0;
}
#ui-prop:last-child {
margin-right: 0;
}
}
.array-object {
flex: 1;
max-width: 100%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
.text {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.image-property {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
height: 36px;
}
.slot {
display: flex;
width: 100%;
}
}
}
}
</style>