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 { 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