Add behavior list component
This commit is contained in:
parent
87023251a9
commit
a36a10dade
@ -1,8 +1,10 @@
|
|||||||
import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior";
|
import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
|
|
||||||
const AllBehaviors: IAnyBehaviorRecorder[] = [
|
const AllBehaviors: IAnyBehaviorRecorder[] = new Array(20).fill(0).map((_, i) => {
|
||||||
new BehaviorRecorder(Template)
|
let behavior = new BehaviorRecorder(Template);
|
||||||
]
|
behavior.behaviorId = behavior.behaviorId + i;
|
||||||
|
return behavior;
|
||||||
|
});
|
||||||
|
|
||||||
export { AllBehaviors };
|
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 {
|
div.behavior-popup-search-box {
|
||||||
padding: 10px 0 0 10px;
|
padding: 10px 0 10px 10px;
|
||||||
width: calc(100% - 10px);
|
width: calc(100% - 10px);
|
||||||
}
|
}
|
@ -1,8 +1,15 @@
|
|||||||
import { Component, ReactNode } from "react";
|
import { Component, ReactNode } from "react";
|
||||||
import { Popup } from "@Context/Popups";
|
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 { SearchBox } from "@Component/SearchBox/SearchBox";
|
||||||
import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup";
|
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";
|
import "./BehaviorPopup.scss";
|
||||||
|
|
||||||
interface IBehaviorPopupProps {
|
interface IBehaviorPopupProps {
|
||||||
@ -11,6 +18,7 @@ interface IBehaviorPopupProps {
|
|||||||
|
|
||||||
interface IBehaviorPopupState {
|
interface IBehaviorPopupState {
|
||||||
searchValue: string;
|
searchValue: string;
|
||||||
|
focusBehavior: Set<IRenderBehavior>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BehaviorPopup extends Popup<IBehaviorPopupProps> {
|
class BehaviorPopup extends Popup<IBehaviorPopupProps> {
|
||||||
@ -19,6 +27,7 @@ class BehaviorPopup extends Popup<IBehaviorPopupProps> {
|
|||||||
public minHeight: number = 300;
|
public minHeight: number = 300;
|
||||||
public width: number = 600;
|
public width: number = 600;
|
||||||
public height: number = 450;
|
public height: number = 450;
|
||||||
|
// public needMask: boolean = false;
|
||||||
|
|
||||||
public onRenderHeader(): ReactNode {
|
public onRenderHeader(): ReactNode {
|
||||||
return <Localization i18nKey="Popup.Add.Behavior.Title"/>
|
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> = {
|
state: Readonly<IBehaviorPopupState> = {
|
||||||
searchValue: ""
|
searchValue: "",
|
||||||
|
focusBehavior: new Set<IRenderBehavior>()
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderHeader = () => {
|
private renderHeader = () => {
|
||||||
@ -55,9 +69,34 @@ class BehaviorPopupComponent extends Component<IBehaviorPopupProps, IBehaviorPop
|
|||||||
i18nKey: "Popup.Add.Behavior.Action.Add"
|
i18nKey: "Popup.Add.Behavior.Action.Add"
|
||||||
}]}
|
}]}
|
||||||
header={this.renderHeader}
|
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>
|
</ConfirmContent>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,11 @@ div.confirm-root {
|
|||||||
|
|
||||||
div.action-view {
|
div.action-view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 46px;
|
||||||
display: flex;
|
display: flex;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -7,6 +7,7 @@ import "./ConfirmPopup.scss";
|
|||||||
|
|
||||||
interface IConfirmPopupProps {
|
interface IConfirmPopupProps {
|
||||||
titleI18N?: AllI18nKeys;
|
titleI18N?: AllI18nKeys;
|
||||||
|
titleI18NOption?: Record<string, string>;
|
||||||
infoI18n?: AllI18nKeys;
|
infoI18n?: AllI18nKeys;
|
||||||
yesI18n?: AllI18nKeys;
|
yesI18n?: AllI18nKeys;
|
||||||
noI18n?: AllI18nKeys;
|
noI18n?: AllI18nKeys;
|
||||||
@ -16,12 +17,16 @@ interface IConfirmPopupProps {
|
|||||||
}
|
}
|
||||||
class ConfirmPopup extends Popup<IConfirmPopupProps> {
|
class ConfirmPopup extends Popup<IConfirmPopupProps> {
|
||||||
|
|
||||||
|
public minWidth: number = 300;
|
||||||
|
public minHeight: number = 180;
|
||||||
public width: number = 300;
|
public width: number = 300;
|
||||||
|
|
||||||
public height: number = 180;
|
public height: number = 180;
|
||||||
|
|
||||||
public onRenderHeader(): ReactNode {
|
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 {
|
private genActionClickFunction(fn?: () => any): () => any {
|
||||||
@ -142,7 +147,7 @@ class ConfirmContent extends Component<IConfirmContentProps> {
|
|||||||
<div
|
<div
|
||||||
className={contentClassNameList.join(" ")}
|
className={contentClassNameList.join(" ")}
|
||||||
style={{
|
style={{
|
||||||
height: `calc( 100% - ${this.getHeaderHeight() + 36}px )`
|
height: `calc( 100% - ${this.getHeaderHeight() + 46}px )`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
@ -54,6 +54,8 @@ const EN_US = {
|
|||||||
"Popup.Setting.Title": "Preferences setting",
|
"Popup.Setting.Title": "Preferences setting",
|
||||||
"Popup.Add.Behavior.Title": "Add behavior",
|
"Popup.Add.Behavior.Title": "Add behavior",
|
||||||
"Popup.Add.Behavior.Action.Add": "Add all select 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.Group": "All group",
|
||||||
"Build.In.Label.Name.All.Range": "All range",
|
"Build.In.Label.Name.All.Range": "All range",
|
||||||
"Behavior.Template.Title": "Behavior",
|
"Behavior.Template.Title": "Behavior",
|
||||||
|
@ -54,6 +54,8 @@ const ZH_CN = {
|
|||||||
"Popup.Setting.Title": "首选项设置",
|
"Popup.Setting.Title": "首选项设置",
|
||||||
"Popup.Add.Behavior.Title": "添加行为",
|
"Popup.Add.Behavior.Title": "添加行为",
|
||||||
"Popup.Add.Behavior.Action.Add": "添加全部选中行为",
|
"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.Group": "全部群",
|
||||||
"Build.In.Label.Name.All.Range": "全部范围",
|
"Build.In.Label.Name.All.Range": "全部范围",
|
||||||
"Behavior.Template.Title": "行为",
|
"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<
|
class BehaviorRecorder<
|
||||||
@ -260,6 +260,11 @@ class Behavior<
|
|||||||
*/
|
*/
|
||||||
public id: string = "";
|
public id: string = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 颜色
|
||||||
|
*/
|
||||||
|
public color: string = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先级
|
* 优先级
|
||||||
* 值越大执行顺序越靠后
|
* 值越大执行顺序越靠后
|
||||||
@ -357,8 +362,10 @@ class Behavior<
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IRenderBehavior = BehaviorInfo | Behavior;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem,
|
Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem,
|
||||||
IAnyBehavior, IAnyBehaviorRecorder
|
IAnyBehavior, IAnyBehaviorRecorder, BehaviorInfo, IRenderBehavior
|
||||||
};
|
};
|
||||||
export default { Behavior };
|
export default { Behavior };
|
Loading…
Reference in New Issue
Block a user