From a36a10dade51f548010173ba8bce9bfbf859efba Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 27 Mar 2022 20:49:44 +0800 Subject: [PATCH] Add behavior list component --- source/Behavior/Behavior.ts | 8 +- .../Component/BehaviorList/BehaviorList.scss | 128 ++++++++++++++++ .../Component/BehaviorList/BehaviorList.tsx | 138 ++++++++++++++++++ .../BehaviorPopup/BehaviorPopup.scss | 2 +- .../Component/BehaviorPopup/BehaviorPopup.tsx | 49 ++++++- .../Component/ConfirmPopup/ConfirmPopup.scss | 3 +- .../Component/ConfirmPopup/ConfirmPopup.tsx | 11 +- source/Localization/EN-US.ts | 2 + source/Localization/ZH-CN.ts | 2 + source/Model/Behavior.ts | 11 +- 10 files changed, 339 insertions(+), 15 deletions(-) create mode 100644 source/Component/BehaviorList/BehaviorList.scss create mode 100644 source/Component/BehaviorList/BehaviorList.tsx diff --git a/source/Behavior/Behavior.ts b/source/Behavior/Behavior.ts index 847bee2..3a7c27c 100644 --- a/source/Behavior/Behavior.ts +++ b/source/Behavior/Behavior.ts @@ -1,8 +1,10 @@ import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; import { Template } from "./Template"; -const AllBehaviors: IAnyBehaviorRecorder[] = [ - new BehaviorRecorder(Template) -] +const AllBehaviors: IAnyBehaviorRecorder[] = new Array(20).fill(0).map((_, i) => { + let behavior = new BehaviorRecorder(Template); + behavior.behaviorId = behavior.behaviorId + i; + return behavior; +}); export { AllBehaviors }; \ No newline at end of file diff --git a/source/Component/BehaviorList/BehaviorList.scss b/source/Component/BehaviorList/BehaviorList.scss new file mode 100644 index 0000000..d1cdf0a --- /dev/null +++ b/source/Component/BehaviorList/BehaviorList.scss @@ -0,0 +1,128 @@ +@import "../Theme/Theme.scss"; + +$behavior-item-height: 45px; + +div.behavior-list { + margin: -5px; + display: flex; + flex-wrap: wrap; + + div.behavior-item { + margin: 5px; + height: $behavior-item-height; + border-radius: 3px; + cursor: pointer; + display: flex; + + div.behavior-color-view { + height: $behavior-item-height; + width: 3px; + min-width: 3px; + border-radius: 3px; + flex-shrink: 0; + } + + div.behavior-icon-view { + height: $behavior-item-height; + min-width: $behavior-item-height; + width: $behavior-item-height; + user-select: none; + display: flex; + justify-content: center; + align-items: center; + + i { + font-size: 25px; + } + } + + div.behavior-content-view { + width: calc( 100% - 68px ); + max-width: 100px; + height: $behavior-item-height; + display: flex; + justify-content: center; + flex-direction: column; + align-items: stretch; + + div.title-view { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + } + + div.info-view { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + opacity: .75; + } + } + + div.behavior-action-view { + height: $behavior-item-height; + min-width: 20px; + width: 20px; + flex-shrink: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-content: center; + align-items: center; + + div.behavior-action-button { + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + user-select: none; + cursor: pointer; + } + + div.behavior-action-button.hover-red:hover i { + color: $lt-red; + } + + div.behavior-action-button.hover-blue:hover i { + color: $lt-blue; + } + } + } +} + +div.dark.behavior-list { + + div.behavior-item { + background-color: $lt-bg-color-lvl3-dark; + } + + div.behavior-item:hover { + color: $lt-font-color-lvl2-dark; + background-color: $lt-bg-color-lvl2-dark; + } + + div.behavior-item.focus { + color: $lt-font-color-lvl1-dark; + background-color: $lt-bg-color-lvl1-dark; + } +} + +div.light.behavior-list { + + div.behavior-item { + background-color: $lt-bg-color-lvl3-light; + } + + div.behavior-item:hover { + color: $lt-font-color-lvl2-light; + background-color: $lt-bg-color-lvl2-light; + } + + div.behavior-item.focus { + color: $lt-font-color-lvl1-light; + background-color: $lt-bg-color-lvl1-light; + } +} \ No newline at end of file diff --git a/source/Component/BehaviorList/BehaviorList.tsx b/source/Component/BehaviorList/BehaviorList.tsx new file mode 100644 index 0000000..ce91bb9 --- /dev/null +++ b/source/Component/BehaviorList/BehaviorList.tsx @@ -0,0 +1,138 @@ +import { Theme } from "@Component/Theme/Theme"; +import { Component, ReactNode } from "react"; +import { IRenderBehavior, Behavior, BehaviorRecorder } from "@Model/Behavior"; +import { Icon } from "@fluentui/react"; +import { Localization } from "@Component/Localization/Localization"; +import "./BehaviorList.scss"; + +interface IBehaviorListProps { + behaviors: IRenderBehavior[]; + focusBehaviors?: IRenderBehavior[]; + click?: (behavior: IRenderBehavior) => void; + action?: (behavior: IRenderBehavior) => void; + actionType?: "info" | "delete"; +} + +class BehaviorList extends Component { + + private isFocus(behavior: IRenderBehavior): boolean { + if (this.props.focusBehaviors) { + for (let i = 0; i < this.props.focusBehaviors.length; i++) { + if (this.props.focusBehaviors[i] === behavior) { + return true; + } + } + } + return false; + } + + private renderActionButton(behavior: IRenderBehavior) { + + const classList: string[] = ["info-button", "behavior-action-button"]; + let iconName = "Info"; + + switch (this.props.actionType) { + case "delete": + classList.push("hover-red"); + iconName = "Delete"; + break; + case "info": + classList.push("hover-blue"); + iconName = "Info"; + break; + default: + classList.push("hover-blue"); + } + + return
{ + this.isActionClick = true; + if (this.props.action) { + this.props.action(behavior) + } + }} + > + +
+ } + + private renderTerm(key: string, className: string, needLocal: boolean) { + if (needLocal) { + return
+ +
; + } else { + return
+ {key} +
; + } + } + + private isActionClick: boolean = false; + + private renderBehavior(behavior: IRenderBehavior) { + + let id: string = behavior.behaviorId; + let name: string = behavior.behaviorName; + let icon: string = behavior.iconName; + let info: string = behavior.describe; + let color: string = ""; + let needLocal: boolean = true; + let focus = this.isFocus(behavior); + + if (behavior instanceof Behavior) { + id = behavior.id; + name = behavior.name; + color = behavior.color; + needLocal = false; + } + + if (behavior instanceof BehaviorRecorder) { + needLocal = true; + if (focus) { + color = "rgb(81, 79, 235)"; + } + } + + if (!color) { + color = "transparent"; + } + + return
{ + if (this.props.click && !this.isActionClick) { + this.props.click(behavior); + } + this.isActionClick = false; + }} + > +
+
+ +
+
+ {this.renderTerm(name, "title-view", needLocal)} + {this.renderTerm(info, "info-view", needLocal)} +
+
+ {this.renderActionButton(behavior)} +
+
+ } + + public render(): ReactNode { + return + {this.props.behaviors.map((behavior) => { + return this.renderBehavior(behavior); + })} + + } +} + +export { BehaviorList }; \ No newline at end of file diff --git a/source/Component/BehaviorPopup/BehaviorPopup.scss b/source/Component/BehaviorPopup/BehaviorPopup.scss index 290e2cf..4adb767 100644 --- a/source/Component/BehaviorPopup/BehaviorPopup.scss +++ b/source/Component/BehaviorPopup/BehaviorPopup.scss @@ -6,6 +6,6 @@ div.behavior-popup { } div.behavior-popup-search-box { - padding: 10px 0 0 10px; + padding: 10px 0 10px 10px; width: calc(100% - 10px); } \ No newline at end of file diff --git a/source/Component/BehaviorPopup/BehaviorPopup.tsx b/source/Component/BehaviorPopup/BehaviorPopup.tsx index 53c062f..ad58909 100644 --- a/source/Component/BehaviorPopup/BehaviorPopup.tsx +++ b/source/Component/BehaviorPopup/BehaviorPopup.tsx @@ -1,8 +1,15 @@ import { Component, ReactNode } from "react"; import { Popup } from "@Context/Popups"; -import { Localization } from "@Component/Localization/Localization"; +import { Localization, I18N } from "@Component/Localization/Localization"; import { SearchBox } from "@Component/SearchBox/SearchBox"; import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup"; +import { BehaviorList } from "@Component/BehaviorList/BehaviorList"; +import { AllBehaviors } from "@Behavior/Behavior"; +import { Message } from "@Component/Message/Message"; +import { IRenderBehavior } from "@Model/Behavior"; +import { useStatus, IMixinStatusProps } from "@Context/Status"; +import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; +import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import "./BehaviorPopup.scss"; interface IBehaviorPopupProps { @@ -11,6 +18,7 @@ interface IBehaviorPopupProps { interface IBehaviorPopupState { searchValue: string; + focusBehavior: Set; } class BehaviorPopup extends Popup { @@ -19,6 +27,7 @@ class BehaviorPopup extends Popup { public minHeight: number = 300; public width: number = 600; public height: number = 450; + // public needMask: boolean = false; public onRenderHeader(): ReactNode { return @@ -29,10 +38,15 @@ class BehaviorPopup extends Popup { } } -class BehaviorPopupComponent extends Component { +@useStatus +@useSettingWithEvent("language") +class BehaviorPopupComponent extends Component< + IBehaviorPopupProps & IMixinStatusProps & IMixinSettingProps, IBehaviorPopupState +> { state: Readonly = { - searchValue: "" + searchValue: "", + focusBehavior: new Set() }; private renderHeader = () => { @@ -55,9 +69,34 @@ class BehaviorPopupComponent extends Component - + + { + if (this.state.focusBehavior.has(behavior)) { + this.state.focusBehavior.delete(behavior); + } else { + this.state.focusBehavior.add(behavior); + } + this.forceUpdate(); + }} + action={(behavior)=>{ + if (this.props.status) { + const status = this.props.status; + status.popup.showPopup(ConfirmPopup, { + infoI18n: behavior.describe as any, + titleI18N: "Popup.Behavior.Info.Title", + titleI18NOption: { + behavior: I18N(this.props, behavior.behaviorName as any) + }, + yesI18n: "Popup.Behavior.Info.Confirm", + }) + } + }} + /> } } diff --git a/source/Component/ConfirmPopup/ConfirmPopup.scss b/source/Component/ConfirmPopup/ConfirmPopup.scss index 76c46d6..dacdaf1 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.scss +++ b/source/Component/ConfirmPopup/ConfirmPopup.scss @@ -38,10 +38,11 @@ div.confirm-root { div.action-view { width: 100%; - height: 36px; + height: 46px; display: flex; box-sizing: border-box; padding-right: 5px; + padding-top: 10px; padding-bottom: 10px; justify-content: flex-end; align-items: center; diff --git a/source/Component/ConfirmPopup/ConfirmPopup.tsx b/source/Component/ConfirmPopup/ConfirmPopup.tsx index a3c7aba..4cb4130 100644 --- a/source/Component/ConfirmPopup/ConfirmPopup.tsx +++ b/source/Component/ConfirmPopup/ConfirmPopup.tsx @@ -7,6 +7,7 @@ import "./ConfirmPopup.scss"; interface IConfirmPopupProps { titleI18N?: AllI18nKeys; + titleI18NOption?: Record; infoI18n?: AllI18nKeys; yesI18n?: AllI18nKeys; noI18n?: AllI18nKeys; @@ -16,12 +17,16 @@ interface IConfirmPopupProps { } class ConfirmPopup extends Popup { + public minWidth: number = 300; + public minHeight: number = 180; public width: number = 300; - public height: number = 180; public onRenderHeader(): ReactNode { - return + return } private genActionClickFunction(fn?: () => any): () => any { @@ -142,7 +147,7 @@ class ConfirmContent extends Component {
{this.props.children} diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 8db5a9d..9e858c0 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -54,6 +54,8 @@ const EN_US = { "Popup.Setting.Title": "Preferences setting", "Popup.Add.Behavior.Title": "Add behavior", "Popup.Add.Behavior.Action.Add": "Add all select behavior", + "Popup.Behavior.Info.Title": "Behavior details: {behavior}", + "Popup.Behavior.Info.Confirm": "OK, I know it", "Build.In.Label.Name.All.Group": "All group", "Build.In.Label.Name.All.Range": "All range", "Behavior.Template.Title": "Behavior", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 69ff4ce..1283176 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -54,6 +54,8 @@ const ZH_CN = { "Popup.Setting.Title": "首选项设置", "Popup.Add.Behavior.Title": "添加行为", "Popup.Add.Behavior.Action.Add": "添加全部选中行为", + "Popup.Behavior.Info.Title": "行为详情: {behavior}", + "Popup.Behavior.Info.Confirm": "好的, 我知道了", "Build.In.Label.Name.All.Group": "全部群", "Build.In.Label.Name.All.Range": "全部范围", "Behavior.Template.Title": "行为", diff --git a/source/Model/Behavior.ts b/source/Model/Behavior.ts index 47e5179..ff8abf9 100644 --- a/source/Model/Behavior.ts +++ b/source/Model/Behavior.ts @@ -155,7 +155,7 @@ class BehaviorInfo = {}> extends Emitter { /** * 行为描述 */ - public describe?: string = ""; + public describe: string = ""; } class BehaviorRecorder< @@ -260,6 +260,11 @@ class Behavior< */ public id: string = ""; + /** + * 颜色 + */ + public color: string = ""; + /** * 优先级 * 值越大执行顺序越靠后 @@ -357,8 +362,10 @@ class Behavior< } +type IRenderBehavior = BehaviorInfo | Behavior; + export { Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem, - IAnyBehavior, IAnyBehaviorRecorder + IAnyBehavior, IAnyBehaviorRecorder, BehaviorInfo, IRenderBehavior }; export default { Behavior }; \ No newline at end of file