Add behavior list component #27

Merged
MrKBear merged 3 commits from dev-mrkbear into master 2022-03-27 21:44:25 +08:00
10 changed files with 339 additions and 15 deletions
Showing only changes of commit a36a10dade - Show all commits

View File

@ -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 };

View File

@ -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;
}
}

View File

@ -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<IBehaviorListProps> {
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 <div
className={classList.join(" ")}
onClick={() => {
this.isActionClick = true;
if (this.props.action) {
this.props.action(behavior)
}
}}
>
<Icon iconName={iconName}/>
</div>
}
private renderTerm(key: string, className: string, needLocal: boolean) {
if (needLocal) {
return <div className={className}>
<Localization i18nKey={key as any}/>
</div>;
} else {
return <div className={className}>
{key}
</div>;
}
}
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 <div
key={id}
className={"behavior-item" + (focus ? " focus" : "")}
onClick={() => {
if (this.props.click && !this.isActionClick) {
this.props.click(behavior);
}
this.isActionClick = false;
}}
>
<div
className="behavior-color-view"
style={{ backgroundColor: color }}
/>
<div className="behavior-icon-view">
<Icon iconName={icon}/>
</div>
<div className="behavior-content-view">
{this.renderTerm(name, "title-view", needLocal)}
{this.renderTerm(info, "info-view", needLocal)}
</div>
<div className="behavior-action-view">
{this.renderActionButton(behavior)}
</div>
</div>
}
public render(): ReactNode {
return <Theme className="behavior-list">
{this.props.behaviors.map((behavior) => {
return this.renderBehavior(behavior);
})}
</Theme>
}
}
export { BehaviorList };

View File

@ -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);
}

View File

@ -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<IRenderBehavior>;
}
class BehaviorPopup extends Popup<IBehaviorPopupProps> {
@ -19,6 +27,7 @@ class BehaviorPopup extends Popup<IBehaviorPopupProps> {
public minHeight: number = 300;
public width: number = 600;
public height: number = 450;
// public needMask: boolean = false;
public onRenderHeader(): ReactNode {
return <Localization i18nKey="Popup.Add.Behavior.Title"/>
@ -29,10 +38,15 @@ class BehaviorPopup extends Popup<IBehaviorPopupProps> {
}
}
class BehaviorPopupComponent extends Component<IBehaviorPopupProps, IBehaviorPopupState> {
@useStatus
@useSettingWithEvent("language")
class BehaviorPopupComponent extends Component<
IBehaviorPopupProps & IMixinStatusProps & IMixinSettingProps, IBehaviorPopupState
> {
state: Readonly<IBehaviorPopupState> = {
searchValue: ""
searchValue: "",
focusBehavior: new Set<IRenderBehavior>()
};
private renderHeader = () => {
@ -55,9 +69,34 @@ class BehaviorPopupComponent extends Component<IBehaviorPopupProps, IBehaviorPop
i18nKey: "Popup.Add.Behavior.Action.Add"
}]}
header={this.renderHeader}
headerHeight={36}
headerHeight={46}
>
<Message i18nKey="ZH_CN" isTitle first/>
<BehaviorList
focusBehaviors={Array.from(this.state.focusBehavior)}
behaviors={AllBehaviors}
click={(behavior) => {
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",
})
}
}}
/>
</ConfirmContent>
}
}

View File

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

View File

@ -7,6 +7,7 @@ import "./ConfirmPopup.scss";
interface IConfirmPopupProps {
titleI18N?: AllI18nKeys;
titleI18NOption?: Record<string, string>;
infoI18n?: AllI18nKeys;
yesI18n?: AllI18nKeys;
noI18n?: AllI18nKeys;
@ -16,12 +17,16 @@ interface IConfirmPopupProps {
}
class ConfirmPopup extends Popup<IConfirmPopupProps> {
public minWidth: number = 300;
public minHeight: number = 180;
public width: number = 300;
public height: number = 180;
public onRenderHeader(): ReactNode {
return <Localization i18nKey={this.props.titleI18N ?? "Popup.Title.Confirm"}/>
return <Localization
i18nKey={this.props.titleI18N ?? "Popup.Title.Confirm"}
options={this.props.titleI18NOption}
/>
}
private genActionClickFunction(fn?: () => any): () => any {
@ -142,7 +147,7 @@ class ConfirmContent extends Component<IConfirmContentProps> {
<div
className={contentClassNameList.join(" ")}
style={{
height: `calc( 100% - ${this.getHeaderHeight() + 36}px )`
height: `calc( 100% - ${this.getHeaderHeight() + 46}px )`
}}
>
{this.props.children}

View File

@ -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",

View File

@ -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": "行为",

View File

@ -155,7 +155,7 @@ class BehaviorInfo<E extends Record<EventType, any> = {}> extends Emitter<E> {
/**
*
*/
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 };