Compare commits

...

2 Commits

Author SHA1 Message Date
50f1cb0562 Show confim popup when delete objects 2022-03-24 17:23:01 +08:00
1f4c6feafa Popup layer add drag to resize function 2022-03-24 14:26:26 +08:00
16 changed files with 437 additions and 48 deletions

View File

@ -73,7 +73,7 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi
iconName: "Settings",
i18NKey: "Command.Bar.Setting.Info",
click: () => {
this.props.status?.popup.showPopup(ConfirmPopup, {});
// this.props.status?.popup.showPopup(ConfirmPopup, {});
}
})}
</div>

View File

@ -32,7 +32,7 @@ div.confirm-root {
user-select: none;
}
div.action-button.yes-button:hover {
div.action-button.red {
color: $lt-red;
}
}

View File

@ -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<IConfirmPopupProps> {
@ -14,17 +20,37 @@ class ConfirmPopup extends Popup<IConfirmPopupProps> {
public height: number = 180;
public onRenderHeader(): ReactNode {
return <Localization i18nKey={this.props.titleI18N ?? "Popup.Title.Confirm"}/>
}
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 <Theme className="confirm-root">
<div className="content-views">
<Message i18nKey="ZH_CN"/>
<Message i18nKey={this.props.infoI18n}/>
</div>
<div className="action-view">
<div className="yes-button action-button">
<Localization i18nKey="Panel.Title.Group.Details.View"/>
<div className={yesClassList.join(" ")} onClick={() => {
this.props.yes ? this.props.yes() : null;
this.close();
}}>
<Localization i18nKey={this.props.yesI18n ?? "Popup.Action.Yes"}/>
</div>
<div className="no-button action-button">
<Localization i18nKey="Panel.Title.Group.Details.View"/>
<div className={noClassList.join(" ")} onClick={() => {
this.props.no ? this.props.no() : null;
this.close();
}}>
<Localization i18nKey={this.props.noI18n ?? "Popup.Action.No"}/>
</div>
</div>
</Theme>;

View File

@ -34,7 +34,6 @@ div.popup-layer.show-scale {
div.popup-mask {
position: absolute;
cursor: pointer;
width: 100%;
height: 100%;
}
@ -46,11 +45,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 +77,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 +101,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 {

View File

@ -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<IPopupProps & IMixinStatusProps & IMixinSettingPro
popup.lastMouseLeft = e.clientX;
popup.lastMouseTop = e.clientY;
}}
onMouseUp={() => {
popup.isOnMouseDown = false;
}}
>
{popup.onRenderHeader()}
</div>
@ -108,6 +105,129 @@ class Popup extends Component<IPopupProps & IMixinStatusProps & IMixinSettingPro
</div>
}
private renderDragBlock(dir: ResizeDragDirection, popup: PopupModel) {
return <div className="render-drag-block-root">
<div
draggable={false}
style={{
cursor: this.mapDirToCursor.get(dir),
zIndex: popup.zIndex() + 2
}}
className="render-drag-block"
onMouseDown={(e) => {
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();
}}
/>
</div>
}
private mapDirToCursor = new Map<ResizeDragDirection, string>([
[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 <div
className={"drag-line-root" + (xy ? " drag-line-y" : "")}
style={{
width: xy ? "0" : "100%",
height: xy ? "100%" : "0",
zIndex: popup.zIndex() + 1
}}
>
{
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
}
<div
draggable={false}
className={dragLineCList.join(" ")}
style={{
cursor: xy ? "e-resize" : "n-resize",
minWidth: xy ? "4px" : "calc( 100% + 2px )",
minHeight: xy ? "calc( 100% + 2px )" : "4px"
}}
onMouseDown={(e) => {
popup.lastMouseLeft = e.clientX;
popup.lastMouseTop = e.clientY;
popup.resizeDragDirection = dir;
popup.isResizeMouseDown = true;
}}
/>
{
!xy && dir === ResizeDragDirection.bottom ? this.renderDragBlock(
ResizeDragDirection.rightBottom, popup
) : null
}
</div>
}
private renderLayer(popup: PopupModel) {
const pageWidth = document.documentElement.clientWidth;
const pageHeight = document.documentElement.clientHeight;
@ -132,8 +252,16 @@ class Popup extends Component<IPopupProps & IMixinStatusProps & IMixinSettingPro
backgroundLevel: BackgroundLevel.Level4,
}, this.props.setting).join(" ")}
>
{this.renderDragLine(ResizeDragDirection.top, popup)}
<div className="popup-layer-container">
{this.renderDragLine(ResizeDragDirection.left, popup)}
<div className="popup-layer-root-content">
{this.renderHeader(popup)}
{this.renderContent(popup)}
</div>
{this.renderDragLine(ResizeDragDirection.right, popup)}
</div>
{this.renderDragLine(ResizeDragDirection.bottom, popup)}
</Theme>
}
@ -145,9 +273,54 @@ class Popup extends Component<IPopupProps & IMixinStatusProps & IMixinSettingPro
private handelMouseUp = () => {
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<IPopupProps & IMixinStatusProps & IMixinSettingPro
popup.left += e.clientX - popup.lastMouseLeft;
popup.lastMouseLeft = e.clientX;
popup.lastMouseTop = e.clientY;
isActionSuccess = true;
this.forceUpdate();
}
});
}
if (this.props.status) {
this.props.status.popup.popups.forEach((popup) => {
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() {

View File

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

View File

@ -8,6 +8,7 @@ interface ITogglesInputProps extends ITextFieldProps {
onIconName?: string;
offIconName?: string;
valueChange?: (value: boolean) => any;
red?: boolean;
}
class TogglesInput extends Component<ITogglesInputProps> {
@ -20,7 +21,7 @@ class TogglesInput extends Component<ITogglesInputProps> {
customStyle
>
<div
className="checkbox"
className={"checkbox" + (this.props.red ? " red" : "")}
style={{
cursor: this.props.disableI18n ? "not-allowed" : "pointer"
}}

View File

@ -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<P extends IAnyObject = IAnyObject> {
}
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<IPopupControllerEvent> {
}
}
export { Popup, PopupController }
export { Popup, PopupController, ResizeDragDirection }

View File

@ -45,6 +45,12 @@ const EN_US = {
"Panel.Title.Group.Details.View": "Group",
"Panel.Info.Group.Details.View": "Edit view group attributes",
"Popup.Title.Unnamed": "Popup message",
"Popup.Title.Confirm": "Confirm message",
"Popup.Action.Yes": "Confirm",
"Popup.Action.No": "Cancel",
"Popup.Action.Objects.Confirm.Title": "Confirm Delete",
"Popup.Action.Objects.Confirm.Delete": "Delete",
"Popup.Delete.Objects.Confirm": "Are you sure you want to delete this object(s)? The object is deleted and cannot be recalled.",
"Build.In.Label.Name.All.Group": "All group",
"Build.In.Label.Name.All.Range": "All range",
"Common.No.Data": "No Data",

View File

@ -45,6 +45,12 @@ const ZH_CN = {
"Panel.Title.Group.Details.View": "群",
"Panel.Info.Group.Details.View": "编辑查看群属性",
"Popup.Title.Unnamed": "弹窗消息",
"Popup.Title.Confirm": "确认消息",
"Popup.Action.Yes": "确定",
"Popup.Action.No": "取消",
"Popup.Action.Objects.Confirm.Title": "删除确认",
"Popup.Action.Objects.Confirm.Delete": "删除",
"Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。",
"Build.In.Label.Name.All.Group": "全部群",
"Build.In.Label.Name.All.Range": "全部范围",
"Common.No.Data": "暂无数据",

View File

@ -10,6 +10,7 @@ import { Group, GenMod } from "@Model/Group";
import { AllI18nKeys } from "@Component/Localization/Localization";
import { ComboInput, IDisplayItem } from "@Component/ComboInput/ComboInput";
import { ObjectPicker } from "@Component/ObjectPicker/ObjectPicker";
import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup";
import "./GroupDetails.scss";
interface IGroupDetailsProps {}
@ -88,12 +89,21 @@ class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps> {
/>
<TogglesInput
keyI18n="Common.Attr.Key.Delete"
keyI18n="Common.Attr.Key.Delete" red
onIconName="delete" offIconName="delete"
valueChange={() => {
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<IGroupDetailsProps & IMixinStatusProps> {
/>
<TogglesInput
keyI18n="Common.Attr.Key.Generation"
keyI18n="Common.Attr.Key.Kill.Random"
onIconName="RemoveFilter" offIconName="RemoveFilter"
valueChange={() => {
group.killIndividuals()

View File

@ -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<IMixinStatusProps> {
}
}}/>
<TogglesInput keyI18n="Common.Attr.Key.Delete" onIconName="delete" offIconName="delete" valueChange={() => {
<TogglesInput
keyI18n="Common.Attr.Key.Delete" onIconName="delete" red
offIconName="delete" valueChange={() => {
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();
}
})
}
}}/>

View File

@ -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<ILabelListProps & IMixinStatusProps & IMixinSe
}}
deleteLabel={(label) => {
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;
}}

View File

@ -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<IMixinStatusProps> {
<Icon iconName="CubeShape"></Icon>
</div>
<div
className="command-item"
className="command-item red"
onClick={() => {
if (this.props.status) {
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[] = [];
this.props.status.focusObject.forEach((obj) => {
status.focusObject.forEach((obj) => {
deleteId.push(obj);
})
this.props.status.model.deleteObject(deleteId);
this.props.status.setFocusObject(new Set<ObjectID>());
status.model.deleteObject(deleteId);
status.setFocusObject(new Set<ObjectID>());
}
})
}
}}
>

View File

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

View File

@ -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<IMixinStatusProps> {
/>
<TogglesInput
keyI18n="Common.Attr.Key.Delete"
keyI18n="Common.Attr.Key.Delete" red
onIconName="delete" offIconName="delete"
valueChange={() => {
if (this.props.status) {
this.props.status.model.deleteObject([range]);
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([range]);
status.setFocusObject(new Set());
}
})
}
}}
/>