Compare commits
	
		
			3 Commits
		
	
	
		
			04060902e0
			...
			90398b593e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 90398b593e | |||
| 93e1a8d3ef | |||
| 3357960f61 | 
| @ -68,7 +68,13 @@ 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" })} | ||||
|                 {this.getRenderButton({ | ||||
|                     iconName: "Settings", | ||||
|                     i18NKey: "Command.Bar.Setting.Info", | ||||
|                     click: () => { | ||||
|                         this.props.status ? this.props.status.popup.showPopup() : undefined; | ||||
|                     } | ||||
|                 })} | ||||
|             </div> | ||||
|         </Theme> | ||||
|     } | ||||
|  | ||||
| @ -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 { useStatusWithEvent, IMixinSettingProps, Themes, Language } from "@Context/Setting"; | ||||
| import { useSettingWithEvent, 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 = useStatusWithEvent("language", "themes")(MessageView); | ||||
| const Message = useSettingWithEvent("language", "themes")(MessageView); | ||||
| export { Message }; | ||||
							
								
								
									
										71
									
								
								source/Component/Popup/Popup.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								source/Component/Popup/Popup.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| @import "../Theme/Theme.scss"; | ||||
| 
 | ||||
| @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: 32px; | ||||
|         max-height: 32px; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| div.popup-layer.dark { | ||||
| 
 | ||||
|     div.popup-layer-header { | ||||
|         background-color: $lt-bg-color-lvl3-dark; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| div.popup-layer.light { | ||||
| 
 | ||||
|     div.popup-layer-header { | ||||
|         background-color: $lt-bg-color-lvl3-light; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| div.dark.popup-mask { | ||||
|     background-color: rgba(0, 0, 0, 0.55); | ||||
| } | ||||
| 
 | ||||
| div.light.popup-mask { | ||||
|     background-color: rgba(0, 0, 0, 0.15); | ||||
| } | ||||
							
								
								
									
										97
									
								
								source/Component/Popup/Popup.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								source/Component/Popup/Popup.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| 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 "./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 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" | ||||
|         > | ||||
|             <div className="popup-layer-header"> | ||||
|                  | ||||
|             </div> | ||||
|         </Theme> | ||||
|     } | ||||
| 
 | ||||
|     public render(): ReactNode { | ||||
|         return <> | ||||
|             {this.renderRootMask()} | ||||
|             {this.renderMaskList()} | ||||
|             {this.props.status?.popup.popups.map((popup) => { | ||||
|                 return this.renderLayer(popup); | ||||
|             })} | ||||
|         </>; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { Popup }; | ||||
| @ -8,11 +8,33 @@ 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; | ||||
| 
 | ||||
|     /** | ||||
|      * 唯一标识符 | ||||
|      */ | ||||
| @ -36,18 +58,29 @@ class Popup { | ||||
|     /** | ||||
|      * 渲染函数 | ||||
|      */ | ||||
|     public rendererFunction: undefined | ((p: Popup) => ReactNode); | ||||
|     public onRender(p: Popup): ReactNode { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 关闭回调 | ||||
|      */ | ||||
|     public onClose(): void { | ||||
|         this.close(); | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * 渲染节点 | ||||
|      */ | ||||
|     public render(): ReactNode { | ||||
|         if (this.rendererFunction) { | ||||
|             this.reactNode = this.rendererFunction(this); | ||||
|         } | ||||
|         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; | ||||
| @ -68,6 +101,11 @@ class PopupController extends Emitter<IPopupControllerEvent> { | ||||
|      */ | ||||
|     private idIndex = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * 最小弹窗 Index | ||||
|      */ | ||||
|     public zIndex = 100; | ||||
| 
 | ||||
|     /** | ||||
|      * 弹窗列表 | ||||
|      */ | ||||
| @ -82,6 +120,7 @@ class PopupController extends Emitter<IPopupControllerEvent> { | ||||
|             popup.index = (index + 1); | ||||
|             return popup; | ||||
|         }); | ||||
|         this.emit("popupChange"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -90,7 +129,7 @@ class PopupController extends Emitter<IPopupControllerEvent> { | ||||
|     public showPopup<P extends IPopupConstructor>(popup?: P): Popup { | ||||
|         let newPopup = new (popup ?? Popup)(this, `P-${this.idIndex ++}`); | ||||
|         this.popups.push(newPopup); | ||||
|         this.emit("popupChange"); | ||||
|         this.sortPopup(); | ||||
|         return newPopup; | ||||
|     } | ||||
| 
 | ||||
| @ -110,6 +149,7 @@ class PopupController extends Emitter<IPopupControllerEvent> { | ||||
|                 let isDelete = currentPopup.id === id; | ||||
|                 if (isDelete) { | ||||
|                     closePopup = currentPopup; | ||||
|                     currentPopup.isClose = true; | ||||
|                     return false; | ||||
|                 } else { | ||||
|                     return true; | ||||
| @ -117,6 +157,7 @@ class PopupController extends Emitter<IPopupControllerEvent> { | ||||
|             } | ||||
|         ); | ||||
|         if (closePopup) { | ||||
|             this.sortPopup(); | ||||
|             this.emit("popupChange"); | ||||
|         } | ||||
|         return closePopup; | ||||
|  | ||||
| @ -14,7 +14,7 @@ enum Themes { | ||||
| type Language = "ZH_CN" | "EN_US"; | ||||
| 
 | ||||
| interface ISettingEvents extends Setting { | ||||
|     change: keyof Setting; | ||||
|     attrChange: keyof Setting; | ||||
| } | ||||
| 
 | ||||
| class Setting extends Emitter<ISettingEvents> { | ||||
| @ -39,7 +39,7 @@ class Setting extends Emitter<ISettingEvents> { | ||||
|      */ | ||||
|     public setProps<P extends keyof Setting>(key: P, value: Setting[P]) { | ||||
|         this[key] = value as any; | ||||
|         this.emit("change", key); | ||||
|         this.emit("attrChange", key); | ||||
|         this.emit(key as any, value as any); | ||||
|     } | ||||
| } | ||||
| @ -59,9 +59,9 @@ const SettingConsumer = SettingContext.Consumer; | ||||
|  */ | ||||
| const useSetting = superConnect<Setting>(SettingConsumer, "setting"); | ||||
| 
 | ||||
| const useStatusWithEvent = superConnectWithEvent<Setting, ISettingEvents>(SettingConsumer, "setting"); | ||||
| const useSettingWithEvent = superConnectWithEvent<Setting, ISettingEvents>(SettingConsumer, "setting"); | ||||
| 
 | ||||
| export { | ||||
|     Themes, Setting, SettingContext, useSetting, Language, useStatusWithEvent, | ||||
|     Themes, Setting, SettingContext, useSetting, Language, useSettingWithEvent, | ||||
|     IMixinSettingProps, SettingProvider, SettingConsumer | ||||
| }; | ||||
| @ -1,4 +1,4 @@ | ||||
| import { createContext, Component, FunctionComponent, ReactNode } from "react"; | ||||
| import { createContext } from "react"; | ||||
| import { Emitter } from "@Model/Emitter"; | ||||
| import { Model, ObjectID } from "@Model/Model"; | ||||
| import { Label } from "@Model/Label"; | ||||
| @ -10,6 +10,7 @@ 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 = [ | ||||
| @ -39,6 +40,7 @@ interface IStatusEvent { | ||||
|     labelAttrChange: void; | ||||
|     groupAttrChange: void; | ||||
|     individualChange: void; | ||||
|     popupChange: void; | ||||
| } | ||||
| 
 | ||||
| class Status extends Emitter<IStatusEvent> { | ||||
| @ -66,6 +68,11 @@ class Status extends Emitter<IStatusEvent> { | ||||
|      */ | ||||
|     public model: Model = new Model(); | ||||
| 
 | ||||
|     /** | ||||
|      * 弹窗 | ||||
|      */ | ||||
|     public popup: PopupController = new PopupController(); | ||||
| 
 | ||||
|     /** | ||||
|      * 焦点对象 | ||||
|      */ | ||||
| @ -96,6 +103,9 @@ 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); | ||||
|  | ||||
| @ -9,6 +9,7 @@ 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 "./SimulatorWeb.scss"; | ||||
| 
 | ||||
| initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/"); | ||||
| @ -101,6 +102,7 @@ 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)` | ||||
|  | ||||
| @ -31,6 +31,12 @@ | ||||
|             ], | ||||
|             "@Component/*": [ | ||||
|                 "./source/Component/*" | ||||
|             ], | ||||
|             "@Localization/*": [ | ||||
|                 "./source/Localization/*" | ||||
|             ], | ||||
|             "@Panel/*": [ | ||||
|                 "./source/Panel/*" | ||||
|             ] | ||||
|         } | ||||
|     }, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user