From 89ae499d4bb44666bca40466279efab60f10818d Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 23 Mar 2022 17:44:37 +0800 Subject: [PATCH 1/4] Add confirm popup component --- .../Component/ConfirmPopup/ConfirmPopup.scss | 0 .../Component/ConfirmPopup/ConfirmPopup.tsx | 8 ++ source/Component/Popup/Popup.scss | 15 ++++ source/Component/Popup/Popup.tsx | 81 ++++++++++++++++--- source/Context/Popups.ts | 44 +++++++--- 5 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 source/Component/ConfirmPopup/ConfirmPopup.scss create mode 100644 source/Component/ConfirmPopup/ConfirmPopup.tsx diff --git a/source/Component/ConfirmPopup/ConfirmPopup.scss b/source/Component/ConfirmPopup/ConfirmPopup.scss new file mode 100644 index 0000000..e69de29 diff --git a/source/Component/ConfirmPopup/ConfirmPopup.tsx b/source/Component/ConfirmPopup/ConfirmPopup.tsx new file mode 100644 index 0000000..4f378d7 --- /dev/null +++ b/source/Component/ConfirmPopup/ConfirmPopup.tsx @@ -0,0 +1,8 @@ +import { Popup } from "@Context/Popups"; +import "./ConfirmPopup.scss"; + +class ConfirmPopup extends Popup { + +} + +export { ConfirmPopup } \ No newline at end of file diff --git a/source/Component/Popup/Popup.scss b/source/Component/Popup/Popup.scss index a39b8d6..a588765 100644 --- a/source/Component/Popup/Popup.scss +++ b/source/Component/Popup/Popup.scss @@ -39,10 +39,17 @@ div.popup-mask { height: 100%; } +div.focus.popup-layer { + border: 0.8px solid #514feb; +} + div.popup-layer { position: absolute; border-radius: 3px; overflow: hidden; + transition: none; + box-sizing: border-box; + border: 0.8px solid transparent; div.popup-layer-header { min-height: $header-height; @@ -79,9 +86,16 @@ div.popup-layer { cursor: pointer; } } + + div.popup-layer-content { + height: calc( 100% - 32px ); + width: 100%; + overflow: hidden; + } } div.popup-layer.dark { + box-shadow: 0 0 15px rgba(0, 0, 0, 0.3); div.popup-layer-header { background-color: $lt-bg-color-lvl3-dark; @@ -94,6 +108,7 @@ div.popup-layer.dark { } div.popup-layer.light { + box-shadow: 0 0 15px rgba(0, 0, 0, 0.15); div.popup-layer-header { background-color: $lt-bg-color-lvl3-light; diff --git a/source/Component/Popup/Popup.tsx b/source/Component/Popup/Popup.tsx index 2bfaefe..1b88b8c 100644 --- a/source/Component/Popup/Popup.tsx +++ b/source/Component/Popup/Popup.tsx @@ -10,7 +10,7 @@ interface IPopupProps {} @useStatusWithEvent("popupChange") class Popup extends Component { - public renderMask(index?: number, click?: () => void, key?: string): ReactNode { + private renderMask(index?: number, click?: () => void, key?: string): ReactNode { const classList: string[] = ["popup-mask", "show-fade"]; return { /> } - public renderRootMask(): ReactNode { + private renderRootMask(): ReactNode { if (this.props.status) { const needMask = this.props.status.popup.popups.some(popup => popup.needMask); if (!needMask) return null; @@ -38,7 +38,7 @@ class Popup extends Component { } } - public renderMaskList(): ReactNode { + private renderMaskList(): ReactNode { if (this.props.status) { return this.props.status.popup.popups .filter((popup) => { @@ -60,9 +60,19 @@ class Popup extends Component { } } - public renderHeader(popup: PopupModel): ReactNode { + private renderHeader(popup: PopupModel): ReactNode { return
-
+
{ + popup.isOnMouseDown = true; + popup.lastMouseLeft = e.clientX; + popup.lastMouseTop = e.clientY; + }} + onMouseUp={() => { + popup.isOnMouseDown = false; + }} + > {popup.onRenderHeader()}
{
} - public renderLayer(popup: PopupModel) { + private renderContent(popup: PopupModel) { + return
+ {popup.render()} +
+ } + + private 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; + if (isNaN(popup.top)) { + popup.top = (pageHeight - popup.height) / 2; + } + if (isNaN(popup.left)) { + popup.left = (pageWidth - popup.width) / 2; + } return {this.renderHeader(popup)} + {this.renderContent(popup)} } + private isMouseDown: boolean = false; + + private handelMouseDown = () => { + this.isMouseDown = true; + } + + private handelMouseUp = () => { + this.isMouseDown = false; + } + + private handelMouseMove = (e: MouseEvent) => { + if ( + this.isMouseDown && + this.props.status && + this.props.status.popup.popups.some(popup => popup.isOnMouseDown) + ) { + this.props.status.popup.popups.forEach((popup) => { + if (popup.isOnMouseDown) { + popup.top += e.clientY - popup.lastMouseTop; + popup.left += e.clientX - popup.lastMouseLeft; + popup.lastMouseLeft = e.clientX; + popup.lastMouseTop = e.clientY; + this.forceUpdate(); + } + }); + } + } + + public componentDidMount() { + window.addEventListener("mousemove", this.handelMouseMove); + window.addEventListener("mousedown", this.handelMouseDown); + window.addEventListener("mouseup", this.handelMouseUp); + } + + public componentWillUnmount() { + window.removeEventListener("mousemove", this.handelMouseMove); + window.removeEventListener("mousedown", this.handelMouseDown); + window.removeEventListener("mouseup", this.handelMouseUp); + } + public render(): ReactNode { return <> {this.renderRootMask()} diff --git a/source/Context/Popups.ts b/source/Context/Popups.ts index 0b75ccd..2e74ea0 100644 --- a/source/Context/Popups.ts +++ b/source/Context/Popups.ts @@ -2,12 +2,16 @@ 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 { +class Popup

{ + + public props: P; + + public constructor(props: P) { + this.props = props; + } public zIndex() { return this.index * 2 + this.controller.zIndex; @@ -17,9 +21,15 @@ class Popup { public height: number = 200; - public top: number = 0; + public top: number = NaN; - public left: number = 0; + public left: number = NaN; + + public lastMouseTop: number = 0; + + public lastMouseLeft: number = 0; + + public isOnMouseDown: boolean = false; /** * 是否关闭 @@ -39,12 +49,12 @@ class Popup { /** * 唯一标识符 */ - public id: string; + public id: string = ""; /** * 控制器 */ - public controller: PopupController; + public controller: PopupController = undefined as any; /** * 渲染层级 @@ -67,7 +77,7 @@ class Popup { * 渲染函数 */ public onRender(p: Popup): ReactNode { - return null; + return undefined; } /** @@ -81,15 +91,15 @@ class Popup { * 渲染节点 */ public render(): ReactNode { - this.reactNode = this.onRender(this); + this.reactNode = this.onRender(this) ?? this.reactNode; return this.reactNode; }; - public close() { + public close(): Popup | undefined { return this.controller.closePopup(this); } - public constructor(controller: PopupController, id: string) { + public init(controller: PopupController, id: string) { this.controller = controller; this.id = id; } @@ -134,8 +144,16 @@ class PopupController extends Emitter { /** * 实例化并开启一个弹窗 */ - public showPopup

(popup?: P): Popup { - let newPopup = new (popup ?? Popup)(this, `P-${this.idIndex ++}`); + public showPopup

>( + popup?: (new () => T) | Popup

, props?: P + ): Popup

{ + let newPopup: Popup; + if (popup instanceof Popup) { + newPopup = popup; + } else { + newPopup = new (popup ?? Popup)(props); + } + newPopup.init(this, `P-${this.idIndex ++}`); this.popups.push(newPopup); this.sortPopup(); return newPopup; From cc127696c522730cf29a79ae8b514352a4f0ac77 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 23 Mar 2022 23:51:43 +0800 Subject: [PATCH 2/4] Add confirm popup component --- source/Component/CommandBar/CommandBar.tsx | 3 +- .../Component/ConfirmPopup/ConfirmPopup.scss | 67 +++++++++++++++++++ .../Component/ConfirmPopup/ConfirmPopup.tsx | 28 +++++++- source/Component/Popup/Popup.scss | 4 +- source/Component/Popup/Popup.tsx | 36 +++++++--- source/Component/Theme/Theme.tsx | 46 +++++++------ source/Context/Popups.ts | 24 ++----- 7 files changed, 156 insertions(+), 52 deletions(-) diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index bd21180..e29e680 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -4,6 +4,7 @@ import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { AllI18nKeys } from "../Localization/Localization"; +import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import { Component, ReactNode } from "react"; import { MouseMod } from "@GLRender/ClassicRenderer"; import "./CommandBar.scss"; @@ -72,7 +73,7 @@ class CommandBar extends Component { - this.props.status ? this.props.status.popup.showPopup() : undefined; + this.props.status?.popup.showPopup(ConfirmPopup, {}); } })}

diff --git a/source/Component/ConfirmPopup/ConfirmPopup.scss b/source/Component/ConfirmPopup/ConfirmPopup.scss index e69de29..50c3869 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.scss +++ b/source/Component/ConfirmPopup/ConfirmPopup.scss @@ -0,0 +1,67 @@ +@import "../Theme/Theme.scss"; + +div.confirm-root { + width: 100%; + height: 100%; + + div.content-views { + width: 100%; + height: calc( 100% - 36px ); + box-sizing: border-box; + padding: 10px; + } + + div.action-view { + width: 100%; + height: 36px; + display: flex; + box-sizing: border-box; + padding-right: 5px; + padding-bottom: 10px; + justify-content: flex-end; + align-items: center; + + div.action-button { + height: 26px; + padding: 0 10px; + border-radius: 3px; + margin: 0 5px; + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + } + + div.action-button.yes-button:hover { + color: $lt-red; + } + } +} + +div.dark.confirm-root { + + div.action-view { + + div.action-button { + background-color: $lt-bg-color-lvl3-dark; + } + + div.action-button:hover { + background-color: $lt-bg-color-lvl2-dark; + } + } +} + +div.light.confirm-root { + + div.action-view { + + div.action-button { + background-color: $lt-bg-color-lvl3-light; + } + + div.action-button:hover { + background-color: $lt-bg-color-lvl2-light; + } + } +} \ No newline at end of file diff --git a/source/Component/ConfirmPopup/ConfirmPopup.tsx b/source/Component/ConfirmPopup/ConfirmPopup.tsx index 4f378d7..dbeec69 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.tsx +++ b/source/Component/ConfirmPopup/ConfirmPopup.tsx @@ -1,8 +1,34 @@ import { Popup } from "@Context/Popups"; +import { ReactNode } from "react"; +import { Message } from "@Component/Message/Message"; +import { Theme } from "@Component/Theme/Theme"; +import { Localization } from "@Component/Localization/Localization"; import "./ConfirmPopup.scss"; -class ConfirmPopup extends Popup { +interface IConfirmPopupProps { + +} +class ConfirmPopup extends Popup { + public width: number = 300; + + public height: number = 180; + + public render(): ReactNode { + return +
+ +
+
+
+ +
+
+ +
+
+
; + } } export { ConfirmPopup } \ No newline at end of file diff --git a/source/Component/Popup/Popup.scss b/source/Component/Popup/Popup.scss index a588765..e37d254 100644 --- a/source/Component/Popup/Popup.scss +++ b/source/Component/Popup/Popup.scss @@ -66,7 +66,7 @@ div.popup-layer { user-select: none; span { - padding-left: 8px; + padding-left: 10px; display: inline-block; vertical-align: middle; white-space: nowrap; @@ -98,7 +98,6 @@ div.popup-layer.dark { box-shadow: 0 0 15px rgba(0, 0, 0, 0.3); div.popup-layer-header { - background-color: $lt-bg-color-lvl3-dark; div.header-close-icon:hover { background-color: $lt-bg-color-lvl2-dark; @@ -111,7 +110,6 @@ div.popup-layer.light { box-shadow: 0 0 15px rgba(0, 0, 0, 0.15); div.popup-layer-header { - background-color: $lt-bg-color-lvl3-light; div.header-close-icon:hover { background-color: $lt-bg-color-lvl2-light; diff --git a/source/Component/Popup/Popup.tsx b/source/Component/Popup/Popup.tsx index 1b88b8c..f37224a 100644 --- a/source/Component/Popup/Popup.tsx +++ b/source/Component/Popup/Popup.tsx @@ -1,18 +1,22 @@ import { Component, ReactNode } from "react"; import { IMixinStatusProps, useStatusWithEvent } from "@Context/Status"; -import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; +import { IMixinSettingProps, useSettingWithEvent } from "@Context/Setting"; +import { BackgroundLevel, FontLevel, getClassList, Theme } from "@Component/Theme/Theme"; import { Popup as PopupModel } from "@Context/Popups"; import { Icon } from "@fluentui/react"; import "./Popup.scss"; interface IPopupProps {} +@useSettingWithEvent("themes") @useStatusWithEvent("popupChange") -class Popup extends Component { +class Popup extends Component { private renderMask(index?: number, click?: () => void, key?: string): ReactNode { - const classList: string[] = ["popup-mask", "show-fade"]; - return { } private renderHeader(popup: PopupModel): ReactNode { - return
+ return
{ @@ -87,7 +97,13 @@ class Popup extends Component { } private renderContent(popup: PopupModel) { - return
+ return
{popup.render()}
} @@ -103,6 +119,7 @@ class Popup extends Component { } return { top: popup.top, left: popup.left }} - key={popup.id} - backgroundLevel={BackgroundLevel.Level4} - className="popup-layer show-scale" + className={getClassList({ + className: "popup-layer show-scale", + backgroundLevel: BackgroundLevel.Level4, + }, this.props.setting).join(" ")} > {this.renderHeader(popup)} {this.renderContent(popup)} diff --git a/source/Component/Theme/Theme.tsx b/source/Component/Theme/Theme.tsx index 20669ba..bea5f42 100644 --- a/source/Component/Theme/Theme.tsx +++ b/source/Component/Theme/Theme.tsx @@ -1,4 +1,4 @@ -import { useSetting, Themes, IMixinSettingProps } from "@Context/Setting"; +import { useSettingWithEvent, Themes, IMixinSettingProps, Setting } from "@Context/Setting"; import { Component, ReactNode, DetailedHTMLProps, HTMLAttributes } from "react"; import "./Theme.scss"; @@ -21,12 +21,33 @@ interface IThemeProps { className?: string; fontLevel?: FontLevel; backgroundLevel?: BackgroundLevel; -} +} + +function getClassList(props: IThemeProps, setting?: Setting) { + const classNameList: string[] = []; + + if (props.className) { + classNameList.push(props.className); + } + + const theme = setting ? setting.themes : Themes.dark; + classNameList.push(theme === Themes.light ? "light" : "dark"); + + if (props.fontLevel) { + classNameList.push(`font-${props.fontLevel}`); + } + + if (props.backgroundLevel) { + classNameList.push(`background-${props.backgroundLevel}`); + } + + return classNameList; +} /** * 主题切换 */ -@useSetting +@useSettingWithEvent("themes") class Theme extends Component< IThemeProps & IMixinSettingProps & DetailedHTMLProps< HTMLAttributes, HTMLDivElement @@ -52,22 +73,7 @@ class Theme extends Component< public render(): ReactNode { const setting = this.props.setting; - const classNameList: string[] = []; - - if (this.props.className) { - classNameList.push(this.props.className); - } - - const theme = setting ? setting.themes : Themes.dark; - classNameList.push(theme === Themes.light ? "light" : "dark"); - - if (this.props.fontLevel) { - classNameList.push(`font-${this.props.fontLevel}`); - } - - if (this.props.backgroundLevel) { - classNameList.push(`background-${this.props.backgroundLevel}`); - } + const classNameList = getClassList(this.props, setting); const propsObj = {...this.props}; delete propsObj.className; @@ -82,4 +88,4 @@ class Theme extends Component< } export default Theme; -export { Theme, FontLevel, BackgroundLevel }; \ No newline at end of file +export { Theme, FontLevel, BackgroundLevel, getClassList }; \ No newline at end of file diff --git a/source/Context/Popups.ts b/source/Context/Popups.ts index 2e74ea0..963d74b 100644 --- a/source/Context/Popups.ts +++ b/source/Context/Popups.ts @@ -1,11 +1,12 @@ import { ReactNode, createElement } from "react"; import { Emitter } from "@Model/Emitter"; import { Localization } from "@Component/Localization/Localization"; +import { IAnyObject } from "@Model/Renderer"; /** * 弹窗类型 */ -class Popup

{ +class Popup

{ public props: P; @@ -61,11 +62,6 @@ class Popup

{ */ public index: number = Infinity; - /** - * react 节点 - */ - public reactNode: ReactNode; - /** * 渲染标题 */ @@ -73,13 +69,6 @@ class Popup

{ return createElement(Localization, {i18nKey: "Popup.Title.Unnamed"}); } - /** - * 渲染函数 - */ - public onRender(p: Popup): ReactNode { - return undefined; - } - /** * 关闭回调 */ @@ -91,8 +80,7 @@ class Popup

{ * 渲染节点 */ public render(): ReactNode { - this.reactNode = this.onRender(this) ?? this.reactNode; - return this.reactNode; + return null; }; public close(): Popup | undefined { @@ -144,10 +132,10 @@ class PopupController extends Emitter { /** * 实例化并开启一个弹窗 */ - public showPopup

>( - popup?: (new () => T) | Popup

, props?: P + public showPopup

>( + popup: (new (props: P) => T) | Popup

, props: P ): Popup

{ - let newPopup: Popup; + let newPopup: Popup

; if (popup instanceof Popup) { newPopup = popup; } else { From 1f4c6feafab5c8e07c78199c931c268793bdeaee Mon Sep 17 00:00:00 2001 From: MrKBear Date: Thu, 24 Mar 2022 14:26:26 +0800 Subject: [PATCH 3/4] Popup layer add drag to resize function --- source/Component/Popup/Popup.scss | 52 ++++++- source/Component/Popup/Popup.tsx | 244 +++++++++++++++++++++++++++++- source/Context/Popups.ts | 28 +++- 3 files changed, 308 insertions(+), 16 deletions(-) diff --git a/source/Component/Popup/Popup.scss b/source/Component/Popup/Popup.scss index e37d254..39de7c4 100644 --- a/source/Component/Popup/Popup.scss +++ b/source/Component/Popup/Popup.scss @@ -46,11 +46,21 @@ div.focus.popup-layer { div.popup-layer { position: absolute; border-radius: 3px; - overflow: hidden; transition: none; box-sizing: border-box; border: 0.8px solid transparent; + div.popup-layer-container { + width: 100%; + height: 100%; + display: flex; + } + + div.popup-layer-root-content { + width: 100%; + height: 100%; + } + div.popup-layer-header { min-height: $header-height; max-height: $header-height; @@ -68,7 +78,7 @@ div.popup-layer { span { padding-left: 10px; display: inline-block; - vertical-align: middle; + vertical-align: bottom; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -92,6 +102,44 @@ div.popup-layer { width: 100%; overflow: hidden; } + + div.drag-line-root.drag-line-y { + flex-direction: column; + } + + div.drag-line-root { + display: flex; + justify-content: center; + align-items: center; + position: relative; + + div.drag-line { + transition: all 300ms ease-in-out; + display: flex; + } + + div.render-drag-block-root { + height: 0; + width: 0; + display: flex; + justify-content: center; + align-items: center; + + div.render-drag-block { + min-width: 5px; + min-height: 5px; + position: relative; + } + } + + div.drag-line:hover { + background-color: $lt-blue; + } + + div.drag-line.hover { + background-color: $lt-blue; + } + } } div.popup-layer.dark { diff --git a/source/Component/Popup/Popup.tsx b/source/Component/Popup/Popup.tsx index f37224a..8d9e0c5 100644 --- a/source/Component/Popup/Popup.tsx +++ b/source/Component/Popup/Popup.tsx @@ -2,7 +2,7 @@ import { Component, ReactNode } from "react"; import { IMixinStatusProps, useStatusWithEvent } from "@Context/Status"; import { IMixinSettingProps, useSettingWithEvent } from "@Context/Setting"; import { BackgroundLevel, FontLevel, getClassList, Theme } from "@Component/Theme/Theme"; -import { Popup as PopupModel } from "@Context/Popups"; +import { Popup as PopupModel, ResizeDragDirection } from "@Context/Popups"; import { Icon } from "@fluentui/react"; import "./Popup.scss"; @@ -79,9 +79,6 @@ class Popup extends Component { - popup.isOnMouseDown = false; - }} > {popup.onRenderHeader()}

@@ -108,6 +105,129 @@ class Popup extends Component } + private renderDragBlock(dir: ResizeDragDirection, popup: PopupModel) { + return
+
{ + popup.lastMouseLeft = e.clientX; + popup.lastMouseTop = e.clientY; + popup.resizeDragDirection = dir; + popup.isResizeMouseDown = true; + }} + onMouseEnter={() => { + popup.resizeHoverDirection = dir; + this.forceUpdate(); + }} + onMouseLeave={() => { + popup.resizeHoverDirection = undefined; + this.forceUpdate(); + }} + /> +
+ } + + private mapDirToCursor = new Map([ + [ResizeDragDirection.rightTop, "sw-resize"], + [ResizeDragDirection.rightBottom, "nw-resize"], + [ResizeDragDirection.leftBottom, "sw-resize"], + [ResizeDragDirection.LeftTop, "nw-resize"] + ]); + + private renderDragLine(dir: ResizeDragDirection, popup: PopupModel) { + let xy: boolean = false; + const dragLineCList: string[] = ["drag-line"]; + + if (dir === ResizeDragDirection.top || dir === ResizeDragDirection.bottom) { + xy = false; + } + if (dir === ResizeDragDirection.left || dir === ResizeDragDirection.right) { + xy = true; + } + if ( + ( + dir === ResizeDragDirection.top && + ( + popup.resizeHoverDirection === ResizeDragDirection.LeftTop || + popup.resizeHoverDirection === ResizeDragDirection.rightTop + ) + ) || + ( + dir === ResizeDragDirection.bottom && + ( + popup.resizeHoverDirection === ResizeDragDirection.leftBottom || + popup.resizeHoverDirection === ResizeDragDirection.rightBottom + ) + ) || + ( + dir === ResizeDragDirection.right && + ( + popup.resizeHoverDirection === ResizeDragDirection.rightTop || + popup.resizeHoverDirection === ResizeDragDirection.rightBottom + ) + ) || + ( + dir === ResizeDragDirection.left && + ( + popup.resizeHoverDirection === ResizeDragDirection.leftBottom || + popup.resizeHoverDirection === ResizeDragDirection.LeftTop + ) + ) + ) { + dragLineCList.push("hover") + } + + return
+ { + xy && dir === ResizeDragDirection.left ? this.renderDragBlock( + ResizeDragDirection.LeftTop, popup + ) : null + } + { + xy && dir === ResizeDragDirection.right ? this.renderDragBlock( + ResizeDragDirection.rightTop, popup + ) : null + } + { + !xy && dir === ResizeDragDirection.bottom ? this.renderDragBlock( + ResizeDragDirection.leftBottom, popup + ) : null + } +
{ + popup.lastMouseLeft = e.clientX; + popup.lastMouseTop = e.clientY; + popup.resizeDragDirection = dir; + popup.isResizeMouseDown = true; + }} + /> + { + !xy && dir === ResizeDragDirection.bottom ? this.renderDragBlock( + ResizeDragDirection.rightBottom, popup + ) : null + } +
+ } + private renderLayer(popup: PopupModel) { const pageWidth = document.documentElement.clientWidth; const pageHeight = document.documentElement.clientHeight; @@ -132,8 +252,16 @@ class Popup extends Component - {this.renderHeader(popup)} - {this.renderContent(popup)} + {this.renderDragLine(ResizeDragDirection.top, popup)} +
+ {this.renderDragLine(ResizeDragDirection.left, popup)} +
+ {this.renderHeader(popup)} + {this.renderContent(popup)} +
+ {this.renderDragLine(ResizeDragDirection.right, popup)} +
+ {this.renderDragLine(ResizeDragDirection.bottom, popup)} } @@ -145,9 +273,54 @@ class Popup extends Component { this.isMouseDown = false; + if (this.props.status) { + this.props.status.popup.popups.forEach((popup) => { + popup.isOnMouseDown = false; + popup.resizeDragDirection = undefined; + popup.isResizeMouseDown = false; + }); + } + } + + private resize(popup: PopupModel, dis: number, dir: boolean, lsk: boolean) { + + if (dir) { + // Y + popup.isResizeOverFlowY = false; + const heightBackup = popup.height + const topBackup = popup.top; + if (lsk) { + popup.height += dis; + } else { + popup.top += dis; + popup.height -= dis; + } + if (popup.height < popup.minHeight) { + popup.height = heightBackup; + popup.top = topBackup; + popup.isResizeOverFlowY = true; + } + } else { + // X + popup.isResizeOverFlowX = false; + const leftBackup = popup.left + const widthBackup = popup.width; + if (lsk) { + popup.width += dis; + } else { + popup.left += dis; + popup.width -= dis; + } + if (popup.width < popup.minWidth) { + popup.width = widthBackup; + popup.left = leftBackup; + popup.isResizeOverFlowX = true; + } + } } private handelMouseMove = (e: MouseEvent) => { + let isActionSuccess: boolean = false; if ( this.isMouseDown && this.props.status && @@ -159,10 +332,69 @@ class Popup extends Component { + if (popup.resizeDragDirection) { + + let moveX = e.clientX - popup.lastMouseLeft; + let moveY = e.clientY - popup.lastMouseTop; + switch (popup.resizeDragDirection) { + + case ResizeDragDirection.LeftTop: + this.resize(popup, moveX, false, false); + this.resize(popup, moveY, true, false); + break; + + case ResizeDragDirection.leftBottom: + this.resize(popup, moveX, false, false); + this.resize(popup, moveY, true, true); + break; + + case ResizeDragDirection.rightTop: + this.resize(popup, moveX, false, true); + this.resize(popup, moveY, true, false); + break; + + case ResizeDragDirection.rightBottom: + this.resize(popup, moveX, false, true); + this.resize(popup, moveY, true, true); + break; + + case ResizeDragDirection.top: + this.resize(popup, moveY, true, false); + break; + + case ResizeDragDirection.left: + this.resize(popup, moveX, false, false); + break; + + case ResizeDragDirection.bottom: + this.resize(popup, moveY, true, true); + break; + + case ResizeDragDirection.right: + this.resize(popup, moveX, false, true); + break; + } + if (!popup.isResizeOverFlowX) { + popup.lastMouseLeft = e.clientX; + } + if (!popup.isResizeOverFlowY) { + popup.lastMouseTop = e.clientY; + } + isActionSuccess = true; + this.forceUpdate(); + } + }); + } + if (isActionSuccess) { + e.preventDefault(); + } } public componentDidMount() { diff --git a/source/Context/Popups.ts b/source/Context/Popups.ts index 963d74b..075498c 100644 --- a/source/Context/Popups.ts +++ b/source/Context/Popups.ts @@ -3,6 +3,17 @@ import { Emitter } from "@Model/Emitter"; import { Localization } from "@Component/Localization/Localization"; import { IAnyObject } from "@Model/Renderer"; +enum ResizeDragDirection { + top = 1, + rightTop = 2, + right = 3, + rightBottom = 4, + bottom = 5, + leftBottom = 6, + left = 7, + LeftTop = 8 +} + /** * 弹窗类型 */ @@ -15,22 +26,23 @@ class Popup

{ } public zIndex() { - return this.index * 2 + this.controller.zIndex; + return this.index * 5 + this.controller.zIndex; } public width: number = 300; - public height: number = 200; - + public minWidth: number = 300; + public minHeight: number = 200; public top: number = NaN; - public left: number = NaN; - public lastMouseTop: number = 0; - public lastMouseLeft: number = 0; - public isOnMouseDown: boolean = false; + public resizeHoverDirection?: ResizeDragDirection; + public resizeDragDirection?: ResizeDragDirection; + public isResizeMouseDown: boolean = false; + public isResizeOverFlowX: boolean = false; + public isResizeOverFlowY: boolean = false; /** * 是否关闭 @@ -178,4 +190,4 @@ class PopupController extends Emitter { } } -export { Popup, PopupController } \ No newline at end of file +export { Popup, PopupController, ResizeDragDirection } \ No newline at end of file From 50f1cb0562f642e1533493280b17499d1a33b77c Mon Sep 17 00:00:00 2001 From: MrKBear Date: Thu, 24 Mar 2022 17:23:01 +0800 Subject: [PATCH 4/4] Show confim popup when delete objects --- source/Component/CommandBar/CommandBar.tsx | 2 +- .../Component/ConfirmPopup/ConfirmPopup.scss | 2 +- .../Component/ConfirmPopup/ConfirmPopup.tsx | 40 +++++++++++++++---- source/Component/Popup/Popup.scss | 1 - .../Component/TogglesInput/TogglesInput.scss | 4 ++ .../Component/TogglesInput/TogglesInput.tsx | 3 +- source/Localization/EN-US.ts | 6 +++ source/Localization/ZH-CN.ts | 6 +++ source/Panel/GroupDetails/GroupDetails.tsx | 18 +++++++-- source/Panel/LabelDetails/LabelDetails.tsx | 18 +++++++-- source/Panel/LabelList/LabelList.tsx | 14 ++++++- source/Panel/ObjectList/ObjectCommand.tsx | 24 +++++++---- source/Panel/ObjectList/ObjectList.scss | 4 ++ source/Panel/RangeDetails/RangeDetails.tsx | 19 ++++++--- 14 files changed, 129 insertions(+), 32 deletions(-) diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index e29e680..e6d38be 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -73,7 +73,7 @@ class CommandBar extends Component { - this.props.status?.popup.showPopup(ConfirmPopup, {}); + // this.props.status?.popup.showPopup(ConfirmPopup, {}); } })}

diff --git a/source/Component/ConfirmPopup/ConfirmPopup.scss b/source/Component/ConfirmPopup/ConfirmPopup.scss index 50c3869..76e46ae 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.scss +++ b/source/Component/ConfirmPopup/ConfirmPopup.scss @@ -32,7 +32,7 @@ div.confirm-root { user-select: none; } - div.action-button.yes-button:hover { + div.action-button.red { color: $lt-red; } } diff --git a/source/Component/ConfirmPopup/ConfirmPopup.tsx b/source/Component/ConfirmPopup/ConfirmPopup.tsx index dbeec69..b530ea8 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.tsx +++ b/source/Component/ConfirmPopup/ConfirmPopup.tsx @@ -2,11 +2,17 @@ import { Popup } from "@Context/Popups"; import { ReactNode } from "react"; import { Message } from "@Component/Message/Message"; import { Theme } from "@Component/Theme/Theme"; -import { Localization } from "@Component/Localization/Localization"; +import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; import "./ConfirmPopup.scss"; interface IConfirmPopupProps { - + titleI18N?: AllI18nKeys; + infoI18n: AllI18nKeys; + yesI18n?: AllI18nKeys; + noI18n?: AllI18nKeys; + yes?: () => any; + no?: () => any; + red?: "yes" | "no"; } class ConfirmPopup extends Popup { @@ -14,17 +20,37 @@ class ConfirmPopup extends Popup { public height: number = 180; + public onRenderHeader(): ReactNode { + return + } + public render(): ReactNode { + + const yesClassList: string[] = ["action-button", "yes-button"]; + const noClassList: string[] = ["action-button", "no-button"]; + if (this.props.red === "no") { + noClassList.push("red"); + } + if (this.props.red === "yes") { + yesClassList.push("red"); + } + return
- +
-
- +
{ + this.props.yes ? this.props.yes() : null; + this.close(); + }}> +
-
- +
{ + this.props.no ? this.props.no() : null; + this.close(); + }}> +
; diff --git a/source/Component/Popup/Popup.scss b/source/Component/Popup/Popup.scss index 39de7c4..77203ec 100644 --- a/source/Component/Popup/Popup.scss +++ b/source/Component/Popup/Popup.scss @@ -34,7 +34,6 @@ div.popup-layer.show-scale { div.popup-mask { position: absolute; - cursor: pointer; width: 100%; height: 100%; } diff --git a/source/Component/TogglesInput/TogglesInput.scss b/source/Component/TogglesInput/TogglesInput.scss index b89fa89..adb948c 100644 --- a/source/Component/TogglesInput/TogglesInput.scss +++ b/source/Component/TogglesInput/TogglesInput.scss @@ -15,6 +15,10 @@ div.toggles-input { cursor: pointer; user-select: none; } + + div.checkbox.red:hover { + color: $lt-red !important; + } } div.dark.text-field-root { diff --git a/source/Component/TogglesInput/TogglesInput.tsx b/source/Component/TogglesInput/TogglesInput.tsx index a355ddb..bf96f22 100644 --- a/source/Component/TogglesInput/TogglesInput.tsx +++ b/source/Component/TogglesInput/TogglesInput.tsx @@ -8,6 +8,7 @@ interface ITogglesInputProps extends ITextFieldProps { onIconName?: string; offIconName?: string; valueChange?: (value: boolean) => any; + red?: boolean; } class TogglesInput extends Component { @@ -20,7 +21,7 @@ class TogglesInput extends Component { customStyle >
{ /> { if (this.props.status) { - this.props.status.model.deleteObject([group]); - this.props.status.setFocusObject(new Set()); + const status = this.props.status; + status.popup.showPopup(ConfirmPopup, { + infoI18n: "Popup.Delete.Objects.Confirm", + titleI18N: "Popup.Action.Objects.Confirm.Title", + yesI18n: "Popup.Action.Objects.Confirm.Delete", + red: "yes", + yes: () => { + status.model.deleteObject([group]); + status.setFocusObject(new Set()); + } + }) } }} /> @@ -147,7 +157,7 @@ class GroupDetails extends Component { /> { group.killIndividuals() diff --git a/source/Panel/LabelDetails/LabelDetails.tsx b/source/Panel/LabelDetails/LabelDetails.tsx index 28691db..5b55fba 100644 --- a/source/Panel/LabelDetails/LabelDetails.tsx +++ b/source/Panel/LabelDetails/LabelDetails.tsx @@ -5,6 +5,7 @@ import { Message } from "@Component/Message/Message"; import { ColorInput } from "@Component/ColorInput/ColorInput"; import { Label } from "@Model/Label"; import { TogglesInput } from "@Component/TogglesInput/TogglesInput"; +import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import "./LabelDetails.scss"; @useStatusWithEvent("focusLabelChange", "labelAttrChange", "labelChange") @@ -27,10 +28,21 @@ class LabelDetails extends Component { } }}/> - { + { if (this.props.status) { - this.props.status.model.deleteLabel(label); - this.props.status.setLabelObject(); + const status = this.props.status; + status.popup.showPopup(ConfirmPopup, { + infoI18n: "Popup.Delete.Objects.Confirm", + titleI18N: "Popup.Action.Objects.Confirm.Title", + yesI18n: "Popup.Action.Objects.Confirm.Delete", + red: "yes", + yes: () => { + status.model.deleteLabel(label); + status.setLabelObject(); + } + }) } }}/> diff --git a/source/Panel/LabelList/LabelList.tsx b/source/Panel/LabelList/LabelList.tsx index f1f9a80..51704a5 100644 --- a/source/Panel/LabelList/LabelList.tsx +++ b/source/Panel/LabelList/LabelList.tsx @@ -4,6 +4,7 @@ import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { Label } from "@Model/Label"; import { Message } from "@Component/Message/Message"; +import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import "./LabelList.scss"; interface ILabelListProps { @@ -47,8 +48,17 @@ class LabelList extends Component { if (this.props.status) { - this.props.status.model.deleteLabel(label); - this.props.status.setLabelObject(); + const status = this.props.status; + status.popup.showPopup(ConfirmPopup, { + infoI18n: "Popup.Delete.Objects.Confirm", + titleI18N: "Popup.Action.Objects.Confirm.Title", + yesI18n: "Popup.Action.Objects.Confirm.Delete", + red: "yes", + yes: () => { + status.model.deleteLabel(label); + status.setLabelObject(); + } + }) } this.labelInnerClick = true; }} diff --git a/source/Panel/ObjectList/ObjectCommand.tsx b/source/Panel/ObjectList/ObjectCommand.tsx index 71cbe42..0450399 100644 --- a/source/Panel/ObjectList/ObjectCommand.tsx +++ b/source/Panel/ObjectList/ObjectCommand.tsx @@ -1,5 +1,6 @@ import { BackgroundLevel, FontLevel, Theme } from "@Component/Theme/Theme"; import { useStatus, IMixinStatusProps } from "../../Context/Status"; +import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import { Icon } from "@fluentui/react"; import { Component, ReactNode } from "react"; import { ObjectID } from "@Model/Renderer"; @@ -54,15 +55,24 @@ class ObjectCommand extends Component {
{ - if (this.props.status) { - let deleteId: ObjectID[] = []; - this.props.status.focusObject.forEach((obj) => { - deleteId.push(obj); + if (this.props.status && this.props.status.focusObject.size > 0) { + const status = this.props.status; + status.popup.showPopup(ConfirmPopup, { + infoI18n: "Popup.Delete.Objects.Confirm", + titleI18N: "Popup.Action.Objects.Confirm.Title", + yesI18n: "Popup.Action.Objects.Confirm.Delete", + red: "yes", + yes: () => { + let deleteId: ObjectID[] = []; + status.focusObject.forEach((obj) => { + deleteId.push(obj); + }) + status.model.deleteObject(deleteId); + status.setFocusObject(new Set()); + } }) - this.props.status.model.deleteObject(deleteId); - this.props.status.setFocusObject(new Set()); } }} > diff --git a/source/Panel/ObjectList/ObjectList.scss b/source/Panel/ObjectList/ObjectList.scss index b98f127..e0e197a 100644 --- a/source/Panel/ObjectList/ObjectList.scss +++ b/source/Panel/ObjectList/ObjectList.scss @@ -59,6 +59,10 @@ div.object-list-command-bar { user-select: none; cursor: pointer; } + + div.command-item.red:hover { + color: $lt-red; + } } div.dark.object-list-command-bar { diff --git a/source/Panel/RangeDetails/RangeDetails.tsx b/source/Panel/RangeDetails/RangeDetails.tsx index ff9adb3..f5aeee9 100644 --- a/source/Panel/RangeDetails/RangeDetails.tsx +++ b/source/Panel/RangeDetails/RangeDetails.tsx @@ -1,13 +1,13 @@ import { Component, ReactNode } from "react"; import { AttrInput } from "@Component/AttrInput/AttrInput"; import { useStatusWithEvent, IMixinStatusProps, Status } from "@Context/Status"; -import { AllI18nKeys } from "@Component/Localization/Localization"; import { Message } from "@Component/Message/Message"; import { Range } from "@Model/Range"; import { ObjectID } from "@Model/Renderer"; import { ColorInput } from "@Component/ColorInput/ColorInput"; import { TogglesInput } from "@Component/TogglesInput/TogglesInput"; import { LabelPicker } from "@Component/LabelPicker/LabelPicker"; +import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import "./RangeDetails.scss"; @useStatusWithEvent("rangeAttrChange", "focusObjectChange", "rangeLabelChange") @@ -53,12 +53,21 @@ class RangeDetails extends Component { /> { - if (this.props.status) { - this.props.status.model.deleteObject([range]); - this.props.status.setFocusObject(new Set()); + if (this.props.status) { + const status = this.props.status; + status.popup.showPopup(ConfirmPopup, { + infoI18n: "Popup.Delete.Objects.Confirm", + titleI18N: "Popup.Action.Objects.Confirm.Title", + yesI18n: "Popup.Action.Objects.Confirm.Delete", + red: "yes", + yes: () => { + status.model.deleteObject([range]); + status.setFocusObject(new Set()); + } + }) } }} />