Compare commits
	
		
			No commits in common. "7923b2d802d293edb31c75eb41009526b6b7d580" and "e8935403ea763f98a46061261c59e2ed4155fa6e" have entirely different histories.
		
	
	
		
			7923b2d802
			...
			e8935403ea
		
	
		
| @ -68,13 +68,7 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi | ||||
|                 {this.getRenderButton({ iconName: "Camera", i18NKey: "Command.Bar.Camera.Info" })} | ||||
|             </div> | ||||
|             <div> | ||||
|                 {this.getRenderButton({ | ||||
|                     iconName: "Settings", | ||||
|                     i18NKey: "Command.Bar.Setting.Info", | ||||
|                     click: () => { | ||||
|                         this.props.status ? this.props.status.popup.showPopup() : undefined; | ||||
|                     } | ||||
|                 })} | ||||
|                 {this.getRenderButton({ iconName: "Settings", i18NKey: "Command.Bar.Setting.Info" })} | ||||
|             </div> | ||||
|         </Theme> | ||||
|     } | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { Localization } from "@Component/Localization/Localization"; | ||||
| import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; | ||||
| import { Themes } from "@Context/Setting"; | ||||
| import { DirectionalHint } from "@fluentui/react"; | ||||
| import { ILayout, LayoutDirection } from "@Context/Layout"; | ||||
| import { ILayout, LayoutDirection } from "@Model/Layout"; | ||||
| import { Component, ReactNode, MouseEvent } from "react"; | ||||
| import { getPanelById, getPanelInfoById } from "../../Panel/Panel"; | ||||
| import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; | ||||
|  | ||||
| @ -2,8 +2,8 @@ import { Component, ReactNode, DetailedHTMLProps, HTMLAttributes } from "react"; | ||||
| import { useSetting, IMixinSettingProps, Language } from "@Context/Setting"; | ||||
| import "./Localization.scss"; | ||||
| 
 | ||||
| import EN_US from "@Localization/EN-US"; | ||||
| import ZH_CN from "@Localization/ZH-CN"; | ||||
| import EN_US from "../../Localization/EN-US"; | ||||
| import ZH_CN from "../../Localization/ZH-CN"; | ||||
| 
 | ||||
