Add behavior picker component #33
@ -39,8 +39,11 @@ div.behavior-picker-list {
|
||||
i.view-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
i.view-icon:hover {
|
||||
div.behavior-picker-line-icon-view:hover {
|
||||
|
||||
i.view-icon {
|
||||
color: $lt-green;
|
||||
}
|
||||
}
|
||||
@ -57,16 +60,28 @@ div.behavior-picker-list {
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-title.is-deleted {
|
||||
|
||||
div {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-title.behavior-add-line {
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
div.behavior-picker-line-delete-view {
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
color: $lt-red;
|
||||
}
|
||||
div.behavior-picker-line-delete-view:hover i {
|
||||
color: $lt-red;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,77 +1,190 @@
|
||||
import { DetailsList } from "@Component/DetailsList/DetailsList";
|
||||
import { Component, ReactNode } from "react";
|
||||
import { Component, ReactNode, createRef } from "react";
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting";
|
||||
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
|
||||
import { Localization } from "@Component/Localization/Localization";
|
||||
import { PickerList } from "@Component/PickerList/PickerList";
|
||||
import "./BehaviorPicker.scss";
|
||||
|
||||
interface IBehaviorPickerProps {
|
||||
behavior: Behavior[];
|
||||
focusBehavior?: Behavior;
|
||||
click?: (behavior: Behavior) => void;
|
||||
delete?: (behavior: Behavior) => void;
|
||||
action?: (behavior: Behavior) => void;
|
||||
add?: () => void;
|
||||
add?: (behavior: Behavior) => void;
|
||||
}
|
||||
|
||||
interface IBehaviorPickerState {
|
||||
isPickerListOpen: boolean;
|
||||
}
|
||||
|
||||
@useStatusWithEvent("behaviorChange")
|
||||
@useSettingWithEvent("language")
|
||||
class BehaviorPicker extends Component<IBehaviorPickerProps & IMixinSettingProps> {
|
||||
class BehaviorPicker extends Component<IBehaviorPickerProps & IMixinSettingProps & IMixinStatusProps> {
|
||||
|
||||
public state = {
|
||||
isPickerListOpen: false
|
||||
}
|
||||
|
||||
private isInnerClick: boolean = false;
|
||||
private clickLineRef = createRef<HTMLDivElement>();
|
||||
|
||||
private getData() {
|
||||
let data: Array<{key: string, behavior: Behavior | undefined}> = [];
|
||||
let data: Array<{select: boolean, key: string, behavior: Behavior | undefined}> = [];
|
||||
for (let i = 0; i < this.props.behavior.length; i++) {
|
||||
data.push({
|
||||
key: this.props.behavior[i].id,
|
||||
behavior: this.props.behavior[i]
|
||||
behavior: this.props.behavior[i],
|
||||
select: this.props.behavior[i].id === this.props.focusBehavior?.id
|
||||
})
|
||||
}
|
||||
data.push({
|
||||
key: "@@AddButton_List_Key",
|
||||
behavior: undefined
|
||||
behavior: undefined,
|
||||
select: false
|
||||
})
|
||||
return data;
|
||||
}
|
||||
|
||||
private renderLine = (behavior?: Behavior): ReactNode => {
|
||||
if (behavior) {
|
||||
|
||||
const titleClassList: string[] = ["behavior-picker-title"];
|
||||
if (this.props.setting) {
|
||||
titleClassList.push(this.props.setting.language);
|
||||
}
|
||||
if (behavior.isDeleted()) {
|
||||
titleClassList.push("is-deleted");
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className="behavior-picker-line-color-view">
|
||||
<div style={{ borderLeft: `10px solid ${behavior.color}` }}/>
|
||||
</div>
|
||||
<div className="behavior-picker-line-icon-view">
|
||||
<Icon iconName={behavior.iconName} className="behavior-icon"/>
|
||||
<Icon iconName="EditCreate" className="view-icon"/>
|
||||
<div
|
||||
className="behavior-picker-line-icon-view"
|
||||
onClick={() => {
|
||||
this.isInnerClick = true;
|
||||
this.props.action && this.props.action(behavior);
|
||||
}}
|
||||
>
|
||||
{
|
||||
behavior.isDeleted() ?
|
||||
<Icon iconName={behavior.iconName}/>:
|
||||
<>
|
||||
<Icon iconName={behavior.iconName} className="behavior-icon"/>
|
||||
<Icon iconName="EditCreate" className="view-icon"/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
<div className={`behavior-picker-title ${this.props.setting?.language}`}>
|
||||
<div className={titleClassList.join(" ")}>
|
||||
<div>{behavior.name}</div>
|
||||
</div>
|
||||
<div className="behavior-picker-line-delete-view">
|
||||
<div
|
||||
className="behavior-picker-line-delete-view"
|
||||
onClick={() => {
|
||||
this.isInnerClick = true;
|
||||
this.props.delete && this.props.delete(behavior);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="Delete"/>
|
||||
</div>
|
||||
</>;
|
||||
} else {
|
||||
|
||||
const openPicker = () => {
|
||||
this.isInnerClick = true;
|
||||
this.setState({
|
||||
isPickerListOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className="behavior-picker-line-icon-view">
|
||||
<div
|
||||
className="behavior-picker-line-icon-view"
|
||||
onClick={openPicker}
|
||||
>
|
||||
<Icon iconName="Add" className="add-icon"/>
|
||||
</div>
|
||||
<div className={`behavior-picker-title`}>
|
||||
<div
|
||||
className="behavior-picker-title behavior-add-line"
|
||||
onClick={openPicker}
|
||||
ref={this.clickLineRef}
|
||||
>
|
||||
<Localization i18nKey="Behavior.Picker.Add.Button"/>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <DetailsList
|
||||
hideCheckBox
|
||||
className="behavior-picker-list"
|
||||
items={this.getData()}
|
||||
columns={[{
|
||||
className: "behavior-picker-line",
|
||||
key: "behavior",
|
||||
render: this.renderLine
|
||||
}]}
|
||||
private getAllData = (): Behavior[] => {
|
||||
if (this.props.status) {
|
||||
let res: Behavior[] = [];
|
||||
for (let i = 0; i < this.props.status.model.behaviorPool.length; i++) {
|
||||
|
||||
let isAdded = false;
|
||||
for (let j = 0; j < this.props.behavior.length; j++) {
|
||||
if (this.props.status.model.behaviorPool[i].id === this.props.behavior[j].id) {
|
||||
isAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAdded) {
|
||||
res.push(this.props.status.model.behaviorPool[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private renderPickerList(): ReactNode {
|
||||
return <PickerList
|
||||
objectList={this.getAllData()}
|
||||
noData="Behavior.Picker.Add.Nodata"
|
||||
target={this.clickLineRef}
|
||||
clickObjectItems={((item) => {
|
||||
if (item instanceof Behavior && this.props.add) {
|
||||
this.props.add(item);
|
||||
}
|
||||
this.setState({
|
||||
isPickerListOpen: false
|
||||
})
|
||||
})}
|
||||
dismiss={() => {
|
||||
this.setState({
|
||||
isPickerListOpen: false
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <>
|
||||
<DetailsList
|
||||
hideCheckBox
|
||||
className="behavior-picker-list"
|
||||
items={this.getData()}
|
||||
clickLine={(item) => {
|
||||
if (!this.isInnerClick && this.props.click && item.behavior) {
|
||||
this.props.click(item.behavior);
|
||||
}
|
||||
this.isInnerClick = false;
|
||||
}}
|
||||
columns={[{
|
||||
className: "behavior-picker-line",
|
||||
key: "behavior",
|
||||
render: this.renderLine
|
||||
}]}
|
||||
/>
|
||||
{this.state.isPickerListOpen ? this.renderPickerList() : null}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export { BehaviorPicker };
|
@ -8,6 +8,7 @@ import { PickerList, IDisplayItem, getObjectDisplayInfo, IDisplayInfo } from "..
|
||||
import { Localization } from "@Component/Localization/Localization";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import { CtrlObject } from "@Model/CtrlObject";
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import "./ObjectPicker.scss";
|
||||
|
||||
type IObjectType = Label | Group | Range | CtrlObject;
|
||||
@ -80,6 +81,7 @@ class ObjectPicker extends Component<IObjectPickerProps & IMixinStatusProps, IOb
|
||||
target={this.pickerTarget}
|
||||
objectList={this.getAllOption()}
|
||||
clickObjectItems={((item) => {
|
||||
if (item instanceof Behavior) return;
|
||||
if (this.props.valueChange) {
|
||||
this.props.valueChange(item);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
|
||||
import { Callout, DirectionalHint, Icon } from "@fluentui/react";
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import { CtrlObject } from "@Model/CtrlObject";
|
||||
import { Group } from "@Model/Group";
|
||||
import { Label } from "@Model/Label";
|
||||
@ -7,7 +8,7 @@ import { Range } from "@Model/Range";
|
||||
import { Component, ReactNode, RefObject } from "react";
|
||||
import "./PickerList.scss";
|
||||
|
||||
type IPickerListItem = CtrlObject | Label | Range | Group;
|
||||
type IPickerListItem = CtrlObject | Label | Range | Group | Behavior;
|
||||
interface IDisplayInfo {
|
||||
color: string;
|
||||
icon: string;
|
||||
@ -75,6 +76,14 @@ function getObjectDisplayInfo(item?: IPickerListItem): IDisplayInfo {
|
||||
}
|
||||
}
|
||||
|
||||
if (item instanceof Behavior) {
|
||||
color = item.color;
|
||||
icon = item.iconName;
|
||||
name = item.name;
|
||||
internal = false;
|
||||
allLabel = false;
|
||||
}
|
||||
|
||||
if (Array.isArray(color)) {
|
||||
color = `rgb(${color[0]},${color[1]},${color[2]})`;
|
||||
}
|
||||
@ -159,7 +168,7 @@ class PickerList extends Component<IPickerListProps> {
|
||||
return <Callout
|
||||
onDismiss={this.props.dismiss}
|
||||
target={this.props.target}
|
||||
directionalHint={DirectionalHint.topAutoEdge}
|
||||
directionalHint={DirectionalHint.topCenter}
|
||||
>
|
||||
<div className="picker-list-root">
|
||||
{this.props.objectList ? this.props.objectList.map((item) => {
|
||||
|
@ -38,6 +38,7 @@ interface IStatusEvent {
|
||||
objectChange: void;
|
||||
rangeLabelChange: void;
|
||||
groupLabelChange: void;
|
||||
groupBehaviorChange: void;
|
||||
labelChange: void;
|
||||
rangeAttrChange: void;
|
||||
labelAttrChange: void;
|
||||
@ -192,6 +193,22 @@ class Status extends Emitter<IStatusEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
public addGroupBehavior(id: ObjectID, val: Behavior) {
|
||||
const group = this.model.getObjectById(id);
|
||||
if (group && group instanceof Group) {
|
||||
group.addBehavior(val);
|
||||
this.emit("groupBehaviorChange");
|
||||
}
|
||||
}
|
||||
|
||||
public deleteGroupBehavior(id: ObjectID, val: Behavior) {
|
||||
const group = this.model.getObjectById(id);
|
||||
if (group && group instanceof Group) {
|
||||
group.deleteBehavior(val);
|
||||
this.emit("groupBehaviorChange");
|
||||
}
|
||||
}
|
||||
|
||||
public addGroupLabel(id: ObjectID, val: Label) {
|
||||
const group = this.model.getObjectById(id);
|
||||
if (group && group instanceof Group) {
|
||||
|
@ -31,6 +31,7 @@ const EN_US = {
|
||||
"Object.List.No.Data": "There are no objects in the model, click the button to create it",
|
||||
"Object.Picker.List.No.Data": "There is no model in the model for this option",
|
||||
"Behavior.Picker.Add.Button": "Click here to assign behavior to this group",
|
||||
"Behavior.Picker.Add.Nodata": "There is no behavior that can be specified",
|
||||
"Panel.Title.Notfound": "{id}",
|
||||
"Panel.Info.Notfound": "This panel with id {id} can not found!",
|
||||
"Panel.Title.Render.View": "Live preview",
|
||||
|
@ -31,6 +31,7 @@ const ZH_CN = {
|
||||
"Object.List.No.Data": "模型中没有任何对象,点击按钮以创建",
|
||||
"Object.Picker.List.No.Data": "模型中没有合适此选项的模型",
|
||||
"Behavior.Picker.Add.Button": "点击此处以赋予行为到此群",
|
||||
"Behavior.Picker.Add.Nodata": "没有可以被指定的行为",
|
||||
"Panel.Title.Notfound": "{id}",
|
||||
"Panel.Info.Notfound": "这个编号为 {id} 的面板无法找到!",
|
||||
"Panel.Title.Render.View": "实时预览",
|
||||
|
@ -315,8 +315,12 @@ class Group extends CtrlObject {
|
||||
public addBehavior(behavior: Behavior | Behavior[]): this {
|
||||
if (Array.isArray(behavior)) {
|
||||
this.behaviors = this.behaviors.concat(behavior);
|
||||
for (let i = 0; i < behavior.length; i++) {
|
||||
behavior[i].mount(this, this.model);
|
||||
}
|
||||
} else {
|
||||
this.behaviors.push(behavior);
|
||||
behavior.mount(this, this.model);
|
||||
}
|
||||
|
||||
// 按照优先级
|
||||
@ -326,6 +330,27 @@ class Group extends CtrlObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除行为
|
||||
* @param behavior 添加行为
|
||||
*/
|
||||
public deleteBehavior(behavior: Behavior): this {
|
||||
|
||||
let deleteIndex = -1;
|
||||
for (let i = 0; i < this.behaviors.length; i++) {
|
||||
if (this.behaviors[i].id === behavior.id) {
|
||||
deleteIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteIndex >= 0) {
|
||||
this.behaviors[deleteIndex].unmount(this, this.model);
|
||||
this.behaviors.splice(deleteIndex, 1);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行行为影响
|
||||
* @param
|
||||
@ -333,7 +358,11 @@ class Group extends CtrlObject {
|
||||
public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void {
|
||||
this.individuals.forEach((individual) => {
|
||||
for(let j = 0; j < this.behaviors.length; j++) {
|
||||
this.behaviors[j][effectType](individual, this, this.model, t);
|
||||
if (this.behaviors[j].isDeleted()) {
|
||||
continue;
|
||||
} else {
|
||||
this.behaviors[j][effectType](individual, this, this.model, t);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -25,7 +25,10 @@ const allOption: IDisplayItem[] = [
|
||||
{nameKey: "Common.Attr.Key.Generation.Mod.Range", key: GenMod.Range}
|
||||
];
|
||||
|
||||
@useStatusWithEvent("groupAttrChange", "groupLabelChange", "focusObjectChange")
|
||||
@useStatusWithEvent(
|
||||
"groupAttrChange", "groupLabelChange", "focusObjectChange",
|
||||
"focusBehaviorChange", "behaviorChange", "groupBehaviorChange"
|
||||
)
|
||||
class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps> {
|
||||
|
||||
private renderFrom(group: Group) {
|
||||
@ -113,6 +116,19 @@ class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps> {
|
||||
|
||||
<BehaviorPicker
|
||||
behavior={group.behaviors}
|
||||
focusBehavior={this.props.status?.focusBehavior}
|
||||
click={(behavior) => {
|
||||
this.props.status?.setBehaviorObject(behavior);
|
||||
}}
|
||||
action={(behavior) => {
|
||||
this.props.status?.setBehaviorObject(behavior);
|
||||
}}
|
||||
delete={(behavior) => {
|
||||
this.props.status?.deleteGroupBehavior(group.id, behavior);
|
||||
}}
|
||||
add={(behavior) => {
|
||||
this.props.status?.addGroupBehavior(group.id, behavior);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Message i18nKey="Common.Attr.Title.Individual.Generation" isTitle/>
|
||||
|
Loading…
Reference in New Issue
Block a user