From 021e2e7821c12e2fe13715e7c6ba8b2386dd9a28 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Thu, 24 Mar 2022 22:42:20 +0800 Subject: [PATCH 1/8] Add setting popup component --- source/Component/CommandBar/CommandBar.tsx | 4 +-- .../Component/SettingPopup/SettingPopup.scss | 6 ++++ .../Component/SettingPopup/SettingPopup.tsx | 34 +++++++++++++++++++ source/Localization/EN-US.ts | 1 + source/Localization/ZH-CN.ts | 1 + 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 source/Component/SettingPopup/SettingPopup.scss create mode 100644 source/Component/SettingPopup/SettingPopup.tsx diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index e6d38be..6deec55 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -4,7 +4,7 @@ import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { AllI18nKeys } from "../Localization/Localization"; -import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; +import { SettingPopup } from "@Component/SettingPopup/SettingPopup"; import { Component, ReactNode } from "react"; import { MouseMod } from "@GLRender/ClassicRenderer"; import "./CommandBar.scss"; @@ -73,7 +73,7 @@ class CommandBar extends Component { - // this.props.status?.popup.showPopup(ConfirmPopup, {}); + this.props.status?.popup.showPopup(SettingPopup, {}); } })} diff --git a/source/Component/SettingPopup/SettingPopup.scss b/source/Component/SettingPopup/SettingPopup.scss new file mode 100644 index 0000000..f5165a4 --- /dev/null +++ b/source/Component/SettingPopup/SettingPopup.scss @@ -0,0 +1,6 @@ +@import "../Theme/Theme.scss"; + +div.setting-popup { + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/source/Component/SettingPopup/SettingPopup.tsx b/source/Component/SettingPopup/SettingPopup.tsx new file mode 100644 index 0000000..0730082 --- /dev/null +++ b/source/Component/SettingPopup/SettingPopup.tsx @@ -0,0 +1,34 @@ +import { Component, ReactNode } from "react"; +import { Popup } from "@Context/Popups"; +import { Theme } from "@Component/Theme/Theme"; +import { Localization } from "@Component/Localization/Localization"; +import "./SettingPopup.scss"; + +interface ISettingPopupProps { + +} + +class SettingPopup extends Popup { + + public minWidth: number = 400; + public minHeight: number = 300; + public width: number = 600; + public height: number = 450; + + public onRenderHeader(): ReactNode { + return + } + + public render(): ReactNode { + return + } +} + +class SettingPopupComponent extends Component { + + public render(): ReactNode { + return + } +} + +export { SettingPopup }; \ No newline at end of file diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 8fbaa52..aaaeb7d 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -51,6 +51,7 @@ const EN_US = { "Popup.Action.Objects.Confirm.Title": "Confirm Delete", "Popup.Action.Objects.Confirm.Delete": "Delete", "Popup.Delete.Objects.Confirm": "Are you sure you want to delete this object(s)? The object is deleted and cannot be recalled.", + "Popup.Setting.Title": "Preferences setting", "Build.In.Label.Name.All.Group": "All group", "Build.In.Label.Name.All.Range": "All range", "Common.No.Data": "No Data", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 3e2a883..7531fc9 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -51,6 +51,7 @@ const ZH_CN = { "Popup.Action.Objects.Confirm.Title": "删除确认", "Popup.Action.Objects.Confirm.Delete": "删除", "Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。", + "Popup.Setting.Title": "首选项设置", "Build.In.Label.Name.All.Group": "全部群", "Build.In.Label.Name.All.Range": "全部范围", "Common.No.Data": "暂无数据", From 156b4651f590e4386ac549f4ab548b6190b4cd60 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Fri, 25 Mar 2022 15:40:15 +0800 Subject: [PATCH 2/8] Add behavior model --- source/Context/Status.tsx | 8 +- source/Model/Behavior.ts | 310 +++++++++++++++++++++++++++++++++++-- source/Model/CtrlObject.ts | 28 +++- source/Model/Model.ts | 74 +++++++-- 4 files changed, 384 insertions(+), 36 deletions(-) diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index b74e557..16f111f 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -83,13 +83,13 @@ class Status extends Emitter { */ public focusLabel?: Label; - private drawtimer?: NodeJS.Timeout; + private drawTimer?: NodeJS.Timeout; private delayDraw = () => { - this.drawtimer ? clearTimeout(this.drawtimer) : null; - this.drawtimer = setTimeout(() => { + this.drawTimer ? clearTimeout(this.drawTimer) : null; + this.drawTimer = setTimeout(() => { this.model.draw(); - this.drawtimer = undefined; + this.drawTimer = undefined; }); } diff --git a/source/Model/Behavior.ts b/source/Model/Behavior.ts index d86de8c..2258936 100644 --- a/source/Model/Behavior.ts +++ b/source/Model/Behavior.ts @@ -3,40 +3,328 @@ import { Emitter, EventType } from "./Emitter"; import type { Individual } from "./Individual"; import type { Group } from "./Group"; import type { Model } from "./Model"; +import type { Range } from "./Range"; +import type { Label } from "./Label"; /** - * 群体的某种行为 + * 行为构造函数类型 */ -abstract class Behavior< - P extends IAnyObject = {}, - E extends Record = {} -> extends Emitter { +type IBehaviorConstructor> = + new (id: string, parameter: IBehaviorParameterValue) => B; + +/** + * 参数类型 + */ +type IMapBasicParamTypeKeyToType = { + "number": number; + "string": string; + "boolean": boolean; +} + +type IMapObjectParamTypeKeyToType = { + "R"?: Range; + "G"?: Group; + "GR"?: Group | Range; + "LR"?: Label | Range; + "LG"?: Label | Group; + "LGR"?: Label | Group | Range; +} + +type IMapVectorParamTypeKeyToType = { + "vec": number[]; +} + +/** + * 参数类型映射 + */ +type AllMapType = IMapBasicParamTypeKeyToType & IMapObjectParamTypeKeyToType & IMapVectorParamTypeKeyToType; +type IParamType = keyof AllMapType; +type IObjectType = keyof IMapObjectParamTypeKeyToType; +type IVectorType = keyof IMapVectorParamTypeKeyToType; +type IParamValue = AllMapType[K]; + + + +/** + * 特殊对象类型判定 + */ +const objectTypeListEnumSet = new Set(["R", "G", "GR", "LR", "LG", "LGR"]); + +/** + * 对象断言表达式 + */ +function isObjectType(key: IParamType): key is IVectorType { + return objectTypeListEnumSet.has(key); +} + +/** + * 向量断言表达式 + */ +function isVectorType(key: IParamType): key is IObjectType { + return key === "vec"; +} + +/** + * 模型参数类型 + */ +interface IBehaviorParameterOptionItem { + + /** + * 参数类型 + */ + type: T; + + /** + * 参数默认值 + */ + defaultValue?: IParamValue; + + /** + * 数值变化回调 + */ + onChange?: (value: IParamValue) => any; + + /** + * 名字 + */ + name: string; + + /** + * 字符长度 + */ + stringLength?: number; + + /** + * 数字步长 + */ + numberStep?: number; + + /** + * 最大值最小值 + */ + numberMax?: number; + numberMin?: number; + + /** + * 图标名字 + */ + iconName?: string; +} + +/** + * 参数键值类型 + */ +type IBehaviorParameterValueItem

= IParamValue; + +/** + * 参数类型列表 + */ +interface IBehaviorParameterOption { + [x: string]: IBehaviorParameterOptionItem; +} + +/** + * 参数类型列表映射到参数对象 + */ +type IBehaviorParameterValue

= { + [x in keyof P]: IBehaviorParameterValueItem +} + + +/** + * 行为的基础信息 + */ +class BehaviorInfo = {}> extends Emitter { + + /** + * 图标名字 + */ + public iconName: string = "" /** * 行为 ID */ - abstract id: string; - + public behaviorId: string = ""; + /** * 行为名称 */ - abstract name: string; + public behaviorName: string = ""; /** * 行为描述 */ public describe?: string = ""; +} + +class BehaviorRecorder< + B extends Behavior +> extends BehaviorInfo { + + /** + * 命名序号 + */ + public nameIndex: number = 0; + + /** + * 获取下一个 ID + */ + public getNextId() { + return `B-${this.behaviorName}-${this.nameIndex ++}`; + } + + /** + * 行为类型 + */ + public behavior: IBehaviorConstructor; + + /** + * 行为实例 + */ + public behaviorInstance: B; + + /** + * 对象参数列表 + */ + public parameterOption: B["parameterOption"]; + + /** + * 获取参数列表的默认值 + */ + public getDefaultValue(): IBehaviorParameterValue { + let defaultObj = {} as IBehaviorParameterValue; + for (let key in this.parameterOption) { + let defaultVal = this.parameterOption[key].defaultValue; + + defaultObj[key] = defaultVal as any; + if (defaultObj[key] === undefined) { + + switch (this.parameterOption[key].type) { + case "string": + defaultObj[key] = "" as any; + break; + + case "number": + defaultObj[key] = 0 as any; + break; + + case "boolean": + defaultObj[key] = false as any; + break; + + case "vec": + defaultObj[key] = [0, 0, 0] as any; + break; + } + } + } + return defaultObj; + } + + /** + * 创建一个新的行为实例 + */ + public new(): B { + return new this.behavior(this.getNextId(), this.getDefaultValue()); + } + + public constructor(behavior: IBehaviorConstructor) { + super(); + this.behavior = behavior; + this.behaviorInstance = new this.behavior(this.getNextId(), {} as any); + this.parameterOption = this.behaviorInstance.parameterOption; + this.iconName = this.behaviorInstance.iconName; + this.behaviorId = this.behaviorInstance.behaviorId; + this.behaviorName = this.behaviorInstance.behaviorName; + this.describe = this.behaviorInstance.describe; + } +} + +/** + * 群体的某种行为 + */ +class Behavior< + P extends IBehaviorParameterOption = {}, + E extends Record = {} +> extends BehaviorInfo { + + /** + * 用户自定义名字 + */ + public name: string = ""; + + /** + * 实例 ID + */ + public id: string = ""; /** * 优先级 * 值越大执行顺序越靠后 */ - public priority?: number = 0; + public priority: number = 0; /** * 行为参数 */ - abstract parameter?: P; + public parameter: IBehaviorParameterValue

; + + /** + * 对象参数列表 + */ + public parameterOption: P = {} as any; + + public constructor(id: string, parameter: IBehaviorParameterValue

) { + super(); + this.id = id; + this.parameter = parameter; + } + + /** + * 相等校验 + */ + public equal(behavior: Behavior): boolean { + return this === behavior || this.id === behavior.id; + }; + + /** + * 删除标记 + */ + private deleteFlag: boolean = false; + + /** + * 标记对象被删除 + */ + public markDelete() { + this.deleteFlag = true; + }; + + /** + * 是否被删除 + */ + public isDeleted(): boolean { + return this.deleteFlag; + } + + /** + * 加载时调用 + */ + public load(model: Model): void {} + + /** + * 卸载时调用 + */ + public unload(model: Model): void {} + + /** + * 挂载时调用 + */ + public mount(group: Group, model: Model): void {} + + /** + * 挂载时调用 + */ + public unmount(group: Group, model: Model): void {} /** * 全部影响作用前 @@ -67,5 +355,5 @@ abstract class Behavior< } -export { Behavior }; +export { Behavior, BehaviorRecorder }; export default { Behavior }; \ No newline at end of file diff --git a/source/Model/CtrlObject.ts b/source/Model/CtrlObject.ts index 982d20d..a6e810e 100644 --- a/source/Model/CtrlObject.ts +++ b/source/Model/CtrlObject.ts @@ -60,22 +60,36 @@ class CtrlObject extends LabelObject { return this === obj || this.id === obj.id; } + /** + * 标记对象被删除 + */ + public markDelete() { + this.deleteFlag = true; + }; /** * 删除标记 */ private deleteFlag: boolean = false; - /** - * 是否被删除 - */ - public isDeleted(): boolean { - if (this.deleteFlag) return true; + /** + * 检测是否被删除 + */ + public testDelete() { for (let i = 0; i < this.model.objectPool.length; i++) { - if (this.model.objectPool[i].equal(this)) return false; + if (this.model.objectPool[i].equal(this)) { + this.deleteFlag = false; + return; + } } this.deleteFlag = true; - return true; + } + + /** + * 是否被删除 + */ + public isDeleted(): boolean { + return this.deleteFlag; } } diff --git a/source/Model/Model.ts b/source/Model/Model.ts index 9e537db..6f445af 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -1,4 +1,3 @@ - import { Individual } from "./Individual"; import { Group } from "./Group"; import { Range } from "./Range"; @@ -6,18 +5,14 @@ import { Emitter, EventType, EventMixin } from "./Emitter"; import { CtrlObject } from "./CtrlObject"; import { ObjectID, AbstractRenderer } from "./Renderer"; import { Label } from "./Label"; +import { Behavior, BehaviorRecorder } from "./Behavior"; type ModelEvent = { loop: number; - groupAdd: Group; - rangeAdd: Range; - labelAdd: Label; - labelDelete: Label; labelChange: Label[]; - objectAdd: CtrlObject; - objectDelete: CtrlObject[]; objectChange: CtrlObject[]; individualChange: Group; + behaviorChange: Behavior; }; /** @@ -68,7 +63,6 @@ class Model extends Emitter { console.log(`Model: Creat label with id ${this.idIndex}`); let label = new Label(this, this.nextId("L"), name); this.labelPool.push(label); - this.emit("labelAdd", label); this.emit("labelChange", this.labelPool); return label; } @@ -97,7 +91,6 @@ class Model extends Emitter { this.labelPool.splice(index, 1); deletedLabel.testDelete(); console.log(`Model: Delete label ${deletedLabel.name ?? deletedLabel.id}`); - this.emit("labelDelete", deletedLabel); this.emit("labelChange", this.labelPool); } } @@ -135,8 +128,6 @@ class Model extends Emitter { console.log(`Model: Creat group with id ${this.idIndex}`); let group = new Group(this, this.nextId("G")); this.objectPool.push(group); - this.emit("groupAdd", group); - this.emit("objectAdd", group); this.emit("objectChange", this.objectPool); return group; } @@ -148,8 +139,6 @@ class Model extends Emitter { console.log(`Model: Creat range with id ${this.idIndex}`); let range = new Range(this, this.nextId("R")); this.objectPool.push(range); - this.emit("rangeAdd", range); - this.emit("objectAdd", range); this.emit("objectChange", this.objectPool); return range; } @@ -176,6 +165,7 @@ class Model extends Emitter { if (needDeleted) { deletedObject.push(currentObject); + currentObject.markDelete(); return false; } else { return true; @@ -184,12 +174,68 @@ class Model extends Emitter { if (deletedObject.length) { console.log(`Model: Delete object ${deletedObject.map((object) => object.id).join(", ")}`); - this.emit("objectDelete", deletedObject); this.emit("objectChange", this.objectPool); } return deletedObject; } + /** + * 行为池 + */ + public behaviorPool: Behavior[] = []; + + /** + * 添加一个行为 + */ + public addBehavior>(recorder: BehaviorRecorder): B { + let behavior = recorder.new(); + behavior.load(this); + this.behaviorPool.push(behavior); + console.log(`Model: Add ${behavior.behaviorName} behavior ${behavior.id}`); + this.emit("behaviorChange", behavior); + return behavior; + }; + + /** + * 通过 ID 获取行为 + */ + public getBehaviorById(id: ObjectID): Behavior | undefined { + for (let i = 0; i < this.behaviorPool.length; i++) { + if (this.behaviorPool[i].id.toString() === id.toString()) { + return this.behaviorPool[i]; + } + } + } + + /** + * 搜索并删除一个 Behavior + * @param name 搜索值 + */ + public deleteBehavior(name: Behavior | ObjectID) { + let deletedBehavior: Behavior | undefined; + let index = 0; + + for (let i = 0; i < this.behaviorPool.length; i++) { + if (name instanceof Behavior) { + if (this.behaviorPool[i].equal(name)) { + deletedBehavior = this.behaviorPool[i]; + index = i; + } + } else if (name === this.behaviorPool[i].id) { + deletedBehavior = this.behaviorPool[i]; + index = i; + } + } + + if (deletedBehavior) { + this.behaviorPool.splice(index, 1); + deletedBehavior.unload(this); + deletedBehavior.markDelete(); + console.log(`Model: Delete behavior ${deletedBehavior.name ?? deletedBehavior.id}`); + this.emit("behaviorChange", deletedBehavior); + } + } + /** * 渲染器 */ From feb277c3a295fb1d51478d6586e5ceb495d07b4e Mon Sep 17 00:00:00 2001 From: MrKBear Date: Fri, 25 Mar 2022 15:43:31 +0800 Subject: [PATCH 3/8] Set group default genmod as range mod --- source/Model/Group.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Model/Group.ts b/source/Model/Group.ts index b536ae5..5e81e0f 100644 --- a/source/Model/Group.ts +++ b/source/Model/Group.ts @@ -22,7 +22,7 @@ class Group extends CtrlObject { /** * 个体生成方式 */ - public genMethod: GenMod = GenMod.Point; + public genMethod: GenMod = GenMod.Range; /** * 生成位置坐标 From 850eea254ca8b57063d4d42487be654f13817a6f Mon Sep 17 00:00:00 2001 From: MrKBear Date: Fri, 25 Mar 2022 17:06:39 +0800 Subject: [PATCH 4/8] Optimizing behavioral models --- source/Model/Behavior.ts | 61 ++++++++++++++++++++++------------------ source/Model/Model.ts | 14 ++++----- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/source/Model/Behavior.ts b/source/Model/Behavior.ts index 2258936..47e5179 100644 --- a/source/Model/Behavior.ts +++ b/source/Model/Behavior.ts @@ -6,12 +6,6 @@ import type { Model } from "./Model"; import type { Range } from "./Range"; import type { Label } from "./Label"; -/** - * 行为构造函数类型 - */ -type IBehaviorConstructor> = - new (id: string, parameter: IBehaviorParameterValue) => B; - /** * 参数类型 */ @@ -43,8 +37,6 @@ type IObjectType = keyof IMapObjectParamTypeKeyToType; type IVectorType = keyof IMapVectorParamTypeKeyToType; type IParamValue = AllMapType[K]; - - /** * 特殊对象类型判定 */ @@ -111,25 +103,34 @@ interface IBehaviorParameterOptionItem { iconName?: string; } -/** - * 参数键值类型 - */ -type IBehaviorParameterValueItem

= IParamValue; +interface IBehaviorParameter { + [x: string]: IParamType; +} /** * 参数类型列表 */ -interface IBehaviorParameterOption { - [x: string]: IBehaviorParameterOptionItem; +type IBehaviorParameterOption

= { + [X in keyof P]: IBehaviorParameterOptionItem; } /** * 参数类型列表映射到参数对象 */ -type IBehaviorParameterValue

= { - [x in keyof P]: IBehaviorParameterValueItem +type IBehaviorParameterValue

= { + [X in keyof P]: IParamValue } +/** + * 行为构造函数类型 + */ +type IBehaviorConstructor< + P extends IBehaviorParameter = {}, + E extends Record = {} +> = new (id: string, parameter: IBehaviorParameterValue

) => Behavior; + +type IAnyBehavior = Behavior; +type IAnyBehaviorRecorder = BehaviorRecorder; /** * 行为的基础信息 @@ -158,8 +159,9 @@ class BehaviorInfo = {}> extends Emitter { } class BehaviorRecorder< - B extends Behavior -> extends BehaviorInfo { + P extends IBehaviorParameter = {}, + E extends Record = {} +> extends BehaviorInfo<{}> { /** * 命名序号 @@ -176,23 +178,23 @@ class BehaviorRecorder< /** * 行为类型 */ - public behavior: IBehaviorConstructor; + public behavior: IBehaviorConstructor; /** * 行为实例 */ - public behaviorInstance: B; + public behaviorInstance: Behavior; /** * 对象参数列表 */ - public parameterOption: B["parameterOption"]; + public parameterOption: IBehaviorParameterOption

; /** * 获取参数列表的默认值 */ - public getDefaultValue(): IBehaviorParameterValue { - let defaultObj = {} as IBehaviorParameterValue; + public getDefaultValue(): IBehaviorParameterValue

{ + let defaultObj = {} as IBehaviorParameterValue

; for (let key in this.parameterOption) { let defaultVal = this.parameterOption[key].defaultValue; @@ -224,11 +226,11 @@ class BehaviorRecorder< /** * 创建一个新的行为实例 */ - public new(): B { + public new(): Behavior { return new this.behavior(this.getNextId(), this.getDefaultValue()); } - public constructor(behavior: IBehaviorConstructor) { + public constructor(behavior: IBehaviorConstructor) { super(); this.behavior = behavior; this.behaviorInstance = new this.behavior(this.getNextId(), {} as any); @@ -244,7 +246,7 @@ class BehaviorRecorder< * 群体的某种行为 */ class Behavior< - P extends IBehaviorParameterOption = {}, + P extends IBehaviorParameter = {}, E extends Record = {} > extends BehaviorInfo { @@ -272,7 +274,7 @@ class Behavior< /** * 对象参数列表 */ - public parameterOption: P = {} as any; + public parameterOption: IBehaviorParameterOption

= {} as any; public constructor(id: string, parameter: IBehaviorParameterValue

) { super(); @@ -355,5 +357,8 @@ class Behavior< } -export { Behavior, BehaviorRecorder }; +export { + Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem, + IAnyBehavior, IAnyBehaviorRecorder +}; export default { Behavior }; \ No newline at end of file diff --git a/source/Model/Model.ts b/source/Model/Model.ts index 6f445af..a28f07b 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -5,14 +5,14 @@ import { Emitter, EventType, EventMixin } from "./Emitter"; import { CtrlObject } from "./CtrlObject"; import { ObjectID, AbstractRenderer } from "./Renderer"; import { Label } from "./Label"; -import { Behavior, BehaviorRecorder } from "./Behavior"; +import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior"; type ModelEvent = { loop: number; labelChange: Label[]; objectChange: CtrlObject[]; individualChange: Group; - behaviorChange: Behavior; + behaviorChange: IAnyBehavior; }; /** @@ -182,12 +182,12 @@ class Model extends Emitter { /** * 行为池 */ - public behaviorPool: Behavior[] = []; + public behaviorPool: IAnyBehavior[] = []; /** * 添加一个行为 */ - public addBehavior>(recorder: BehaviorRecorder): B { + public addBehavior(recorder: B): B["behaviorInstance"] { let behavior = recorder.new(); behavior.load(this); this.behaviorPool.push(behavior); @@ -199,7 +199,7 @@ class Model extends Emitter { /** * 通过 ID 获取行为 */ - public getBehaviorById(id: ObjectID): Behavior | undefined { + public getBehaviorById(id: ObjectID): IAnyBehavior | undefined { for (let i = 0; i < this.behaviorPool.length; i++) { if (this.behaviorPool[i].id.toString() === id.toString()) { return this.behaviorPool[i]; @@ -211,8 +211,8 @@ class Model extends Emitter { * 搜索并删除一个 Behavior * @param name 搜索值 */ - public deleteBehavior(name: Behavior | ObjectID) { - let deletedBehavior: Behavior | undefined; + public deleteBehavior(name: IAnyBehavior | ObjectID) { + let deletedBehavior: IAnyBehavior | undefined; let index = 0; for (let i = 0; i < this.behaviorPool.length; i++) { From 43ab6cb03f6b0c928aa443ac42292c91455e1286 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Fri, 25 Mar 2022 17:18:39 +0800 Subject: [PATCH 5/8] Add template behavior --- source/Behavior/Behavior.ts | 8 ++++++++ source/Behavior/Template.ts | 20 ++++++++++++++++++++ source/Localization/EN-US.ts | 2 ++ source/Localization/ZH-CN.ts | 2 ++ 4 files changed, 32 insertions(+) create mode 100644 source/Behavior/Behavior.ts create mode 100644 source/Behavior/Template.ts diff --git a/source/Behavior/Behavior.ts b/source/Behavior/Behavior.ts new file mode 100644 index 0000000..847bee2 --- /dev/null +++ b/source/Behavior/Behavior.ts @@ -0,0 +1,8 @@ +import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; +import { Template } from "./Template"; + +const AllBehaviors: IAnyBehaviorRecorder[] = [ + new BehaviorRecorder(Template) +] + +export { AllBehaviors }; \ No newline at end of file diff --git a/source/Behavior/Template.ts b/source/Behavior/Template.ts new file mode 100644 index 0000000..ff3a17a --- /dev/null +++ b/source/Behavior/Template.ts @@ -0,0 +1,20 @@ +import { Behavior } from "@Model/Behavior"; + +type ITemplateBehaviorParameter = { + +} + +type ITemplateBehaviorEvent = {} + +class Template extends Behavior { + + public override behaviorId: string = "Template"; + + public override behaviorName: string = "Behavior.Template.Title"; + + public override iconName: string = "Running"; + + public override describe: string = "Behavior.Template.Intro"; +} + +export { Template }; \ No newline at end of file diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index aaaeb7d..a367ddf 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -54,6 +54,8 @@ const EN_US = { "Popup.Setting.Title": "Preferences setting", "Build.In.Label.Name.All.Group": "All group", "Build.In.Label.Name.All.Range": "All range", + "Behavior.Template.Title": "Behavior", + "Behavior.Template.Intro": "This is a template behavior", "Common.No.Data": "No Data", "Common.No.Unknown.Error": "Unknown error", "Common.Attr.Title.Basic": "Basic properties", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 7531fc9..71359a8 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -54,6 +54,8 @@ const ZH_CN = { "Popup.Setting.Title": "首选项设置", "Build.In.Label.Name.All.Group": "全部群", "Build.In.Label.Name.All.Range": "全部范围", + "Behavior.Template.Title": "行为", + "Behavior.Template.Intro": "这是一个模板行为", "Common.No.Data": "暂无数据", "Common.No.Unknown.Error": "未知错误", "Common.Attr.Title.Basic": "基础属性", From ffdb796e8f2017be0228b4842c6d40b7f216d303 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Fri, 25 Mar 2022 17:23:10 +0800 Subject: [PATCH 6/8] Optimization icon --- source/Component/CommandBar/CommandBar.tsx | 4 ++-- source/Panel/ObjectList/ObjectCommand.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index 6deec55..0bf2433 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -52,13 +52,13 @@ class CommandBar extends Component { this.props.status ? this.props.status.newRange() : undefined; } })} - {this.getRenderButton({ iconName: "StepSharedAdd", i18NKey: "Command.Bar.Add.Behavior.Info" })} + {this.getRenderButton({ iconName: "Running", i18NKey: "Command.Bar.Add.Behavior.Info" })} {this.getRenderButton({ iconName: "Tag", i18NKey: "Command.Bar.Add.Tag.Info", diff --git a/source/Panel/ObjectList/ObjectCommand.tsx b/source/Panel/ObjectList/ObjectCommand.tsx index 0450399..e33ee70 100644 --- a/source/Panel/ObjectList/ObjectCommand.tsx +++ b/source/Panel/ObjectList/ObjectCommand.tsx @@ -52,7 +52,7 @@ class ObjectCommand extends Component { this.props.status ? this.props.status.newRange() : undefined; }} > - +

Date: Sat, 26 Mar 2022 13:39:36 +0800 Subject: [PATCH 7/8] Add behavior pop up --- .../BehaviorPopup/BehaviorPopup.scss | 6 ++++ .../Component/BehaviorPopup/BehaviorPopup.tsx | 34 +++++++++++++++++++ source/Component/CommandBar/CommandBar.tsx | 11 ++++-- source/Localization/EN-US.ts | 1 + source/Localization/ZH-CN.ts | 1 + 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 source/Component/BehaviorPopup/BehaviorPopup.scss create mode 100644 source/Component/BehaviorPopup/BehaviorPopup.tsx diff --git a/source/Component/BehaviorPopup/BehaviorPopup.scss b/source/Component/BehaviorPopup/BehaviorPopup.scss new file mode 100644 index 0000000..aaea67b --- /dev/null +++ b/source/Component/BehaviorPopup/BehaviorPopup.scss @@ -0,0 +1,6 @@ +@import "../Theme/Theme.scss"; + +div.behavior-popup { + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/source/Component/BehaviorPopup/BehaviorPopup.tsx b/source/Component/BehaviorPopup/BehaviorPopup.tsx new file mode 100644 index 0000000..f3a8761 --- /dev/null +++ b/source/Component/BehaviorPopup/BehaviorPopup.tsx @@ -0,0 +1,34 @@ +import { Component, ReactNode } from "react"; +import { Popup } from "@Context/Popups"; +import { Theme } from "@Component/Theme/Theme"; +import { Localization } from "@Component/Localization/Localization"; +import "./BehaviorPopup.scss"; + +interface IBehaviorPopupProps { + +} + +class BehaviorPopup extends Popup { + + public minWidth: number = 400; + public minHeight: number = 300; + public width: number = 600; + public height: number = 450; + + public onRenderHeader(): ReactNode { + return + } + + public render(): ReactNode { + return + } +} + +class BehaviorPopupComponent extends Component { + + public render(): ReactNode { + return + } +} + +export { BehaviorPopup }; \ No newline at end of file diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index 0bf2433..4ae4121 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -4,7 +4,8 @@ import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { AllI18nKeys } from "../Localization/Localization"; -import { SettingPopup } from "@Component/SettingPopup/SettingPopup"; +import { SettingPopup } from "../SettingPopup/SettingPopup"; +import { BehaviorPopup } from "../BehaviorPopup/BehaviorPopup"; import { Component, ReactNode } from "react"; import { MouseMod } from "@GLRender/ClassicRenderer"; import "./CommandBar.scss"; @@ -58,7 +59,13 @@ class CommandBar extends Component { + this.props.status?.popup.showPopup(BehaviorPopup, {}); + } + })} {this.getRenderButton({ iconName: "Tag", i18NKey: "Command.Bar.Add.Tag.Info", diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index a367ddf..23c8276 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -52,6 +52,7 @@ const EN_US = { "Popup.Action.Objects.Confirm.Delete": "Delete", "Popup.Delete.Objects.Confirm": "Are you sure you want to delete this object(s)? The object is deleted and cannot be recalled.", "Popup.Setting.Title": "Preferences setting", + "Popup.Add.Behavior.Title": "Add behavior", "Build.In.Label.Name.All.Group": "All group", "Build.In.Label.Name.All.Range": "All range", "Behavior.Template.Title": "Behavior", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 71359a8..60b890e 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -52,6 +52,7 @@ const ZH_CN = { "Popup.Action.Objects.Confirm.Delete": "删除", "Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。", "Popup.Setting.Title": "首选项设置", + "Popup.Add.Behavior.Title": "添加行为", "Build.In.Label.Name.All.Group": "全部群", "Build.In.Label.Name.All.Range": "全部范围", "Behavior.Template.Title": "行为", From 87023251a9095dc8f605f53346bd92cb91cbe4ec Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sat, 26 Mar 2022 18:54:51 +0800 Subject: [PATCH 8/8] Add search box component --- .../BehaviorPopup/BehaviorPopup.scss | 5 + .../Component/BehaviorPopup/BehaviorPopup.tsx | 37 ++++- .../Component/ConfirmPopup/ConfirmPopup.scss | 48 +++++- .../Component/ConfirmPopup/ConfirmPopup.tsx | 150 +++++++++++++++--- source/Component/SearchBox/SearchBox.scss | 95 +++++++++++ source/Component/SearchBox/SearchBox.tsx | 60 +++++++ source/Localization/EN-US.ts | 2 + source/Localization/ZH-CN.ts | 2 + 8 files changed, 369 insertions(+), 30 deletions(-) create mode 100644 source/Component/SearchBox/SearchBox.scss create mode 100644 source/Component/SearchBox/SearchBox.tsx diff --git a/source/Component/BehaviorPopup/BehaviorPopup.scss b/source/Component/BehaviorPopup/BehaviorPopup.scss index aaea67b..290e2cf 100644 --- a/source/Component/BehaviorPopup/BehaviorPopup.scss +++ b/source/Component/BehaviorPopup/BehaviorPopup.scss @@ -3,4 +3,9 @@ div.behavior-popup { width: 100%; height: 100%; +} + +div.behavior-popup-search-box { + padding: 10px 0 0 10px; + width: calc(100% - 10px); } \ No newline at end of file diff --git a/source/Component/BehaviorPopup/BehaviorPopup.tsx b/source/Component/BehaviorPopup/BehaviorPopup.tsx index f3a8761..53c062f 100644 --- a/source/Component/BehaviorPopup/BehaviorPopup.tsx +++ b/source/Component/BehaviorPopup/BehaviorPopup.tsx @@ -1,13 +1,18 @@ import { Component, ReactNode } from "react"; import { Popup } from "@Context/Popups"; -import { Theme } from "@Component/Theme/Theme"; import { Localization } from "@Component/Localization/Localization"; +import { SearchBox } from "@Component/SearchBox/SearchBox"; +import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup"; import "./BehaviorPopup.scss"; interface IBehaviorPopupProps { } +interface IBehaviorPopupState { + searchValue: string; +} + class BehaviorPopup extends Popup { public minWidth: number = 400; @@ -24,10 +29,36 @@ class BehaviorPopup extends Popup { } } -class BehaviorPopupComponent extends Component { +class BehaviorPopupComponent extends Component { + + state: Readonly = { + searchValue: "" + }; + + private renderHeader = () => { + return
+ { + this.setState({ + searchValue: value + }); + }} + value={this.state.searchValue} + /> +
; + } public render(): ReactNode { - return + return + + } } diff --git a/source/Component/ConfirmPopup/ConfirmPopup.scss b/source/Component/ConfirmPopup/ConfirmPopup.scss index 76e46ae..76c46d6 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.scss +++ b/source/Component/ConfirmPopup/ConfirmPopup.scss @@ -4,10 +4,35 @@ div.confirm-root { width: 100%; height: 100%; + div.header-view { + width: 100%; + } + div.content-views { width: 100%; - height: calc( 100% - 36px ); box-sizing: border-box; + overflow: scroll; + -ms-overflow-style: none; + flex-shrink: 1; + } + + div.content-views::-webkit-scrollbar { + width : 8px; /*高宽分别对应横竖滚动条的尺寸*/ + height: 0; + } + + div.content-views::-webkit-scrollbar-thumb { + /*滚动条里面小方块*/ + border-radius: 8px; + } + + div.content-views::-webkit-scrollbar-track { + /*滚动条里面轨道*/ + border-radius: 8px; + background-color: rgba($color: #000000, $alpha: 0); + } + + div.content-views.has-padding { padding: 10px; } @@ -35,14 +60,27 @@ div.confirm-root { div.action-button.red { color: $lt-red; } + + div.action-button.blue { + color: $lt-blue; + } + + div.action-button.disable { + opacity: .75; + cursor: not-allowed; + } } } div.dark.confirm-root { + div.content-views::-webkit-scrollbar-thumb { + background-color: $lt-bg-color-lvl1-dark; + } + div.action-view { - div.action-button { + div.action-button, div.action-button.disable:hover { background-color: $lt-bg-color-lvl3-dark; } @@ -54,9 +92,13 @@ div.dark.confirm-root { div.light.confirm-root { + div.content-views::-webkit-scrollbar-thumb { + background-color: $lt-bg-color-lvl1-light; + } + div.action-view { - div.action-button { + div.action-button, div.action-button.disable:hover { background-color: $lt-bg-color-lvl3-light; } diff --git a/source/Component/ConfirmPopup/ConfirmPopup.tsx b/source/Component/ConfirmPopup/ConfirmPopup.tsx index b530ea8..a3c7aba 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.tsx +++ b/source/Component/ConfirmPopup/ConfirmPopup.tsx @@ -1,5 +1,5 @@ import { Popup } from "@Context/Popups"; -import { ReactNode } from "react"; +import { Component, ReactNode } from "react"; import { Message } from "@Component/Message/Message"; import { Theme } from "@Component/Theme/Theme"; import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; @@ -7,12 +7,12 @@ import "./ConfirmPopup.scss"; interface IConfirmPopupProps { titleI18N?: AllI18nKeys; - infoI18n: AllI18nKeys; + infoI18n?: AllI18nKeys; yesI18n?: AllI18nKeys; noI18n?: AllI18nKeys; + red?: "yes" | "no"; yes?: () => any; no?: () => any; - red?: "yes" | "no"; } class ConfirmPopup extends Popup { @@ -24,37 +24,139 @@ class ConfirmPopup extends Popup { return } + private genActionClickFunction(fn?: () => any): () => any { + return () => { + if (fn) fn(); + this.close(); + }; + } + public render(): ReactNode { - const yesClassList: string[] = ["action-button", "yes-button"]; - const noClassList: string[] = ["action-button", "no-button"]; - if (this.props.red === "no") { - noClassList.push("red"); + const actionList: IActionButtonProps[] = []; + + if (this.props.yesI18n || this.props.yes) { + actionList.push({ + i18nKey: this.props.yesI18n ?? "Popup.Action.Yes", + onClick: this.genActionClickFunction(this.props.yes), + color: this.props.red === "yes" ? "red" : undefined + }); } - if (this.props.red === "yes") { - yesClassList.push("red"); + + if (this.props.noI18n || this.props.no) { + actionList.push({ + i18nKey: this.props.noI18n ?? "Popup.Action.Yes", + onClick: this.genActionClickFunction(this.props.no), + color: this.props.red === "no" ? "red" : undefined + }); + } + + return + {this.props.infoI18n ? : null} + + } +} + +interface IConfirmContentProps { + hidePadding?: boolean; + className?: string; + actions: IActionButtonProps[]; + header?: () => ReactNode; + headerHeight?: number; +} + +interface IActionButtonProps { + className?: string; + disable?: boolean; + color?: "red" | "blue"; + i18nKey: AllI18nKeys; + i18nOption?: Record; + onClick?: () => void; +} + +class ConfirmContent extends Component { + + public renderActionButton(props: IActionButtonProps, key: number): ReactNode { + + const classList = ["action-button"]; + if (props.className) { + classList.push(props.className); + } + + if (props.color === "red") { + classList.push("red"); + } + + if (props.color === "blue") { + classList.push("blue"); + } + + if (props.disable) { + classList.push("disable"); + } + + return
+ +
+ } + + private getHeaderHeight(): number { + return this.props.headerHeight ?? 0; + } + + private renderHeader() { + return
+ {this.props.header ? this.props.header() : null} +
+ } + + public render(): ReactNode { + + const contentClassNameList: string[] = ["content-views"]; + + if (this.props.className) { + contentClassNameList.push(this.props.className); + } + + if (!this.props.hidePadding) { + contentClassNameList.push("has-padding"); } return -
- + + {this.props.header ? this.renderHeader() : null} + +
+ {this.props.children}
+
-
{ - this.props.yes ? this.props.yes() : null; - this.close(); - }}> - -
-
{ - this.props.no ? this.props.no() : null; - this.close(); - }}> - -
+ { + this.props.actions.map((prop, index) => { + return this.renderActionButton(prop, index); + }) + }
; } } -export { ConfirmPopup } \ No newline at end of file +export { ConfirmPopup, ConfirmContent } \ No newline at end of file diff --git a/source/Component/SearchBox/SearchBox.scss b/source/Component/SearchBox/SearchBox.scss new file mode 100644 index 0000000..a7636fd --- /dev/null +++ b/source/Component/SearchBox/SearchBox.scss @@ -0,0 +1,95 @@ +@import "../Theme/Theme.scss"; + +$search-box-height: 26px; + +div.search-box-root { + min-height: $search-box-height; + max-width: 280px; + width: 100%; + border-radius: 3px; + display: flex; + cursor: pointer; + overflow: hidden; + + div.search-icon { + min-width: $search-box-height; + height: $search-box-height; + flex-shrink: 0; + display: flex; + justify-content: center; + align-items: center; + user-select: none; + } + + div.input-box { + width: calc(100% - 26px); + height: $search-box-height; + + input { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + border: 0; + outline: none; + background-color: transparent; + vertical-align: middle; + } + } + + div.clean-box { + height: $search-box-height; + width: 0; + display: flex; + align-items: center; + + div.clean-box-view { + flex-shrink: 0; + height: 24px; + width: 24px; + display: flex; + justify-content: center; + align-items: center; + position: relative; + right: 24px; + border-radius: 3px; + user-select: none; + } + } +} + +div.dark.search-box-root { + + div.clean-box { + + div.clean-box-view:hover { + background-color: $lt-bg-color-lvl2-dark; + } + + div.clean-box-view { + background-color: $lt-bg-color-lvl3-dark; + } + } + + div.input-box input { + color: $lt-font-color-normal-dark; + } +} + +div.light.search-box-root { + + div.clean-box { + + div.clean-box-view:hover { + background-color: $lt-bg-color-lvl3-light; + } + + div.clean-box-view { + background-color: $lt-bg-color-lvl2-light; + } + } + + div.input-box input { + color: $lt-font-color-normal-light; + } +} \ No newline at end of file diff --git a/source/Component/SearchBox/SearchBox.tsx b/source/Component/SearchBox/SearchBox.tsx new file mode 100644 index 0000000..07c3ceb --- /dev/null +++ b/source/Component/SearchBox/SearchBox.tsx @@ -0,0 +1,60 @@ +import { AllI18nKeys, I18N } from "@Component/Localization/Localization"; +import { BackgroundLevel, FontLevel, Theme } from "@Component/Theme/Theme"; +import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; +import { Icon } from "@fluentui/react"; +import { Component, ReactNode } from "react"; +import "./SearchBox.scss"; + +interface ISearchBoxProps { + value?: string; + valueChange?: (value: string) => void; + placeholderI18N?: AllI18nKeys; + className?: string; +} + +@useSettingWithEvent("language") +class SearchBox extends Component { + + private renderCleanBox() { + return
+
{ + if (this.props.valueChange) { + this.props.valueChange("") + } + }} + > + +
+
; + } + + public render(): ReactNode { + return +
+ +
+
+ { + if (e.target instanceof HTMLInputElement && this.props.valueChange) { + this.props.valueChange(e.target.value) + } + }} + /> +
+ {this.props.value ? this.renderCleanBox() : null} +
+ } +} + +export { SearchBox }; \ No newline at end of file diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 23c8276..8db5a9d 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -53,10 +53,12 @@ const EN_US = { "Popup.Delete.Objects.Confirm": "Are you sure you want to delete this object(s)? The object is deleted and cannot be recalled.", "Popup.Setting.Title": "Preferences setting", "Popup.Add.Behavior.Title": "Add behavior", + "Popup.Add.Behavior.Action.Add": "Add all select behavior", "Build.In.Label.Name.All.Group": "All group", "Build.In.Label.Name.All.Range": "All range", "Behavior.Template.Title": "Behavior", "Behavior.Template.Intro": "This is a template behavior", + "Common.Search.Placeholder": "Search in here...", "Common.No.Data": "No Data", "Common.No.Unknown.Error": "Unknown error", "Common.Attr.Title.Basic": "Basic properties", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 60b890e..69ff4ce 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -53,10 +53,12 @@ const ZH_CN = { "Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。", "Popup.Setting.Title": "首选项设置", "Popup.Add.Behavior.Title": "添加行为", + "Popup.Add.Behavior.Action.Add": "添加全部选中行为", "Build.In.Label.Name.All.Group": "全部群", "Build.In.Label.Name.All.Range": "全部范围", "Behavior.Template.Title": "行为", "Behavior.Template.Intro": "这是一个模板行为", + "Common.Search.Placeholder": "在此处搜索...", "Common.No.Data": "暂无数据", "Common.No.Unknown.Error": "未知错误", "Common.Attr.Title.Basic": "基础属性",