| const LanguageDataBase = { | ||||
|     EN_US, ZH_CN | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { AllI18nKeys, I18N } from "@Component/Localization/Localization"; | ||||
| import { useSettingWithEvent, IMixinSettingProps, Themes, Language } from "@Context/Setting"; | ||||
| import { useSetting, IMixinSettingProps, Themes, Language } from "@Context/Setting"; | ||||
| import { FunctionComponent } from "react"; | ||||
| import "./Message.scss"; | ||||
| 
 | ||||
| @ -38,5 +38,5 @@ const MessageView: FunctionComponent<IMessageProps & IMixinSettingProps> = (prop | ||||
|     </div> | ||||
| } | ||||
| 
 | ||||
| const Message = useSettingWithEvent("language", "themes")(MessageView); | ||||
| const Message = useSetting(MessageView); | ||||
| export { Message }; | ||||
| @ -1,114 +0,0 @@ | ||||
| @import "../Theme/Theme.scss"; | ||||
| 
 | ||||
| $header-height: 32px; | ||||
| 
 | ||||
| @keyframes show-scale{ | ||||
|     from { | ||||
|         transform: scale3d(1.15, 1.15, 1); | ||||
|     } | ||||
|     to { | ||||
|         transform: scale3d(1, 1, 1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @keyframes show-fade{ | ||||
|     from { | ||||
|         opacity: 0; | ||||
|     } | ||||
|     to { | ||||
|         opacity: 1; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| div.popup-mask.show-fade { | ||||
|     animation: show-fade .1s cubic-bezier(0, 0, 1, 1) both; | ||||
|     opacity: 1; | ||||
| } | ||||
| 
 | ||||
| div.popup-layer.show-scale { | ||||
|     animation: show-scale .3s cubic-bezier(.1, .9, .2, 1) both, | ||||
|     show-fade .1s cubic-bezier(0, 0, 1, 1) both; | ||||
|     transform: scale3d(1, 1, 1); | ||||
|     opacity: 1; | ||||
| } | ||||
| 
 | ||||
| div.popup-mask { | ||||
|     position: absolute; | ||||
|     cursor: pointer; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
| } | ||||
| 
 | ||||
| div.popup-layer { | ||||
|     position: absolute; | ||||
|     border-radius: 3px; | ||||
|     overflow: hidden; | ||||
| 
 | ||||
|     div.popup-layer-header { | ||||
|         min-height: $header-height; | ||||
|         max-height: $header-height; | ||||
|         height: $header-height; | ||||
|         display: flex; | ||||
|         width: 100%; | ||||
| 
 | ||||
|         div.header-text { | ||||
|             width: calc( 100% - 32px ); | ||||
|             flex-shrink: 1; | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             user-select: none; | ||||
|              | ||||
|             span { | ||||
|                 padding-left: 8px; | ||||
|                 display: inline-block; | ||||
|                 vertical-align: middle; | ||||
|                 white-space: nowrap; | ||||
|                 text-overflow: ellipsis; | ||||
|                 overflow: hidden; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         div.header-close-icon { | ||||
|             width: $header-height; | ||||
|             height: $header-height; | ||||
|             flex-shrink: 0; | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             justify-content: center; | ||||
|             user-select: none; | ||||
|             cursor: pointer; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| div.popup-layer.dark { | ||||
| 
 | ||||
|     div.popup-layer-header { | ||||
|         background-color: $lt-bg-color-lvl3-dark; | ||||
| 
 | ||||
|         div.header-close-icon:hover { | ||||
|             background-color: $lt-bg-color-lvl2-dark; | ||||
|             color: $lt-red; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| div.popup-layer.light { | ||||
| 
 | ||||
|     div.popup-layer-header { | ||||
|         background-color: $lt-bg-color-lvl3-light; | ||||
| 
 | ||||
|         div.header-close-icon:hover { | ||||
|             background-color: $lt-bg-color-lvl2-light; | ||||
|             color: $lt-red; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| div.dark.popup-mask { | ||||
|     background-color: rgba(0, 0, 0, 0.55); | ||||
| } | ||||
| 
 | ||||
| div.light.popup-mask { | ||||
|     background-color: rgba(0, 0, 0, 0.15); | ||||
| } | ||||
| @ -1,112 +0,0 @@ | ||||
| import { Component, ReactNode } from "react"; | ||||
| import { IMixinStatusProps, useStatusWithEvent } from "@Context/Status"; | ||||
| import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; | ||||
| import { Popup as PopupModel } from "@Context/Popups"; | ||||
| import { Icon } from "@fluentui/react"; | ||||
| import "./Popup.scss"; | ||||
| 
 | ||||
| interface IPopupProps {} | ||||
| 
 | ||||
| @useStatusWithEvent("popupChange") | ||||
| class Popup extends Component<IPopupProps & IMixinStatusProps> { | ||||
| 
 | ||||
|     public renderMask(index?: number, click?: () => void, key?: string): ReactNode { | ||||
|         const classList: string[] = ["popup-mask", "show-fade"]; | ||||
|         return <Theme | ||||
|             key={key} | ||||
|             onClick={click} | ||||
|             className={classList.join(" ")} | ||||
|             style={{ | ||||
|                 zIndex: index, | ||||
|             }} | ||||
|         /> | ||||
|     } | ||||
| 
 | ||||
|     public renderRootMask(): ReactNode { | ||||
|         if (this.props.status) { | ||||
|             const needMask = this.props.status.popup.popups.some(popup => popup.needMask); | ||||
|             if (!needMask) return null; | ||||
|             return this.renderMask(this.props.status.popup.zIndex, | ||||
|                 () => { | ||||
|                     this.props.status?.popup.popups.forEach( | ||||
|                         popup => popup.onClose() | ||||
|                     ) | ||||
|                 } | ||||
|             ); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public renderMaskList(): ReactNode { | ||||
|         if (this.props.status) { | ||||
|             return this.props.status.popup.popups | ||||
|             .filter((popup) => { | ||||
|                 return popup.needMask && popup.maskForSelf; | ||||
|             }) | ||||
|             .filter((_, index) => { | ||||
|                 if (index === 0) return false; | ||||
|                 return true; | ||||
|             }) | ||||
|             .map((popup) => { | ||||
|                 return this.renderMask(popup.zIndex() - 1, | ||||
|                     () => { | ||||
|                         popup.onClose(); | ||||
|                     }, popup.id | ||||
|                 ); | ||||
|             }) | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public renderHeader(popup: PopupModel): ReactNode { | ||||
|         return <div className="popup-layer-header"> | ||||
|             <div className="header-text"> | ||||
|                 {popup.onRenderHeader()} | ||||
|             </div> | ||||
|             <div | ||||
|                 className="header-close-icon" | ||||
|                 onClick={() => { | ||||
|                     popup.onClose(); | ||||
|                 }} | ||||
|             > | ||||
|                 <Icon iconName="CalculatorMultiply"/> | ||||
|             </div> | ||||
|         </div> | ||||
|     } | ||||
| 
 | ||||
|     public renderLayer(popup: PopupModel) { | ||||
|         const pageWidth = document.documentElement.clientWidth; | ||||
|         const pageHeight = document.documentElement.clientHeight; | ||||
|         const top = (pageHeight - popup.height) / 2; | ||||
|         const left = (pageWidth - popup.width) / 2; | ||||
| 
 | ||||
|         return <Theme | ||||
|             style={{ | ||||
|                 width: popup.width, | ||||
|                 height: popup.height, | ||||
|                 zIndex: popup.zIndex(), | ||||
|                 top: top, | ||||
|                 left: left | ||||
|             }} | ||||
|             key={popup.id} | ||||
|             backgroundLevel={BackgroundLevel.Level4} | ||||
|             className="popup-layer show-scale" | ||||
|         > | ||||
|             {this.renderHeader(popup)} | ||||
|         </Theme> | ||||
|     } | ||||
| 
 | ||||
|     public render(): ReactNode { | ||||
|         return <> | ||||
|             {this.renderRootMask()} | ||||
|             {this.renderMaskList()} | ||||
|             {this.props.status?.popup.popups.map((popup) => { | ||||
|                 return this.renderLayer(popup); | ||||
|             })} | ||||
|         </>; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { Popup }; | ||||
| @ -1,78 +0,0 @@ | ||||
| import { Emitter, EventType } from "@Model/Emitter"; | ||||
| import { Component, FunctionComponent, ReactNode, Consumer } from "react"; | ||||
| 
 | ||||
| type RenderComponent = (new (...p: any) => Component<any, any, any>) | FunctionComponent<any>; | ||||
| 
 | ||||
| function superConnectWithEvent<C extends Emitter<E>, E extends Record<EventType, any>>( | ||||
| 	consumer: Consumer<C>, keyName: string | ||||
| ) { | ||||
| 	return (...events: Array<keyof E>) => { | ||||
| 		return <R extends RenderComponent>(components: R): R => { | ||||
| 			const Components = components as any; | ||||
| 			const Consumer = consumer; | ||||
| 			return class extends Component<R> { | ||||
| 	 | ||||
| 				private status: C | undefined; | ||||
| 				private isEventMount: boolean = false; | ||||
| 				private propsObject: Record<string, C> = {}; | ||||
| 	 | ||||
| 				private handelChange = () => { | ||||
| 					this.forceUpdate(); | ||||
| 				} | ||||
| 	 | ||||
| 				private mountEvent() { | ||||
| 					if (this.status && !this.isEventMount) { | ||||
| 						this.isEventMount = true; | ||||
| 						console.log("Component dep event mount: " + events.join(", ")); | ||||
| 						for (let i = 0; i < events.length; i++) { | ||||
| 							this.status.on(events[i], this.handelChange); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 	 | ||||
| 				private unmountEvent() { | ||||
| 					if (this.status) { | ||||
| 						for (let i = 0; i < events.length; i++) { | ||||
| 							this.status.off(events[i], this.handelChange); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 	 | ||||
| 				public render(): ReactNode { | ||||
| 					return <Consumer> | ||||
| 						{(status: C) => { | ||||
| 							this.status = status; | ||||
| 							this.propsObject[keyName] = status; | ||||
| 							this.mountEvent(); | ||||
| 							return <Components {...this.props} {...this.propsObject}/>; | ||||
| 						}} | ||||
| 					</Consumer> | ||||
| 				} | ||||
| 	 | ||||
| 				public componentWillUnmount() { | ||||
| 					this.unmountEvent(); | ||||
| 				} | ||||
| 	 | ||||
| 			} as any; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function superConnect<C extends Emitter<any>>(consumer: Consumer<C>, keyName: string) { | ||||
| 	return <R extends RenderComponent>(components: R): R => { | ||||
| 		return ((props: any) => { | ||||
| 
 | ||||
| 			const Components = components as any; | ||||
| 			const Consumer = consumer; | ||||
| 
 | ||||
| 			return <Consumer> | ||||
| 				{(status: C) => <Components | ||||
| 					{...props} | ||||
| 					{...{[keyName]: status}} | ||||
| 				/>} | ||||
| 			</Consumer> | ||||
| 		}) as any; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export { superConnectWithEvent, superConnect }; | ||||
| @ -1,175 +0,0 @@ | ||||
| import { ReactNode, createElement } from "react"; | ||||
| import { Emitter } from "@Model/Emitter"; | ||||
| import { Localization } from "@Component/Localization/Localization"; | ||||
| 
 | ||||
| type IPopupConstructor = new (controller: PopupController, id: string) => Popup; | ||||
| 
 | ||||
| /** | ||||
|  * 弹窗类型 | ||||
|  */ | ||||
| class Popup { | ||||
| 
 | ||||
|     public zIndex() { | ||||
|         return this.index * 2 + this.controller.zIndex; | ||||
|     } | ||||
| 
 | ||||
|     public width: number = 300; | ||||
| 
 | ||||
|     public height: number = 200; | ||||
| 
 | ||||
|     public top: number = 0; | ||||
| 
 | ||||
|     public left: number = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * 是否关闭 | ||||
|      */ | ||||
|     public isClose: boolean = false; | ||||
| 
 | ||||
|     /** | ||||
|      * 需要蒙版 | ||||
|      */ | ||||
|     public needMask: boolean = true; | ||||
| 
 | ||||
|     /** | ||||
|      * 单独遮挡下层的蒙版 | ||||
|      */ | ||||
|     public maskForSelf: boolean = false; | ||||
| 
 | ||||
|     /** | ||||
|      * 唯一标识符 | ||||
|      */ | ||||
|     public id: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 控制器 | ||||
|      */ | ||||
|     public controller: PopupController; | ||||
| 
 | ||||
|     /** | ||||
|      * 渲染层级 | ||||
|      */ | ||||
|     public index: number = Infinity; | ||||
| 
 | ||||
|     /** | ||||
|      * react 节点 | ||||
|      */ | ||||
|     public reactNode: ReactNode; | ||||
| 
 | ||||
|     /** | ||||
|      * 渲染标题 | ||||
|      */ | ||||
|     public onRenderHeader(): ReactNode { | ||||
|         return createElement(Localization, {i18nKey: "Popup.Title.Unnamed"}); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 渲染函数 | ||||
|      */ | ||||
|     public onRender(p: Popup): ReactNode { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 关闭回调 | ||||
|      */ | ||||
|     public onClose(): void { | ||||
|         this.close(); | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * 渲染节点 | ||||
|      */ | ||||
|     public render(): ReactNode { | ||||
|         this.reactNode = this.onRender(this); | ||||
|         return this.reactNode; | ||||
|     }; | ||||
| 
 | ||||
|     public close() { | ||||
|         return this.controller.closePopup(this); | ||||
|     } | ||||
| 
 | ||||
|     public constructor(controller: PopupController, id: string) { | ||||
|         this.controller = controller; | ||||
|         this.id = id; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| interface IPopupControllerEvent { | ||||
|     popupChange: void; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 弹窗模型 | ||||
|  */ | ||||
| class PopupController extends Emitter<IPopupControllerEvent> { | ||||
| 
 | ||||
|     /** | ||||
|      * ID 序列号 | ||||
|      */ | ||||
|     private idIndex = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * 最小弹窗 Index | ||||
|      */ | ||||
|     public zIndex = 100; | ||||
| 
 | ||||
|     /** | ||||
|      * 弹窗列表 | ||||
|      */ | ||||
|     public popups: Popup[] = []; | ||||
| 
 | ||||
|     /** | ||||
|      * 排序并重置序号 | ||||
|      */ | ||||
|     public sortPopup() { | ||||
|         this.popups = this.popups.sort((a, b) => a.index - b.index); | ||||
|         this.popups = this.popups.map((popup, index) => { | ||||
|             popup.index = (index + 1); | ||||
|             return popup; | ||||
|         }); | ||||
|         this.emit("popupChange"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 实例化并开启一个弹窗 | ||||
|      */ | ||||
|     public showPopup<P extends IPopupConstructor>(popup?: P): Popup { | ||||
|         let newPopup = new (popup ?? Popup)(this, `P-${this.idIndex ++}`); | ||||
|         this.popups.push(newPopup); | ||||
|         this.sortPopup(); | ||||
|         return newPopup; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 关闭一个弹窗 | ||||
|      */ | ||||
|     public closePopup(popup: Popup | string): Popup | undefined { | ||||
|         let id: string; | ||||
|         if (popup instanceof Popup) { | ||||
|             id = popup.id; | ||||
|         } else { | ||||
|             id = popup; | ||||
|         } | ||||
|         let closePopup: Popup | undefined; | ||||
|         this.popups = this.popups.filter( | ||||
|             currentPopup => { | ||||
|                 let isDelete = currentPopup.id === id; | ||||
|                 if (isDelete) { | ||||
|                     closePopup = currentPopup; | ||||
|                     currentPopup.isClose = true; | ||||
|                     return false; | ||||
|                 } else { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|         if (closePopup) { | ||||
|             this.sortPopup(); | ||||
|             this.emit("popupChange"); | ||||
|         } | ||||
|         return closePopup; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { Popup, PopupController } | ||||
| @ -1,7 +1,6 @@ | ||||
| import { createContext } from "react"; | ||||
| import { superConnect, superConnectWithEvent } from "./Context"; | ||||
| import { createContext, Component, FunctionComponent } from "react"; | ||||
| import { Emitter } from "@Model/Emitter"; | ||||
| import { Layout } from "./Layout"; | ||||
| import { Layout } from "@Model/Layout"; | ||||
| 
 | ||||
| /** | ||||
|  * 主题模式 | ||||
| @ -13,11 +12,9 @@ enum Themes { | ||||
| 
 | ||||
| type Language = "ZH_CN" | "EN_US"; | ||||
| 
 | ||||
| interface ISettingEvents extends Setting { | ||||
|     attrChange: keyof Setting; | ||||
| } | ||||
| 
 | ||||
| class Setting extends Emitter<ISettingEvents> { | ||||
| class Setting extends Emitter< | ||||
|     Setting & {change: keyof Setting} | ||||
| > { | ||||
| 
 | ||||
|     /** | ||||
|      * 主题 | ||||
| @ -39,7 +36,7 @@ class Setting extends Emitter<ISettingEvents> { | ||||
|      */ | ||||
|     public setProps<P extends keyof Setting>(key: P, value: Setting[P]) { | ||||
|         this[key] = value as any; | ||||
|         this.emit("attrChange", key); | ||||
|         this.emit("change", key); | ||||
|         this.emit(key as any, value as any); | ||||
|     } | ||||
| } | ||||
| @ -54,14 +51,21 @@ SettingContext.displayName = "Setting"; | ||||
| const SettingProvider = SettingContext.Provider; | ||||
| const SettingConsumer = SettingContext.Consumer; | ||||
| 
 | ||||
| type RenderComponent = (new (...p: any) => Component<any, any, any>) | FunctionComponent<any>; | ||||
| 
 | ||||
| /** | ||||
|  * 修饰器 | ||||
|  */ | ||||
| const useSetting = superConnect<Setting>(SettingConsumer, "setting"); | ||||
| 
 | ||||
| const useSettingWithEvent = superConnectWithEvent<Setting, ISettingEvents>(SettingConsumer, "setting"); | ||||
| function useSetting<R extends RenderComponent>(components: R): R { | ||||
|     return ((props: any) => { | ||||
|         const C = components; | ||||
|         return <SettingConsumer> | ||||
|             {(setting: Setting) => <C {...props} setting={setting}></C>} | ||||
|         </SettingConsumer> | ||||
|     }) as any; | ||||
| } | ||||
| 
 | ||||
| export { | ||||
|     Themes, Setting, SettingContext, useSetting, Language, useSettingWithEvent, | ||||
|     Themes, Setting, SettingContext, useSetting, Language, | ||||
|     IMixinSettingProps, SettingProvider, SettingConsumer | ||||
| }; | ||||
| @ -1,4 +1,4 @@ | ||||
| import { createContext } from "react"; | ||||
| import { createContext, Component, FunctionComponent, useState, useEffect, ReactNode } from "react"; | ||||
| import { Emitter } from "@Model/Emitter"; | ||||
| import { Model, ObjectID } from "@Model/Model"; | ||||
| import { Label } from "@Model/Label"; | ||||
| @ -9,8 +9,6 @@ import { AbstractRenderer } from "@Model/Renderer"; | ||||
| import { ClassicRenderer, MouseMod } from "@GLRender/ClassicRenderer"; | ||||
| import { Setting } from "./Setting"; | ||||
| import { I18N } from "@Component/Localization/Localization"; | ||||
| import { superConnectWithEvent, superConnect } from "./Context"; | ||||
| import { PopupController } from "./Popups"; | ||||
| 
 | ||||
| function randomColor(unNormal: boolean = false) { | ||||
|     const color = [ | ||||
| @ -40,7 +38,6 @@ interface IStatusEvent { | ||||
|     labelAttrChange: void; | ||||
|     groupAttrChange: void; | ||||
|     individualChange: void; | ||||
|     popupChange: void; | ||||
| } | ||||
| 
 | ||||
| class Status extends Emitter<IStatusEvent> { | ||||
| @ -68,11 +65,6 @@ class Status extends Emitter<IStatusEvent> { | ||||
|      */ | ||||
|     public model: Model = new Model(); | ||||
| 
 | ||||
|     /** | ||||
|      * 弹窗 | ||||
|      */ | ||||
|     public popup: PopupController = new PopupController(); | ||||
| 
 | ||||
|     /** | ||||
|      * 焦点对象 | ||||
|      */ | ||||
| @ -103,9 +95,6 @@ class Status extends Emitter<IStatusEvent> { | ||||
|         this.model.on("objectChange", () => this.emit("objectChange")); | ||||
|         this.model.on("labelChange", () => this.emit("labelChange")); | ||||
| 
 | ||||
|         // 弹窗事件
 | ||||
|         this.popup.on("popupChange", () => this.emit("popupChange")); | ||||
| 
 | ||||
|         // 对象变换时执行渲染,更新渲染器数据
 | ||||
|         this.on("objectChange", this.delayDraw); | ||||
|         this.model.on("individualChange", this.delayDraw); | ||||
| @ -269,12 +258,67 @@ StatusContext.displayName = "Status"; | ||||
| const StatusProvider = StatusContext.Provider; | ||||
| const StatusConsumer = StatusContext.Consumer; | ||||
| 
 | ||||
| type RenderComponent = (new (...p: any) => Component<any, any, any>) | FunctionComponent<any>; | ||||
| 
 | ||||
| /** | ||||
|  * 修饰器 | ||||
|  */ | ||||
| const useStatus = superConnect<Status>(StatusConsumer, "status"); | ||||
| function useStatus<R extends RenderComponent>(components: R): R { | ||||
|     return ((props: any) => { | ||||
|         const C = components; | ||||
|         return <StatusConsumer> | ||||
|             {(status: Status) => <C {...props} status={status}></C>} | ||||
|         </StatusConsumer> | ||||
|     }) as any; | ||||
| } | ||||
| 
 | ||||
| const useStatusWithEvent = superConnectWithEvent<Status, IStatusEvent>(StatusConsumer, "status"); | ||||
| function useStatusWithEvent(...events: Array<keyof IStatusEvent>) { | ||||
|     return <R extends RenderComponent>(components: R): R => { | ||||
|         const C = components as any; | ||||
|         return class extends Component<R> { | ||||
| 
 | ||||
|             private status: Status | undefined; | ||||
|             private isEventMount: boolean = false; | ||||
| 
 | ||||
|             private handelChange = () => { | ||||
|                 this.forceUpdate(); | ||||
|             } | ||||
| 
 | ||||
|             private mountEvent() { | ||||
|                 if (this.status && !this.isEventMount) { | ||||
|                     this.isEventMount = true; | ||||
|                     console.log("Component dep event mount: " + events.join(", ")); | ||||
|                     for (let i = 0; i < events.length; i++) { | ||||
|                         this.status.on(events[i], this.handelChange); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             private unmountEvent() { | ||||
|                 if (this.status) { | ||||
|                     for (let i = 0; i < events.length; i++) { | ||||
|                         this.status.off(events[i], this.handelChange); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             public render(): ReactNode { | ||||
|                 return <StatusConsumer> | ||||
|                     {(status: Status) => { | ||||
|                         this.status = status; | ||||
|                         this.mountEvent(); | ||||
|                         return <C {...this.props} status={status}></C>; | ||||
|                     }} | ||||
|                 </StatusConsumer> | ||||
|             } | ||||
| 
 | ||||
|             public componentWillUnmount() { | ||||
|                 this.unmountEvent(); | ||||
|             } | ||||
| 
 | ||||
|         } as any; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { | ||||
|     Status, StatusContext, useStatus, useStatusWithEvent, | ||||
|  | ||||
| @ -44,7 +44,6 @@ const EN_US = { | ||||
|     "Panel.Info.Label.Details.View": "Edit view label attributes", | ||||
|     "Panel.Title.Group.Details.View": "Group", | ||||
|     "Panel.Info.Group.Details.View": "Edit view group attributes", | ||||
|     "Popup.Title.Unnamed": "Popup message", | ||||
|     "Build.In.Label.Name.All.Group": "All group", | ||||
|     "Build.In.Label.Name.All.Range": "All range", | ||||
|     "Common.No.Data": "No Data", | ||||
| @ -52,7 +51,6 @@ const EN_US = { | ||||
|     "Common.Attr.Title.Basic": "Basic properties", | ||||
|     "Common.Attr.Title.Spatial": "Spatial property", | ||||
|     "Common.Attr.Title.Individual.Generation": "Individual generation", | ||||
|     "Common.Attr.Title.Individual.kill": "Individual kill", | ||||
|     "Common.Attr.Key.Display.Name": "Display name", | ||||
|     "Common.Attr.Key.Position.X": "Position X", | ||||
|     "Common.Attr.Key.Position.Y": "Position Y", | ||||
| @ -81,8 +79,6 @@ const EN_US = { | ||||
|     "Common.Attr.Key.Generation.Error.Empty.Range.List": "The specified label does not contain any scope objects", | ||||
|     "Common.Attr.Key.Generation.Error.Invalid.Range": "The specified scope object is invalid", | ||||
|     "Common.Attr.Key.Generation.Error.Invalid.Label": "The specified label has expired", | ||||
|     "Common.Attr.Key.Kill.Random": "Random kill", | ||||
|     "Common.Attr.Key.Kill.Count": "Kill count", | ||||
|     "Panel.Info.Range.Details.Attr.Error.Not.Range": "Object is not a Range", | ||||
|     "Panel.Info.Range.Details.Attr.Error.Unspecified": "Unspecified range object", | ||||
|     "Panel.Info.Group.Details.Attr.Error.Not.Group": "Object is not a Group", | ||||
|  | ||||
| @ -44,15 +44,13 @@ const ZH_CN = { | ||||
|     "Panel.Info.Label.Details.View": "编辑查看标签属性", | ||||
|     "Panel.Title.Group.Details.View": "群", | ||||
|     "Panel.Info.Group.Details.View": "编辑查看群属性", | ||||
|     "Popup.Title.Unnamed": "弹窗消息", | ||||
|     "Build.In.Label.Name.All.Group": "全部群", | ||||
|     "Build.In.Label.Name.All.Range": "全部范围", | ||||
|     "Common.No.Data": "暂无数据", | ||||
|     "Common.No.Unknown.Error": "未知错误", | ||||
|     "Common.Attr.Title.Basic": "基础属性", | ||||
|     "Common.Attr.Title.Spatial": "空间属性", | ||||
|     "Common.Attr.Title.Individual.Generation": "生成个体", | ||||
|     "Common.Attr.Title.Individual.kill": "消除个体", | ||||
|     "Common.Attr.Title.Individual.Generation": "个体生成", | ||||
|     "Common.Attr.Key.Display.Name": "显示名称", | ||||
|     "Common.Attr.Key.Position.X": "X 坐标", | ||||
|     "Common.Attr.Key.Position.Y": "Y 坐标", | ||||
| @ -81,8 +79,6 @@ const ZH_CN = { | ||||
|     "Common.Attr.Key.Generation.Error.Empty.Range.List": "指定的标签中没有包含任何范围对象", | ||||
|     "Common.Attr.Key.Generation.Error.Invalid.Range": "指定的范围对象已失效", | ||||
|     "Common.Attr.Key.Generation.Error.Invalid.Label": "指定的标签已失效", | ||||
|     "Common.Attr.Key.Kill.Random": "随机消除", | ||||
|     "Common.Attr.Key.Kill.Count": "消除数量", | ||||
|     "Panel.Info.Range.Details.Attr.Error.Not.Range": "对象不是一个范围", | ||||
|     "Panel.Info.Range.Details.Attr.Error.Unspecified": "未指定范围对象", | ||||
|     "Panel.Info.Group.Details.Attr.Error.Not.Group": "对象不是一个群", | ||||
|  | ||||
| @ -49,11 +49,6 @@ class Group extends CtrlObject { | ||||
|      */ | ||||
|     public genErrorMessageShowCount: number = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * 删除个数 | ||||
|      */ | ||||
|     public killCount: number = 1; | ||||
| 
 | ||||
|     private genInSingleRange(count: number, range: Range) { | ||||
|         for (let i = 0; i < count; i++) { | ||||
|             let individual = new Individual(this); | ||||
| @ -193,42 +188,6 @@ class Group extends CtrlObject { | ||||
|         return success; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 随机杀死个体 | ||||
|      */ | ||||
|     public killIndividuals(): boolean { | ||||
|         let success = false; | ||||
|         let killCount = this.killCount; | ||||
|         if (killCount > this.individuals.size) { | ||||
|             killCount = this.individuals.size; | ||||
|         } | ||||
| 
 | ||||
|         // 生成索引数组
 | ||||
|         const allIndex = new Array(this.individuals.size).fill(0).map((_, i) => i); | ||||
|         const deleteIndex: Set<number> = new Set(); | ||||
| 
 | ||||
|         for (let i = 0; i < killCount; i++) { | ||||
|             let randomIndex = Math.floor(Math.random() * allIndex.length); | ||||
|             deleteIndex.add(allIndex[randomIndex]); | ||||
|             allIndex.splice(randomIndex, 1); | ||||
|         } | ||||
| 
 | ||||
|         let j = 0; | ||||
|         this.individuals.forEach((individual) => { | ||||
|             if (deleteIndex.has(j)) { | ||||
|                 this.remove(individual); | ||||
|                 success = true; | ||||
|             } | ||||
|             j++; | ||||
|         }); | ||||
| 
 | ||||
|         if (success) { | ||||
|             this.model.emit("individualChange", this); | ||||
|         } | ||||
| 
 | ||||
|         return success | ||||
|     } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 创建个体 | ||||
|      * @param count 创建数量 | ||||
|  | ||||
| @ -46,7 +46,7 @@ class Label { | ||||
|      * 判断是否为相同标签 | ||||
|      */ | ||||
|     public equal(label: Label): boolean { | ||||
|         // if (this.isDeleted() || label.isDeleted()) return false;
 | ||||
|         if (this.isDeleted() || label.isDeleted()) return false; | ||||
|         return this === label || this.id === label.id; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Emitter } from "@Model/Emitter"; | ||||
| import { Emitter } from "./Emitter"; | ||||
| 
 | ||||
| enum LayoutDirection { | ||||
| 	X = 1, | ||||
| @ -7,10 +7,9 @@ import { StatusProvider, Status } from "@Context/Status"; | ||||
| import { ClassicRenderer } from "@GLRender/ClassicRenderer"; | ||||
| import { initializeIcons } from '@fluentui/font-icons-mdl2'; | ||||
| import { RootContainer } from "@Component/Container/RootContainer"; | ||||
| import { LayoutDirection } from "@Context/Layout"; | ||||
| import { CommandBar } from "@Component/CommandBar/CommandBar"; | ||||
| import { Popup } from "@Component/Popup/Popup"; | ||||
| import { LayoutDirection } from "@Model/Layout"; | ||||
| import "./SimulatorWeb.scss"; | ||||
| import { CommandBar } from "@Component/CommandBar/CommandBar"; | ||||
| 
 | ||||
| initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/"); | ||||
| 
 | ||||
| @ -102,7 +101,6 @@ class SimulatorWeb extends Component { | ||||
|             backgroundLevel={BackgroundLevel.Level5} | ||||
|             fontLevel={FontLevel.Level3} | ||||
|         > | ||||
|             <Popup/> | ||||
|             <HeaderBar height={45}/> | ||||
|             <div className="app-root-space" style={{ | ||||
|                 height: `calc( 100% - ${45}px)` | ||||
|  | ||||
| @ -136,24 +136,6 @@ class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps> { | ||||
| 				}} | ||||
| 			/> | ||||
| 
 | ||||
|             <Message i18nKey="Common.Attr.Title.Individual.kill" isTitle/> | ||||
| 
 | ||||
|             <AttrInput | ||||
|                 id={group.id} isNumber={true} step={1} keyI18n="Common.Attr.Key.Kill.Count" | ||||
|                 value={group.killCount} min={1} max={1000} | ||||
|                 valueChange={(val) => { | ||||
|                     this.props.status?.changeGroupAttrib(group.id, "killCount", (val as any) / 1); | ||||
|                 }} | ||||
|             /> | ||||
| 
 | ||||
|             <TogglesInput | ||||
| 				keyI18n="Common.Attr.Key.Generation" | ||||
| 				onIconName="RemoveFilter" offIconName="RemoveFilter" | ||||
| 				valueChange={() => { | ||||
| 					group.killIndividuals() | ||||
| 				}} | ||||
| 			/> | ||||
| 
 | ||||
| 		</> | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -31,12 +31,6 @@ | ||||
|             ], | ||||
|             "@Component/*": [ | ||||
|                 "./source/Component/*" | ||||
|             ], | ||||
|             "@Localization/*": [ | ||||
|                 "./source/Localization/*" | ||||
|             ], | ||||
|             "@Panel/*": [ | ||||
|                 "./source/Panel/*" | ||||
|             ] | ||||
|         } | ||||
|     }, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user