Add behavior list component #27
@ -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 };
|
128
source/Component/BehaviorList/BehaviorList.scss
Normal file
128
source/Component/BehaviorList/BehaviorList.scss
Normal 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;
|
||||
}
|
||||
}
|
138
source/Component/BehaviorList/BehaviorList.tsx
Normal file
138
source/Component/BehaviorList/BehaviorList.tsx
Normal 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 };
|
@ -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);
|
||||
}
|
@ -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>
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -78,6 +78,9 @@ class Popup extends Component<IPopupProps & IMixinStatusProps & IMixinSettingPro
|
||||
popup.isOnMouseDown = true;
|
||||
popup.lastMouseLeft = e.clientX;
|
||||
popup.lastMouseTop = e.clientY;
|
||||
if (this.props.status) {
|
||||
this.props.status.popup.topping(popup);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{popup.onRenderHeader()}
|
||||
@ -231,6 +234,9 @@ class Popup extends Component<IPopupProps & IMixinStatusProps & IMixinSettingPro
|
||||
private renderLayer(popup: PopupModel) {
|
||||
const pageWidth = document.documentElement.clientWidth;
|
||||
const pageHeight = document.documentElement.clientHeight;
|
||||
const hasAnimate = !popup.isInit;
|
||||
popup.isInit = true;
|
||||
|
||||
if (isNaN(popup.top)) {
|
||||
popup.top = (pageHeight - popup.height) / 2;
|
||||
}
|
||||
@ -248,7 +254,7 @@ class Popup extends Component<IPopupProps & IMixinStatusProps & IMixinSettingPro
|
||||
left: popup.left
|
||||
}}
|
||||
className={getClassList({
|
||||
className: "popup-layer show-scale",
|
||||
className: "popup-layer" + (hasAnimate ? " show-scale" : ""),
|
||||
backgroundLevel: BackgroundLevel.Level4,
|
||||
}, this.props.setting).join(" ")}
|
||||
>
|
||||
|
@ -43,6 +43,7 @@ class Popup<P extends IAnyObject = IAnyObject> {
|
||||
public isResizeMouseDown: boolean = false;
|
||||
public isResizeOverFlowX: boolean = false;
|
||||
public isResizeOverFlowY: boolean = false;
|
||||
public isInit = false;
|
||||
|
||||
/**
|
||||
* 是否关闭
|
||||
@ -129,6 +130,14 @@ class PopupController extends Emitter<IPopupControllerEvent> {
|
||||
*/
|
||||
public popups: Popup[] = [];
|
||||
|
||||
/**
|
||||
* 指定弹窗
|
||||
*/
|
||||
public topping(popup: Popup) {
|
||||
popup.index = Infinity;
|
||||
this.sortPopup();
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序并重置序号
|
||||
*/
|
||||
|
@ -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",
|
||||
|
@ -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": "行为",
|
||||
|
@ -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 };
|
Loading…
Reference in New Issue
Block a user