Add behavior model & search box & setting add behavior popup #26
8
source/Behavior/Behavior.ts
Normal file
8
source/Behavior/Behavior.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior";
|
||||||
|
import { Template } from "./Template";
|
||||||
|
|
||||||
|
const AllBehaviors: IAnyBehaviorRecorder[] = [
|
||||||
|
new BehaviorRecorder(Template)
|
||||||
|
]
|
||||||
|
|
||||||
|
export { AllBehaviors };
|
20
source/Behavior/Template.ts
Normal file
20
source/Behavior/Template.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Behavior } from "@Model/Behavior";
|
||||||
|
|
||||||
|
type ITemplateBehaviorParameter = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type ITemplateBehaviorEvent = {}
|
||||||
|
|
||||||
|
class Template extends Behavior<ITemplateBehaviorParameter, ITemplateBehaviorEvent> {
|
||||||
|
|
||||||
|
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 };
|
11
source/Component/BehaviorPopup/BehaviorPopup.scss
Normal file
11
source/Component/BehaviorPopup/BehaviorPopup.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@import "../Theme/Theme.scss";
|
||||||
|
|
||||||
|
div.behavior-popup {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.behavior-popup-search-box {
|
||||||
|
padding: 10px 0 0 10px;
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
}
|
65
source/Component/BehaviorPopup/BehaviorPopup.tsx
Normal file
65
source/Component/BehaviorPopup/BehaviorPopup.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Component, ReactNode } from "react";
|
||||||
|
import { Popup } from "@Context/Popups";
|
||||||
|
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<IBehaviorPopupProps> {
|
||||||
|
|
||||||
|
public minWidth: number = 400;
|
||||||
|
public minHeight: number = 300;
|
||||||
|
public width: number = 600;
|
||||||
|
public height: number = 450;
|
||||||
|
|
||||||
|
public onRenderHeader(): ReactNode {
|
||||||
|
return <Localization i18nKey="Popup.Add.Behavior.Title"/>
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
return <BehaviorPopupComponent {...this.props}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BehaviorPopupComponent extends Component<IBehaviorPopupProps, IBehaviorPopupState> {
|
||||||
|
|
||||||
|
state: Readonly<IBehaviorPopupState> = {
|
||||||
|
searchValue: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderHeader = () => {
|
||||||
|
return <div className="behavior-popup-search-box">
|
||||||
|
<SearchBox
|
||||||
|
valueChange={(value) => {
|
||||||
|
this.setState({
|
||||||
|
searchValue: value
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
value={this.state.searchValue}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
return <ConfirmContent
|
||||||
|
className="behavior-popup"
|
||||||
|
actions={[{
|
||||||
|
i18nKey: "Popup.Add.Behavior.Action.Add"
|
||||||
|
}]}
|
||||||
|
header={this.renderHeader}
|
||||||
|
headerHeight={36}
|
||||||
|
>
|
||||||
|
|
||||||
|
</ConfirmContent>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BehaviorPopup };
|
@ -4,7 +4,8 @@ import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost
|
|||||||
import { useSetting, IMixinSettingProps } from "@Context/Setting";
|
import { useSetting, IMixinSettingProps } from "@Context/Setting";
|
||||||
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
|
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
|
||||||
import { AllI18nKeys } from "../Localization/Localization";
|
import { AllI18nKeys } from "../Localization/Localization";
|
||||||
import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup";
|
import { SettingPopup } from "../SettingPopup/SettingPopup";
|
||||||
|
import { BehaviorPopup } from "../BehaviorPopup/BehaviorPopup";
|
||||||
import { Component, ReactNode } from "react";
|
import { Component, ReactNode } from "react";
|
||||||
import { MouseMod } from "@GLRender/ClassicRenderer";
|
import { MouseMod } from "@GLRender/ClassicRenderer";
|
||||||
import "./CommandBar.scss";
|
import "./CommandBar.scss";
|
||||||
@ -52,13 +53,19 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi
|
|||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
{this.getRenderButton({
|
{this.getRenderButton({
|
||||||
iconName: "CubeShape",
|
iconName: "ProductVariant",
|
||||||
i18NKey: "Command.Bar.Add.Range.Info",
|
i18NKey: "Command.Bar.Add.Range.Info",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.props.status ? this.props.status.newRange() : undefined;
|
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",
|
||||||
|
click: () => {
|
||||||
|
this.props.status?.popup.showPopup(BehaviorPopup, {});
|
||||||
|
}
|
||||||
|
})}
|
||||||
{this.getRenderButton({
|
{this.getRenderButton({
|
||||||
iconName: "Tag",
|
iconName: "Tag",
|
||||||
i18NKey: "Command.Bar.Add.Tag.Info",
|
i18NKey: "Command.Bar.Add.Tag.Info",
|
||||||
@ -73,7 +80,7 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi
|
|||||||
iconName: "Settings",
|
iconName: "Settings",
|
||||||
i18NKey: "Command.Bar.Setting.Info",
|
i18NKey: "Command.Bar.Setting.Info",
|
||||||
click: () => {
|
click: () => {
|
||||||
// this.props.status?.popup.showPopup(ConfirmPopup, {});
|
this.props.status?.popup.showPopup(SettingPopup, {});
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,10 +4,35 @@ div.confirm-root {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
div.header-view {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
div.content-views {
|
div.content-views {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc( 100% - 36px );
|
|
||||||
box-sizing: border-box;
|
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;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,14 +60,27 @@ div.confirm-root {
|
|||||||
div.action-button.red {
|
div.action-button.red {
|
||||||
color: $lt-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.dark.confirm-root {
|
||||||
|
|
||||||
|
div.content-views::-webkit-scrollbar-thumb {
|
||||||
|
background-color: $lt-bg-color-lvl1-dark;
|
||||||
|
}
|
||||||
|
|
||||||
div.action-view {
|
div.action-view {
|
||||||
|
|
||||||
div.action-button {
|
div.action-button, div.action-button.disable:hover {
|
||||||
background-color: $lt-bg-color-lvl3-dark;
|
background-color: $lt-bg-color-lvl3-dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +92,13 @@ div.dark.confirm-root {
|
|||||||
|
|
||||||
div.light.confirm-root {
|
div.light.confirm-root {
|
||||||
|
|
||||||
|
div.content-views::-webkit-scrollbar-thumb {
|
||||||
|
background-color: $lt-bg-color-lvl1-light;
|
||||||
|
}
|
||||||
|
|
||||||
div.action-view {
|
div.action-view {
|
||||||
|
|
||||||
div.action-button {
|
div.action-button, div.action-button.disable:hover {
|
||||||
background-color: $lt-bg-color-lvl3-light;
|
background-color: $lt-bg-color-lvl3-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Popup } from "@Context/Popups";
|
import { Popup } from "@Context/Popups";
|
||||||
import { ReactNode } from "react";
|
import { Component, ReactNode } from "react";
|
||||||
import { Message } from "@Component/Message/Message";
|
import { Message } from "@Component/Message/Message";
|
||||||
import { Theme } from "@Component/Theme/Theme";
|
import { Theme } from "@Component/Theme/Theme";
|
||||||
import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
|
import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
|
||||||
@ -7,12 +7,12 @@ import "./ConfirmPopup.scss";
|
|||||||
|
|
||||||
interface IConfirmPopupProps {
|
interface IConfirmPopupProps {
|
||||||
titleI18N?: AllI18nKeys;
|
titleI18N?: AllI18nKeys;
|
||||||
infoI18n: AllI18nKeys;
|
infoI18n?: AllI18nKeys;
|
||||||
yesI18n?: AllI18nKeys;
|
yesI18n?: AllI18nKeys;
|
||||||
noI18n?: AllI18nKeys;
|
noI18n?: AllI18nKeys;
|
||||||
|
red?: "yes" | "no";
|
||||||
yes?: () => any;
|
yes?: () => any;
|
||||||
no?: () => any;
|
no?: () => any;
|
||||||
red?: "yes" | "no";
|
|
||||||
}
|
}
|
||||||
class ConfirmPopup extends Popup<IConfirmPopupProps> {
|
class ConfirmPopup extends Popup<IConfirmPopupProps> {
|
||||||
|
|
||||||
@ -24,37 +24,139 @@ class ConfirmPopup extends Popup<IConfirmPopupProps> {
|
|||||||
return <Localization i18nKey={this.props.titleI18N ?? "Popup.Title.Confirm"}/>
|
return <Localization i18nKey={this.props.titleI18N ?? "Popup.Title.Confirm"}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private genActionClickFunction(fn?: () => any): () => any {
|
||||||
|
return () => {
|
||||||
|
if (fn) fn();
|
||||||
|
this.close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public render(): ReactNode {
|
public render(): ReactNode {
|
||||||
|
|
||||||
const yesClassList: string[] = ["action-button", "yes-button"];
|
const actionList: IActionButtonProps[] = [];
|
||||||
const noClassList: string[] = ["action-button", "no-button"];
|
|
||||||
if (this.props.red === "no") {
|
if (this.props.yesI18n || this.props.yes) {
|
||||||
noClassList.push("red");
|
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 <ConfirmContent
|
||||||
|
actions={actionList}
|
||||||
|
>
|
||||||
|
{this.props.infoI18n ? <Message i18nKey={this.props.infoI18n}/> : null}
|
||||||
|
</ConfirmContent>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string, string>;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfirmContent extends Component<IConfirmContentProps> {
|
||||||
|
|
||||||
|
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 <div
|
||||||
|
className={classList.join(" ")}
|
||||||
|
onClick={props.disable ? undefined : props.onClick}
|
||||||
|
key={key}
|
||||||
|
>
|
||||||
|
<Localization i18nKey={props.i18nKey} options={props.i18nOption}/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHeaderHeight(): number {
|
||||||
|
return this.props.headerHeight ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderHeader() {
|
||||||
|
return <div
|
||||||
|
className="header-view"
|
||||||
|
style={{
|
||||||
|
maxHeight: this.getHeaderHeight(),
|
||||||
|
minHeight: this.getHeaderHeight(),
|
||||||
|
height: this.getHeaderHeight()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.header ? this.props.header() : null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <Theme className="confirm-root">
|
return <Theme className="confirm-root">
|
||||||
<div className="content-views">
|
|
||||||
<Message i18nKey={this.props.infoI18n}/>
|
{this.props.header ? this.renderHeader() : null}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={contentClassNameList.join(" ")}
|
||||||
|
style={{
|
||||||
|
height: `calc( 100% - ${this.getHeaderHeight() + 36}px )`
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="action-view">
|
<div className="action-view">
|
||||||
<div className={yesClassList.join(" ")} onClick={() => {
|
{
|
||||||
this.props.yes ? this.props.yes() : null;
|
this.props.actions.map((prop, index) => {
|
||||||
this.close();
|
return this.renderActionButton(prop, index);
|
||||||
}}>
|
})
|
||||||
<Localization i18nKey={this.props.yesI18n ?? "Popup.Action.Yes"}/>
|
}
|
||||||
</div>
|
|
||||||
<div className={noClassList.join(" ")} onClick={() => {
|
|
||||||
this.props.no ? this.props.no() : null;
|
|
||||||
this.close();
|
|
||||||
}}>
|
|
||||||
<Localization i18nKey={this.props.noI18n ?? "Popup.Action.No"}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Theme>;
|
</Theme>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ConfirmPopup }
|
export { ConfirmPopup, ConfirmContent }
|
95
source/Component/SearchBox/SearchBox.scss
Normal file
95
source/Component/SearchBox/SearchBox.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
60
source/Component/SearchBox/SearchBox.tsx
Normal file
60
source/Component/SearchBox/SearchBox.tsx
Normal file
@ -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<ISearchBoxProps & IMixinSettingProps> {
|
||||||
|
|
||||||
|
private renderCleanBox() {
|
||||||
|
return <div className="clean-box">
|
||||||
|
<div
|
||||||
|
className="clean-box-view"
|
||||||
|
onClick={() => {
|
||||||
|
if (this.props.valueChange) {
|
||||||
|
this.props.valueChange("")
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon iconName="CalculatorMultiply"/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
return <Theme
|
||||||
|
className={"search-box-root" + (this.props.className ? ` ${this.props.className}` : "")}
|
||||||
|
backgroundLevel={BackgroundLevel.Level3}
|
||||||
|
fontLevel={FontLevel.normal}
|
||||||
|
>
|
||||||
|
<div className="search-icon">
|
||||||
|
<Icon iconName="search"/>
|
||||||
|
</div>
|
||||||
|
<div className="input-box">
|
||||||
|
<input
|
||||||
|
value={this.props.value}
|
||||||
|
placeholder={
|
||||||
|
I18N(this.props, this.props.placeholderI18N ?? "Common.Search.Placeholder")
|
||||||
|
}
|
||||||
|
onInput={(e) => {
|
||||||
|
if (e.target instanceof HTMLInputElement && this.props.valueChange) {
|
||||||
|
this.props.valueChange(e.target.value)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{this.props.value ? this.renderCleanBox() : null}
|
||||||
|
</Theme>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SearchBox };
|
6
source/Component/SettingPopup/SettingPopup.scss
Normal file
6
source/Component/SettingPopup/SettingPopup.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@import "../Theme/Theme.scss";
|
||||||
|
|
||||||
|
div.setting-popup {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
34
source/Component/SettingPopup/SettingPopup.tsx
Normal file
34
source/Component/SettingPopup/SettingPopup.tsx
Normal file
@ -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<ISettingPopupProps> {
|
||||||
|
|
||||||
|
public minWidth: number = 400;
|
||||||
|
public minHeight: number = 300;
|
||||||
|
public width: number = 600;
|
||||||
|
public height: number = 450;
|
||||||
|
|
||||||
|
public onRenderHeader(): ReactNode {
|
||||||
|
return <Localization i18nKey="Popup.Setting.Title"/>
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
return <SettingPopupComponent {...this.props}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingPopupComponent extends Component<ISettingPopupProps> {
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
return <Theme className="setting-popup"></Theme>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SettingPopup };
|
@ -83,13 +83,13 @@ class Status extends Emitter<IStatusEvent> {
|
|||||||
*/
|
*/
|
||||||
public focusLabel?: Label;
|
public focusLabel?: Label;
|
||||||
|
|
||||||
private drawtimer?: NodeJS.Timeout;
|
private drawTimer?: NodeJS.Timeout;
|
||||||
|
|
||||||
private delayDraw = () => {
|
private delayDraw = () => {
|
||||||
this.drawtimer ? clearTimeout(this.drawtimer) : null;
|
this.drawTimer ? clearTimeout(this.drawTimer) : null;
|
||||||
this.drawtimer = setTimeout(() => {
|
this.drawTimer = setTimeout(() => {
|
||||||
this.model.draw();
|
this.model.draw();
|
||||||
this.drawtimer = undefined;
|
this.drawTimer = undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +51,14 @@ const EN_US = {
|
|||||||
"Popup.Action.Objects.Confirm.Title": "Confirm Delete",
|
"Popup.Action.Objects.Confirm.Title": "Confirm Delete",
|
||||||
"Popup.Action.Objects.Confirm.Delete": "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.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.Group": "All group",
|
||||||
"Build.In.Label.Name.All.Range": "All range",
|
"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.Data": "No Data",
|
||||||
"Common.No.Unknown.Error": "Unknown error",
|
"Common.No.Unknown.Error": "Unknown error",
|
||||||
"Common.Attr.Title.Basic": "Basic properties",
|
"Common.Attr.Title.Basic": "Basic properties",
|
||||||
|
@ -51,8 +51,14 @@ const ZH_CN = {
|
|||||||
"Popup.Action.Objects.Confirm.Title": "删除确认",
|
"Popup.Action.Objects.Confirm.Title": "删除确认",
|
||||||
"Popup.Action.Objects.Confirm.Delete": "删除",
|
"Popup.Action.Objects.Confirm.Delete": "删除",
|
||||||
"Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。",
|
"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.Group": "全部群",
|
||||||
"Build.In.Label.Name.All.Range": "全部范围",
|
"Build.In.Label.Name.All.Range": "全部范围",
|
||||||
|
"Behavior.Template.Title": "行为",
|
||||||
|
"Behavior.Template.Intro": "这是一个模板行为",
|
||||||
|
"Common.Search.Placeholder": "在此处搜索...",
|
||||||
"Common.No.Data": "暂无数据",
|
"Common.No.Data": "暂无数据",
|
||||||
"Common.No.Unknown.Error": "未知错误",
|
"Common.No.Unknown.Error": "未知错误",
|
||||||
"Common.Attr.Title.Basic": "基础属性",
|
"Common.Attr.Title.Basic": "基础属性",
|
||||||
|
@ -3,40 +3,330 @@ import { Emitter, EventType } from "./Emitter";
|
|||||||
import type { Individual } from "./Individual";
|
import type { Individual } from "./Individual";
|
||||||
import type { Group } from "./Group";
|
import type { Group } from "./Group";
|
||||||
import type { Model } from "./Model";
|
import type { Model } from "./Model";
|
||||||
|
import type { Range } from "./Range";
|
||||||
|
import type { Label } from "./Label";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 群体的某种行为
|
* 参数类型
|
||||||
*/
|
*/
|
||||||
abstract class Behavior<
|
type IMapBasicParamTypeKeyToType = {
|
||||||
P extends IAnyObject = {},
|
"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<K extends IParamType> = AllMapType[K];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特殊对象类型判定
|
||||||
|
*/
|
||||||
|
const objectTypeListEnumSet = new Set<IParamType>(["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<T extends IParamType = IParamType> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数类型
|
||||||
|
*/
|
||||||
|
type: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数默认值
|
||||||
|
*/
|
||||||
|
defaultValue?: IParamValue<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数值变化回调
|
||||||
|
*/
|
||||||
|
onChange?: (value: IParamValue<T>) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名字
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符长度
|
||||||
|
*/
|
||||||
|
stringLength?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字步长
|
||||||
|
*/
|
||||||
|
numberStep?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大值最小值
|
||||||
|
*/
|
||||||
|
numberMax?: number;
|
||||||
|
numberMin?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标名字
|
||||||
|
*/
|
||||||
|
iconName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBehaviorParameter {
|
||||||
|
[x: string]: IParamType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数类型列表
|
||||||
|
*/
|
||||||
|
type IBehaviorParameterOption<P extends IBehaviorParameter> = {
|
||||||
|
[X in keyof P]: IBehaviorParameterOptionItem<P[X]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数类型列表映射到参数对象
|
||||||
|
*/
|
||||||
|
type IBehaviorParameterValue<P extends IBehaviorParameter> = {
|
||||||
|
[X in keyof P]: IParamValue<P[X]>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为构造函数类型
|
||||||
|
*/
|
||||||
|
type IBehaviorConstructor<
|
||||||
|
P extends IBehaviorParameter = {},
|
||||||
E extends Record<EventType, any> = {}
|
E extends Record<EventType, any> = {}
|
||||||
> extends Emitter<E> {
|
> = new (id: string, parameter: IBehaviorParameterValue<P>) => Behavior<P, E>;
|
||||||
|
|
||||||
|
type IAnyBehavior = Behavior<any, any>;
|
||||||
|
type IAnyBehaviorRecorder = BehaviorRecorder<any, any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为的基础信息
|
||||||
|
*/
|
||||||
|
class BehaviorInfo<E extends Record<EventType, any> = {}> extends Emitter<E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标名字
|
||||||
|
*/
|
||||||
|
public iconName: string = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行为 ID
|
* 行为 ID
|
||||||
*/
|
*/
|
||||||
abstract id: string;
|
public behaviorId: string = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行为名称
|
* 行为名称
|
||||||
*/
|
*/
|
||||||
abstract name: string;
|
public behaviorName: string = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行为描述
|
* 行为描述
|
||||||
*/
|
*/
|
||||||
public describe?: string = "";
|
public describe?: string = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
class BehaviorRecorder<
|
||||||
|
P extends IBehaviorParameter = {},
|
||||||
|
E extends Record<EventType, any> = {}
|
||||||
|
> extends BehaviorInfo<{}> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命名序号
|
||||||
|
*/
|
||||||
|
public nameIndex: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取下一个 ID
|
||||||
|
*/
|
||||||
|
public getNextId() {
|
||||||
|
return `B-${this.behaviorName}-${this.nameIndex ++}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为类型
|
||||||
|
*/
|
||||||
|
public behavior: IBehaviorConstructor<P, E>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为实例
|
||||||
|
*/
|
||||||
|
public behaviorInstance: Behavior<P, E>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象参数列表
|
||||||
|
*/
|
||||||
|
public parameterOption: IBehaviorParameterOption<P>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数列表的默认值
|
||||||
|
*/
|
||||||
|
public getDefaultValue(): IBehaviorParameterValue<P> {
|
||||||
|
let defaultObj = {} as IBehaviorParameterValue<P>;
|
||||||
|
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(): Behavior<P, E> {
|
||||||
|
return new this.behavior(this.getNextId(), this.getDefaultValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(behavior: IBehaviorConstructor<P, E>) {
|
||||||
|
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 IBehaviorParameter = {},
|
||||||
|
E extends Record<EventType, any> = {}
|
||||||
|
> extends BehaviorInfo<E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义名字
|
||||||
|
*/
|
||||||
|
public name: string = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例 ID
|
||||||
|
*/
|
||||||
|
public id: string = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先级
|
* 优先级
|
||||||
* 值越大执行顺序越靠后
|
* 值越大执行顺序越靠后
|
||||||
*/
|
*/
|
||||||
public priority?: number = 0;
|
public priority: number = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行为参数
|
* 行为参数
|
||||||
*/
|
*/
|
||||||
abstract parameter?: P;
|
public parameter: IBehaviorParameterValue<P>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象参数列表
|
||||||
|
*/
|
||||||
|
public parameterOption: IBehaviorParameterOption<P> = {} as any;
|
||||||
|
|
||||||
|
public constructor(id: string, parameter: IBehaviorParameterValue<P>) {
|
||||||
|
super();
|
||||||
|
this.id = id;
|
||||||
|
this.parameter = parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相等校验
|
||||||
|
*/
|
||||||
|
public equal(behavior: Behavior<any, any>): 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 +357,8 @@ abstract class Behavior<
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Behavior };
|
export {
|
||||||
|
Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem,
|
||||||
|
IAnyBehavior, IAnyBehaviorRecorder
|
||||||
|
};
|
||||||
export default { Behavior };
|
export default { Behavior };
|
@ -60,22 +60,36 @@ class CtrlObject extends LabelObject {
|
|||||||
return this === obj || this.id === obj.id;
|
return this === obj || this.id === obj.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记对象被删除
|
||||||
|
*/
|
||||||
|
public markDelete() {
|
||||||
|
this.deleteFlag = true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除标记
|
* 删除标记
|
||||||
*/
|
*/
|
||||||
private deleteFlag: boolean = false;
|
private deleteFlag: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否被删除
|
* 检测是否被删除
|
||||||
*/
|
*/
|
||||||
public isDeleted(): boolean {
|
public testDelete() {
|
||||||
if (this.deleteFlag) return true;
|
|
||||||
for (let i = 0; i < this.model.objectPool.length; i++) {
|
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;
|
this.deleteFlag = true;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否被删除
|
||||||
|
*/
|
||||||
|
public isDeleted(): boolean {
|
||||||
|
return this.deleteFlag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class Group extends CtrlObject {
|
|||||||
/**
|
/**
|
||||||
* 个体生成方式
|
* 个体生成方式
|
||||||
*/
|
*/
|
||||||
public genMethod: GenMod = GenMod.Point;
|
public genMethod: GenMod = GenMod.Range;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成位置坐标
|
* 生成位置坐标
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { Individual } from "./Individual";
|
import { Individual } from "./Individual";
|
||||||
import { Group } from "./Group";
|
import { Group } from "./Group";
|
||||||
import { Range } from "./Range";
|
import { Range } from "./Range";
|
||||||
@ -6,18 +5,14 @@ import { Emitter, EventType, EventMixin } from "./Emitter";
|
|||||||
import { CtrlObject } from "./CtrlObject";
|
import { CtrlObject } from "./CtrlObject";
|
||||||
import { ObjectID, AbstractRenderer } from "./Renderer";
|
import { ObjectID, AbstractRenderer } from "./Renderer";
|
||||||
import { Label } from "./Label";
|
import { Label } from "./Label";
|
||||||
|
import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior";
|
||||||
|
|
||||||
type ModelEvent = {
|
type ModelEvent = {
|
||||||
loop: number;
|
loop: number;
|
||||||
groupAdd: Group;
|
|
||||||
rangeAdd: Range;
|
|
||||||
labelAdd: Label;
|
|
||||||
labelDelete: Label;
|
|
||||||
labelChange: Label[];
|
labelChange: Label[];
|
||||||
objectAdd: CtrlObject;
|
|
||||||
objectDelete: CtrlObject[];
|
|
||||||
objectChange: CtrlObject[];
|
objectChange: CtrlObject[];
|
||||||
individualChange: Group;
|
individualChange: Group;
|
||||||
|
behaviorChange: IAnyBehavior;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,7 +63,6 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
console.log(`Model: Creat label with id ${this.idIndex}`);
|
console.log(`Model: Creat label with id ${this.idIndex}`);
|
||||||
let label = new Label(this, this.nextId("L"), name);
|
let label = new Label(this, this.nextId("L"), name);
|
||||||
this.labelPool.push(label);
|
this.labelPool.push(label);
|
||||||
this.emit("labelAdd", label);
|
|
||||||
this.emit("labelChange", this.labelPool);
|
this.emit("labelChange", this.labelPool);
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
@ -97,7 +91,6 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
this.labelPool.splice(index, 1);
|
this.labelPool.splice(index, 1);
|
||||||
deletedLabel.testDelete();
|
deletedLabel.testDelete();
|
||||||
console.log(`Model: Delete label ${deletedLabel.name ?? deletedLabel.id}`);
|
console.log(`Model: Delete label ${deletedLabel.name ?? deletedLabel.id}`);
|
||||||
this.emit("labelDelete", deletedLabel);
|
|
||||||
this.emit("labelChange", this.labelPool);
|
this.emit("labelChange", this.labelPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,8 +128,6 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
console.log(`Model: Creat group with id ${this.idIndex}`);
|
console.log(`Model: Creat group with id ${this.idIndex}`);
|
||||||
let group = new Group(this, this.nextId("G"));
|
let group = new Group(this, this.nextId("G"));
|
||||||
this.objectPool.push(group);
|
this.objectPool.push(group);
|
||||||
this.emit("groupAdd", group);
|
|
||||||
this.emit("objectAdd", group);
|
|
||||||
this.emit("objectChange", this.objectPool);
|
this.emit("objectChange", this.objectPool);
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
@ -148,8 +139,6 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
console.log(`Model: Creat range with id ${this.idIndex}`);
|
console.log(`Model: Creat range with id ${this.idIndex}`);
|
||||||
let range = new Range(this, this.nextId("R"));
|
let range = new Range(this, this.nextId("R"));
|
||||||
this.objectPool.push(range);
|
this.objectPool.push(range);
|
||||||
this.emit("rangeAdd", range);
|
|
||||||
this.emit("objectAdd", range);
|
|
||||||
this.emit("objectChange", this.objectPool);
|
this.emit("objectChange", this.objectPool);
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
@ -176,6 +165,7 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
|
|
||||||
if (needDeleted) {
|
if (needDeleted) {
|
||||||
deletedObject.push(currentObject);
|
deletedObject.push(currentObject);
|
||||||
|
currentObject.markDelete();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
@ -184,12 +174,68 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
|
|
||||||
if (deletedObject.length) {
|
if (deletedObject.length) {
|
||||||
console.log(`Model: Delete object ${deletedObject.map((object) => object.id).join(", ")}`);
|
console.log(`Model: Delete object ${deletedObject.map((object) => object.id).join(", ")}`);
|
||||||
this.emit("objectDelete", deletedObject);
|
|
||||||
this.emit("objectChange", this.objectPool);
|
this.emit("objectChange", this.objectPool);
|
||||||
}
|
}
|
||||||
return deletedObject;
|
return deletedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为池
|
||||||
|
*/
|
||||||
|
public behaviorPool: IAnyBehavior[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个行为
|
||||||
|
*/
|
||||||
|
public addBehavior<B extends IAnyBehaviorRecorder>(recorder: B): B["behaviorInstance"] {
|
||||||
|
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): IAnyBehavior | 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: IAnyBehavior | ObjectID) {
|
||||||
|
let deletedBehavior: IAnyBehavior | 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染器
|
* 渲染器
|
||||||
*/
|
*/
|
||||||
|
@ -52,7 +52,7 @@ class ObjectCommand extends Component<IMixinStatusProps> {
|
|||||||
this.props.status ? this.props.status.newRange() : undefined;
|
this.props.status ? this.props.status.newRange() : undefined;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon iconName="CubeShape"></Icon>
|
<Icon iconName="ProductVariant"></Icon>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="command-item red"
|
className="command-item red"
|
||||||
|
Loading…
Reference in New Issue
Block a user