Compare commits
	
		
			9 Commits
		
	
	
		
			bb762c8273
			...
			b6e13fa296
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b6e13fa296 | |||
| 87023251a9 | |||
| ce5e6a34d8 | |||
| ffdb796e8f | |||
| 43ab6cb03f | |||
| 850eea254c | |||
| feb277c3a2 | |||
| 156b4651f5 | |||
| 021e2e7821 | 
							
								
								
									
										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 { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | ||||
| 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 { MouseMod } from "@GLRender/ClassicRenderer"; | ||||
| import "./CommandBar.scss"; | ||||
| @ -52,13 +53,19 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi | ||||
|                     } | ||||
|                 })} | ||||
|                 {this.getRenderButton({ | ||||
|                     iconName: "CubeShape", | ||||
|                     iconName: "ProductVariant", | ||||
|                     i18NKey: "Command.Bar.Add.Range.Info", | ||||
|                     click: () => { | ||||
|                         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({ | ||||
|                     iconName: "Tag", | ||||
|                     i18NKey: "Command.Bar.Add.Tag.Info", | ||||
| @ -73,7 +80,7 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi | ||||
|                     iconName: "Settings", | ||||
|                     i18NKey: "Command.Bar.Setting.Info", | ||||
|                     click: () => { | ||||
|                         // this.props.status?.popup.showPopup(ConfirmPopup, {});
 | ||||
|                         this.props.status?.popup.showPopup(SettingPopup, {}); | ||||
|                     } | ||||
|                 })} | ||||
|             </div> | ||||
|  | ||||
| @ -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; | ||||
| 		} | ||||
| 
 | ||||
|  | ||||
| @ -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<IConfirmPopupProps> { | ||||
| 
 | ||||
| @ -24,37 +24,139 @@ class ConfirmPopup extends Popup<IConfirmPopupProps> { | ||||
| 		return <Localization i18nKey={this.props.titleI18N ?? "Popup.Title.Confirm"}/> | ||||
| 	} | ||||
| 
 | ||||
| 	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 <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"> | ||||
| 			<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 className="action-view"> | ||||
| 				<div className={yesClassList.join(" ")} onClick={() => { | ||||
| 					this.props.yes ? this.props.yes() : null; | ||||
| 					this.close(); | ||||
| 				}}> | ||||
| 					<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> | ||||
| 				{ | ||||
| 					this.props.actions.map((prop, index) => { | ||||
| 						return this.renderActionButton(prop, index); | ||||
| 					}) | ||||
| 				} | ||||
| 			</div> | ||||
| 		</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; | ||||
| 
 | ||||
|     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; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -51,8 +51,14 @@ 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", | ||||
|     "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", | ||||
|  | ||||
| @ -51,8 +51,14 @@ const ZH_CN = { | ||||
|     "Popup.Action.Objects.Confirm.Title": "删除确认", | ||||
|     "Popup.Action.Objects.Confirm.Delete": "删除", | ||||
|     "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": "基础属性", | ||||
|  | ||||
| @ -3,40 +3,330 @@ 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 = {}, | ||||
| 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<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> = {} | ||||
| > 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 | ||||
|      */ | ||||
|     abstract id: string; | ||||
|     public behaviorId: string = ""; | ||||
|          | ||||
|     /** | ||||
|      * 行为名称 | ||||
|      */ | ||||
|     abstract name: string; | ||||
|     public behaviorName: 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 }; | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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 { 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, IAnyBehavior, IAnyBehaviorRecorder } 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: IAnyBehavior; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
| @ -68,7 +63,6 @@ class Model extends Emitter<ModelEvent> { | ||||
|         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<ModelEvent> { | ||||
|             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<ModelEvent> { | ||||
|         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<ModelEvent> { | ||||
|         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<ModelEvent> { | ||||
| 
 | ||||
|             if (needDeleted) { | ||||
|                 deletedObject.push(currentObject); | ||||
|                 currentObject.markDelete(); | ||||
|                 return false; | ||||
|             } else { | ||||
|                 return true; | ||||
| @ -184,12 +174,68 @@ class Model extends Emitter<ModelEvent> { | ||||
| 
 | ||||
|         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: 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; | ||||
| 				}} | ||||
| 			> | ||||
| 				<Icon iconName="CubeShape"></Icon> | ||||
| 				<Icon iconName="ProductVariant"></Icon> | ||||
| 			</div> | ||||
| 			<div | ||||
| 				className="command-item red" | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user