alexander test

This commit is contained in:
IDONTSUDO 2024-09-20 13:56:33 +03:00
parent 3b8d9e4298
commit 401080d78e
41 changed files with 811 additions and 363 deletions

View file

@ -18,6 +18,12 @@ export const SkillsSchema = new Schema({
param: {
type: Schema.Types.Mixed,
},
bgColor: {
type:Schema.Types.String,
},
borderColor:{
type:Schema.Types.String
}
}).plugin(require("mongoose-autopopulate"));
export const skillsSchema = "skills";

View file

@ -143,14 +143,14 @@
}
.dotted {
background-image: -webkit-repeating-radial-gradient(
background-image: -webkit-repeating-radial-gradient(
center center,
rgba(147, 147, 147, 30%),
rgba(147, 147, 147, 30%) 2px,
transparent 2px,
transparent 100%
);
background-image: -moz-repeating-radial-gradient(
center center,
rgba(147, 147, 147, 30%),
@ -179,5 +179,9 @@
width: 100%;
height: 100%;
}
::-webkit-scrollbar-thumb {
background-color: #d6dee1;
border-radius: 20px;
}
</style>
</html>

View file

@ -104,4 +104,9 @@ export const ArrayExtensions = () => {
return this;
};
}
if ([].updateAll === undefined) {
Array.prototype.updateAll = function (element) {
return this.map((el) => Object.assign(el, element));
};
}
};

View file

@ -28,6 +28,7 @@ declare global {
indexOfR(element: T): Result<void, Array<T>>;
replacePropIndex(property: Partial<T>, index: number): T[];
someR(predicate: (value: T) => boolean): Result<void, Array<T>>;
updateAll(value: Partial<T>): Array<T>;
}
interface Date {
formatDate(): string;

View file

@ -0,0 +1,18 @@
import { ClassConstructor } from "class-transformer";
import React from "react";
interface LifeCycleStore {
init?: () => void;
dispose?: () => void;
}
export const useStore = <S extends LifeCycleStore>(storeConstructor: ClassConstructor<S>) => {
const [store] = React.useState(new storeConstructor());
React.useEffect(() => {
store?.init?.();
return () => {
store?.dispose?.();
};
}, []);
return store;
};

View file

@ -1,12 +1,22 @@
import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator";
import {
IsArray,
IsEnum,
IsNotEmpty,
IsObject,
IsOptional,
IsString,
ValidateNested,
isMongoId,
} from "class-validator";
import { Type } from "class-transformer";
import { ISkillView } from "../../features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree";
import { v4 } from "uuid";
import { Result } from "../helper/result";
import { ValidationModel } from "./validation_model";
import { TopicViewModel } from "../../features/topics/topic_view_model";
import { ITopicModel, TopicViewModel } from "../../features/topics/topic_view_model";
import makeAutoObservable from "mobx-store-inheritance";
import clone from "just-clone";
import { BehaviorTreeBuilderStore } from "../../features/behavior_tree_builder/presentation/behavior_tree_builder_store";
export interface IDependency {
skills: ISkillDependency[];
@ -34,19 +44,27 @@ export interface IWeightsDependency {
export interface IDeviceDependency {
sid: string;
}
export interface IDependency {}
export interface IParam {
isFilled: boolean;
type: string;
dependency?: Object;
dependency?: DependencyViewModel;
}
export class DependencyViewModel {
static empty = () => new DependencyViewModel();
toView = (store: BehaviorTreeBuilderStore | undefined): React.ReactNode => "string";
}
export class ParamViewModel implements IParam {
type: string;
dependency: Object;
constructor(type: string, dependency: Object) {
@Type(() => DependencyViewModel)
dependency: DependencyViewModel;
isFilled: boolean = false;
constructor(type: string, dependency: DependencyViewModel) {
this.type = type;
this.dependency = dependency;
}
static empty = () => new ParamViewModel("", {});
static empty = () => new ParamViewModel("", DependencyViewModel.empty());
}
export interface IBTAction {
name: string;
@ -69,6 +87,7 @@ export class BtActionViewModel extends ValidationModel implements IBTAction {
@IsNotEmpty()
@IsString()
type: string;
@Type(() => ParamViewModel)
param: IParam[];
@IsNotEmpty()
@IsString()
@ -144,6 +163,11 @@ export class Module implements IModule {
name: string;
@IsString()
description: string;
constructor(name: string, description: string) {
this.name = name;
this.description = description;
}
static empty = () => new Module("", "");
}
export class BTAction implements IBTAction {
@IsString()
@ -186,6 +210,9 @@ export class Setting implements ISetting {
}
export class SkillModel extends ValidationModel implements ISkill {
@IsOptional()
@IsString()
_id?: string;
constructor() {
super();
makeAutoObservable(this);
@ -202,12 +229,14 @@ export class SkillModel extends ValidationModel implements ISkill {
@ValidateNested()
@Type(() => Module)
Module: IModule;
@Type(() => BtActionViewModel)
BTAction: BtActionViewModel[];
topicsOut: TopicViewModel[] = [];
static empty() {
const skillModel = new SkillModel();
skillModel.BTAction = [];
skillModel.SkillPackage = SkillPackage.empty();
skillModel.Module = Module.empty();
return skillModel;
}
public static isEmpty(skill: SkillModel): Result<void, SkillModel> {
@ -223,6 +252,7 @@ export class SkillModel extends ValidationModel implements ISkill {
result.sid = sid;
return result;
};
emptyParam = () => this.BTAction.at(0)?.param.at(0)?.dependency === undefined;
}
export class SkillDependency implements IDependency {
@ -239,6 +269,13 @@ export class SkillDependency implements IDependency {
}
export class Skills {
constructor() {
makeAutoObservable(this);
}
@IsOptional()
@IsArray()
@Type(() => TopicViewModel)
topicsStack: ITopicModel[] = [];
@IsArray()
@Type(() => SkillModel)
skills: SkillModel[];
@ -247,6 +284,16 @@ export class Skills {
skills.skills = skilsModel;
return skills;
};
deleteTopic = (sid: string) => (this.topicsStack = this.topicsStack.filter((el) => !el.sid?.isEqual(sid)));
getSkillAtSid = (sid: string): Result<void, SkillModel> => {
const result = this.skills.filter((skill) => skill.sid?.isEqual(sid)).at(0);
if (result === undefined) {
return Result.error(undefined);
}
return Result.ok(result);
};
validation = (): Result<string[], void> => {
const errors: string[] = [];
this.skills.forEach((skill) => {
@ -330,8 +377,9 @@ export class Skills {
getSkillDo = (name: string) =>
this.skills.reduce((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(name))) {
acc = el.Module.name;
}
}
return acc;
}, "error");
@ -357,14 +405,14 @@ export class Skills {
.flat(1)
.filter((el) => el !== "");
getDependencyBySkillLabelAndType = <T>(skillType: string, sid: string) =>
getDependencyBySkillLabelAndType = (skillType: string, sid: string): DependencyViewModel =>
this.skills
.reduce<Object[]>((acc, skill) => {
.reduce<DependencyViewModel[]>((acc, skill) => {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.map((action) => {
action.param.map((param) => {
if (param.type.isEqualR(skillType)) {
acc.push(param?.dependency ?? {});
acc.push(param?.dependency ?? DependencyViewModel.empty());
}
return param;
});
@ -374,7 +422,7 @@ export class Skills {
return acc;
}, [])
.at(0) as T;
.at(0) ?? DependencyViewModel.empty();
static isEmpty(model: Skills): Result<void, void> {
if (model.skills.isEmpty()) {
return Result.error(undefined);
@ -392,13 +440,10 @@ export class Skills {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.forEach((action) => {
action.param.forEach((param) => {
if (param.type.isEqual(skillType)) {
acc = Object.keys(param?.dependency ?? {}).isNotEmpty();
}
acc = param.isFilled;
});
});
}
return acc;
}, false);
@ -406,7 +451,6 @@ export class Skills {
this.skills.reduce((acc, skill) => {
skill.BTAction.forEach((action) =>
action.param.forEach((param) => {
// acc.incrementValue(param.sid ?? "empty");
return param;
})
);
@ -434,4 +478,11 @@ export class Skills {
}
return acc;
}, 0);
skillIsDependencyFilled = (label: string): Result<boolean, boolean> =>
this.getSkill(label).fold(
(skill) => {
return Result.ok(skill.BTAction.find((el) => el.name.isEqual(label))?.param.isEmpty());
},
() => Result.error(false)
);
}

View file

@ -3,6 +3,7 @@ import { themeStore } from "../../..";
import { CoreText, CoreTextType } from "../text/text";
export enum ButtonV2Type {
default = "default",
empty = "empty",
}
export const ButtonV2 = ({
text,
@ -10,7 +11,7 @@ export const ButtonV2 = ({
onClick,
style,
icon,
isFilled,
type,
}: {
text?: string;
@ -18,27 +19,36 @@ export const ButtonV2 = ({
onClick: () => void;
style?: React.CSSProperties;
icon?: React.ReactNode;
isFilled?: boolean;
type?: ButtonV2Type;
}) => {
return (
<div
style={{
cursor: "pointer",
backgroundColor: themeStore.theme.greenWhite,
color: textColor ?? themeStore.theme.black,
width: "max-content",
height: "max-content",
padding: match(type)
.with(ButtonV2Type.default, () => 15)
.otherwise(() => 5),
borderRadius: match(type)
.with(ButtonV2Type.default, () => 5)
.otherwise(() => 100),
display: "flex",
paddingRight: 10,
paddingLeft: 10,
}}
style={Object.assign(
{
cursor: "pointer",
backgroundColor: match(type)
.with(ButtonV2Type.empty, () => undefined)
.with(ButtonV2Type.default, () => themeStore.theme.greenWhite)
.otherwise(() => themeStore.theme.greenWhite),
color: textColor ?? themeStore.theme.black,
width: "max-content",
height: "max-content",
padding: match(type)
.with(ButtonV2Type.default, () => 15)
.otherwise(() => 5),
border: match(type)
.with(ButtonV2Type.empty, () => `1px solid ${themeStore.theme.greenWhite}`)
.with(ButtonV2Type.default, () => undefined)
.otherwise(() => undefined),
borderRadius: match(type)
.with(ButtonV2Type.default, () => 5)
.otherwise(() => 100),
display: "flex",
paddingRight: 10,
paddingLeft: 10,
},
style
)}
onClick={() => onClick()}
>
{icon}

View file

@ -0,0 +1,56 @@
import React from "react";
import { themeStore } from "../../..";
import { CoreText, CoreTextType } from "../text/text";
export const DrawerV2: React.FC<{
isOpen?: boolean;
title?: string;
onClose: () => void;
children: React.ReactNode;
}> = ({ isOpen, onClose, children, title }) => {
return (
<div
style={{
position: "fixed",
top: 0,
right: isOpen ? 0 : -300,
width: 300,
height: "100%",
backgroundColor: themeStore.theme.darkSurface,
boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.5)",
transition: "right 0.3s ease",
zIndex: 1000,
}}
>
<div style={{ height: "100%", width: "100%" }}>
<div style={{ padding: 20 }}>
<div
style={{
display: "flex",
width: "100%",
justifyContent: "space-between",
alignItems: "center",
}}
>
<CoreText type={CoreTextType.header} text={title} color={themeStore.theme.darkOnSurfaceVariant} />
<button
style={{
background: "none",
border: "none",
fontSize: 24,
cursor: "pointer",
color: themeStore.theme.darkOnSurfaceVariant,
}}
onClick={onClose}
>
&times;
</button>
</div>
<div style={{ width: "100%", height: 1, backgroundColor: themeStore.theme.outlineVariant }}></div>
</div>
{children}
</div>
</div>
);
};

View file

@ -4,7 +4,6 @@ import { Parts } from "../../../../../../features/details/details_http_repositor
export class SelectDetailViewModel {
details: Parts[];
constructor(parts: Parts[]) {
console.log(parts)
this.details = parts;
makeAutoObservable(this);
}

View file

@ -8,11 +8,12 @@ export interface IIconsProps extends IStyle {
height?: number;
width?: number;
color?: string;
iconStyle?: React.CSSProperties;
isNeedStopPropagation?: boolean;
}
export function Icon(props: IIconsProps) {
return getIconSvg(props.type, props.height, props.width, props.color).fold(
return getIconSvg(props.type, props.height, props.width, props.color, props.iconStyle).fold(
(node) => {
return (
<div
@ -49,11 +50,21 @@ const getIconSvg = (
type: string,
height: number | undefined,
width: number | undefined,
color: string | undefined
color: string | undefined,
iconStyle: React.CSSProperties | undefined
): Result<undefined, React.JSX.Element> => {
switch (type) {
case "":
return Result.ok();
case "Floppy":
return Result.ok(
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3.70455 1.36364C3.28261 1.36364 2.87796 1.53125 2.5796 1.8296C2.28125 2.12796 2.11364 2.53261 2.11364 2.95455V17.0455C2.11365 17.428 2.2515 17.7977 2.50195 18.0869C2.75239 18.3761 3.09865 18.5654 3.47727 18.62V12.0455C3.47727 11.503 3.69278 10.9827 4.07637 10.5991C4.45997 10.2155 4.98024 10 5.52273 10H15.9773C16.5198 10 17.04 10.2155 17.4236 10.5991C17.8072 10.9827 18.0227 11.503 18.0227 12.0455V18.62C18.4014 18.5654 18.7476 18.3761 18.9981 18.0869C19.2485 17.7977 19.3864 17.428 19.3864 17.0455V5.26C19.3864 4.83727 19.2191 4.43273 18.92 4.13455L16.6155 1.83C16.4677 1.68214 16.2922 1.56484 16.0991 1.48482C15.906 1.4048 15.699 1.36362 15.49 1.36364H15.2955V5.22727C15.2955 5.49589 15.2425 5.76187 15.1398 6.01003C15.037 6.2582 14.8863 6.48369 14.6964 6.67363C14.5064 6.86357 14.2809 7.01423 14.0328 7.11703C13.7846 7.21982 13.5186 7.27273 13.25 7.27273H7.34091C6.79842 7.27273 6.27815 7.05722 5.89455 6.67363C5.51096 6.29003 5.29545 5.76976 5.29545 5.22727V1.36364H3.70455ZM6.65909 1.36364V5.22727C6.65909 5.60364 6.96455 5.90909 7.34091 5.90909H13.25C13.4308 5.90909 13.6043 5.83726 13.7321 5.70939C13.86 5.58153 13.9318 5.4081 13.9318 5.22727V1.36364H6.65909ZM16.6591 18.6364V12.0455C16.6591 11.8646 16.5873 11.6912 16.4594 11.5633C16.3315 11.4355 16.1581 11.3636 15.9773 11.3636H5.52273C5.3419 11.3636 5.16847 11.4355 5.04061 11.5633C4.91274 11.6912 4.84091 11.8646 4.84091 12.0455V18.6364H16.6591ZM0.75 2.95455C0.75 2.17095 1.06128 1.41945 1.61537 0.865366C2.16945 0.311281 2.92095 0 3.70455 0H15.4909C16.2745 4.67926e-05 17.026 0.311359 17.58 0.865454L19.8845 3.17C20.4391 3.72455 20.75 4.47636 20.75 5.26V17.0455C20.75 17.8291 20.4387 18.5805 19.8846 19.1346C19.3305 19.6887 18.5791 20 17.7955 20H3.70455C2.92095 20 2.16945 19.6887 1.61537 19.1346C1.06128 18.5805 0.75 17.8291 0.75 17.0455V2.95455Z"
fill="black"
/>
</svg>
);
case "LeftIcon":
return Result.ok(
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -80,8 +91,9 @@ const getIconSvg = (
case "Bucket":
return Result.ok(
<svg
style={iconStyle}
width={width ?? "12"}
height={height ??"14"}
height={height ?? "14"}
viewBox="0 0 12 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View file

@ -86,128 +86,145 @@ class MainPageStore {
};
updateLeftStyle = (left: number, index: number) => this.icons.replacePropIndex({ left: left }, index);
}
export const MainPageV2: React.FC<{ children: React.ReactNode; bgColor: string; style?: React.CSSProperties | void }> =
observer(({ children, bgColor, style }) => {
const [store] = React.useState(() => new MainPageStore());
useEffect(() => {
store.init(4);
}, []);
return (
export const MainPageV2: React.FC<{
children: React.ReactNode;
bgColor: string;
style?: React.CSSProperties | void;
rightChild?: React.ReactNode;
}> = observer(({ children, bgColor, style, rightChild }) => {
const [store] = React.useState(() => new MainPageStore());
useEffect(() => {
store.init(4);
}, []);
return (
<div
style={{
display: "flex",
flexDirection: "column",
backgroundColor: bgColor,
width: "100%",
height: "100%",
overflow: "auto",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
backgroundColor: bgColor,
width: "100%",
height: "100%",
overflow: "auto",
alignSelf: "center",
width: 645,
height: 60,
backgroundColor: themeStore.theme.navBlue,
paddingBottom: 60,
borderRadius: 20,
zIndex: 20,
}}
>
<div
<RefDiv
style={{
alignSelf: "center",
width: 645,
display: "flex",
width: "100%",
height: 60,
backgroundColor: themeStore.theme.navBlue,
paddingBottom: 60,
borderRadius: 20,
zIndex: 20,
justifyContent: "space-evenly",
alignItems: "center",
}}
>
<RefDiv
style={{
display: "flex",
width: "100%",
height: 60,
justifyContent: "space-evenly",
alignItems: "center",
}}
children={
<>
{store.icons.map((el, i) => (
<div key={i} style={{ zIndex: 25 }}>
{el.isActive === true ? (
<>
<RefDiv
style={{ position: "relative", top: 30 }}
property="offsetLeft"
onChange={(text) => store.updateLeftStyle(Number(text), i)}
>
<Icon
type={el.icon}
style={{
margin: 10,
zIndex: 25,
children={
<>
{store.icons.map((el, i) => (
<div key={i} style={{ zIndex: 25 }}>
{el.isActive === true ? (
<>
<RefDiv
style={{ position: "relative", top: 30 }}
property="offsetLeft"
onChange={(text) => store.updateLeftStyle(Number(text), i)}
>
<Icon
type={el.icon}
style={{
margin: 10,
zIndex: 25,
position: "relative",
}}
onClick={() => {
store.icons
.map((element) => {
element.isActive = false;
return element;
})
.map((element, index) =>
i.isEqualR(index).fold(
() => {
element.isActive = true;
},
() => element
)
);
}}
/>
</RefDiv>
</>
) : (
<>
<RefDiv
style={{ position: "relative", top: el.top, zIndex: 23 }}
property="offsetLeft"
onChange={(text) => store.updateLeftStyle(Number(text), i)}
>
<Icon type={el.icon} style={{ margin: 10 }} onClick={() => store.selectIndex(i)} />
</RefDiv>
</>
)}
</div>
))}
</>
}
/>
<div style={{ width: 0, height: 0 }}>
<RefDiv
property="offsetLeft"
onChange={(text) => {
store.offsetLeftCircle = Number(text);
}}
position: "relative",
}}
onClick={() => {
store.icons
.map((element) => {
element.isActive = false;
return element;
})
.map((element, index) =>
i.isEqualR(index).fold(
() => {
element.isActive = true;
},
() => element
)
);
}}
/>
</RefDiv>
</>
) : (
<>
<RefDiv
style={{ position: "relative", top: el.top, zIndex: 23 }}
property="offsetLeft"
onChange={(text) => store.updateLeftStyle(Number(text), i)}
>
<Icon type={el.icon} style={{ margin: 10 }} onClick={() => store.selectIndex(i)} />
</RefDiv>
</>
)}
</div>
))}
</>
}
/>
<div style={{ width: 0, height: 0 }}>
<RefDiv
property="offsetLeft"
onChange={(text) => {
store.offsetLeftCircle = Number(text);
}}
style={{
position: "relative",
top: -30,
left: store.leftCircle,
backgroundColor: bgColor,
width: 60,
height: 60,
borderRadius: "50%",
paddingTop: 10,
zIndex: 21,
}}
>
<div
style={{
position: "relative",
top: -30,
left: store.leftCircle,
backgroundColor: bgColor,
width: 60,
height: 60,
borderRadius: "50%",
paddingTop: 10,
zIndex: 21,
top: -5,
left: 5,
backgroundColor: themeStore.theme.greenWhite,
width: 50,
height: 50,
borderRadius: 200,
}}
>
<div
style={{
position: "relative",
top: -5,
left: 5,
backgroundColor: themeStore.theme.greenWhite,
width: 50,
height: 50,
borderRadius: 200,
}}
></div>
</RefDiv>
</div>
></div>
</RefDiv>
</div>
<div
style={{
position: "absolute",
right: 0,
top: 13,
width: 300,
height: 50,
}}
>
{rightChild}
</div>
<div style={Object.assign({ width: "100%" }, style)}>{children}</div>
</div>
);
});
<div style={Object.assign({ width: "100%" }, style)}>{children}</div>
</div>
);
});

View file

@ -12,16 +12,13 @@ import { DigitalTwinsScreenPath } from "../../digital_twins/digital_twins_screen
import { TopicsScreenPath } from "../../topics/topics_screen";
import { SkillsScreenPath } from "../../skills/skills_screen";
import { CalculationsTemplateScreenPath } from "../../calculations_template/calculations_template_screen";
import { useStore } from "../../../core/helper/use_store";
export const AllProjectScreenPath = "/";
export const AllProjectScreen: React.FunctionComponent = observer(() => {
const [store] = React.useState(() => new AllProjectStore());
const store = useStore(AllProjectStore);
const navigate = useNavigate();
React.useEffect(() => {
store.init();
}, []);
return (
<PreviewPage
largeText={"Проекты"}

View file

@ -22,9 +22,10 @@ export class BehaviorTreeBuilderHttpRepository extends CoreHttpRepository {
`${this.featureApi}/by_id?id=${id}`,
BehaviorTreeModel
) as unknown as Promise<Result<HttpError, BehaviorTreeModel>>;
deleteBt = (id: string) => this._jsonRequest(HttpMethod.DELETE, this.featureApi);
deleteBt = (id: string) => this._jsonRequest(HttpMethod.DELETE, `${this.featureApi}?id=${id}`);
editBt = async (model: BehaviorTreeModel) => {
await this._jsonRequest(HttpMethod.POST, `${this.featureApi}/fill/tree`, model);
model.__v = undefined
return await this._jsonRequest(HttpMethod.PUT, this.featureApi, model);
};
}

View file

@ -8,6 +8,7 @@ import { message } from "antd";
import { ValidationModel } from "../../../core/model/validation_model";
export class BehaviorTreeModel extends ValidationModel {
__v?: string;
@IsNumber()
unixTime: number = Date.now();
@IsString()

View file

@ -37,6 +37,7 @@ export class BehaviorTreeBuilderModel {
this.result = "";
this.getFirstSequence(editor).map((sortedSequence) => {
const firstNodeId = sortedSequence.getKeyFromValueIsExists(1) as string;
this.findSequence(firstNodeId, editor, sortedSequence, 2);
this.result += `<${this.getNodeLabelAtId(editor, firstNodeId, skills)}>`;
this.toXML(sortedSequence as Map<string, number>, editor, area, firstNodeId, skills);
@ -49,12 +50,16 @@ export class BehaviorTreeBuilderModel {
}
public static getNodeLabelAtId(editor: NodeEditor<Schemes>, id: string, skills?: Skills) {
if (skills?.getSkillsNames().find((el) => el.name.isEqual(editor.getNode(id).label))) {
return `Action ID="RbsAction" do="${skills.getSkillDo(editor.getNode(id).label)}" command="${
editor.getNode(id).label
}" sid=${id}"`;
try {
if (skills?.getSkillsNames().find((el) => el.name.isEqual(editor.getNode(id).label))) {
return `Action ID="RbsAction" do="${skills.getSkillDo(editor.getNode(id).label)}" command="${
editor.getNode(id).label
}" sid=${id}"`;
}
return editor.getNode(id).label;
} catch (error) {
console.log(error);
}
return editor.getNode(id).label;
}
static toXML(

View file

@ -35,9 +35,9 @@ export class PrimitiveViewModel {
input: true,
output: true,
css: {
border: "1px solid #003E61",
borderRadius: 33,
background: "#BDE8AE",
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
@ -46,23 +46,132 @@ export class PrimitiveViewModel {
input: true,
output: true,
css: {
border: `1px solid white`,
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
label: SystemPrimitive.ForceFailure,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
label: SystemPrimitive.Repeat,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
label: SystemPrimitive.RetryUntilSuccessful,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
label: SystemPrimitive.KeepRunningUntilFailure,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
label: SystemPrimitive.Delay,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
label: SystemPrimitive.RunOnce,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid 6A4C93",
borderRadius: 5,
background: "rgba(21, 15, 29, 1)",
},
},
{
label: SystemPrimitive.ReactiveSequence,
group: SystemPrimitive.Sequence,
input: true,
output: true,
css: {
background: "rgba(28, 40, 8, 1)",
border: "1px solid rgba(138, 201, 38, 1)",
borderRadius: 5,
},
},
{
label: SystemPrimitive.Sequence,
group: SystemPrimitive.Sequence,
input: true,
output: true,
css: {
background: "rgba(28, 40, 8, 1)",
border: "1px solid rgba(138, 201, 38, 1)",
borderRadius: 5,
},
},
{
label: SystemPrimitive.SequenceWithMemory,
group: SystemPrimitive.Sequence,
input: true,
output: true,
css: {
background: "rgba(28, 40, 8, 1)",
border: "1px solid rgba(138, 201, 38, 1)",
borderRadius: 5,
},
},
{
label: SystemPrimitive.ReactiveFallback,
group: SystemPrimitive.Fallback,
input: true,
output: true,
css: {
background: "rgba(40, 8, 8, 1)",
border: "1px solid rgba(201, 38, 38, 1)",
borderRadius: 5,
},
},
{
label: SystemPrimitive.Fallback,
group: SystemPrimitive.Fallback,
input: true,
output: true,
css: {
background: "rgba(40, 8, 8, 1)",
border: "1px solid rgba(201, 38, 38, 1)",
borderRadius: 5,
color: "white",
background: "rgba(106, 76, 147, 1)",
},
},
{ label: SystemPrimitive.ForceFailure, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.Repeat, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.RetryUntilSuccessful, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.KeepRunningUntilFailure, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.Delay, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.RunOnce, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.ReactiveSequence, group: SystemPrimitive.Sequence, input: true, output: true },
{ label: SystemPrimitive.Sequence, group: SystemPrimitive.Sequence, input: true, output: true },
{ label: SystemPrimitive.SequenceWithMemory, group: SystemPrimitive.Sequence, input: true, output: true },
{ label: SystemPrimitive.ReactiveFallback, group: SystemPrimitive.Fallback, input: true, output: true },
{ label: SystemPrimitive.Fallback, group: SystemPrimitive.Fallback, input: true, output: true },
];
getPrimitiveAtLabel = (name: string) => {
return this.primitives.find((el) => el.label.isEqual(name));
@ -73,7 +182,9 @@ export class PrimitiveViewModel {
.rFind<ISkillView>((el) => el.name.isEqual(primitive.group))
.fold(
() => {
acc.at(acc.findIndex((element) => element.name === primitive.group))?.children?.push({ name: primitive.label });
acc
.at(acc.findIndex((element) => element.name === primitive.group))
?.children?.push({ name: primitive.label });
},
() => {
acc.push({ name: primitive.group, children: [{ name: primitive.label }] });

View file

@ -3,25 +3,20 @@ import { useRete } from "rete-react-plugin";
import { createEditor } from "./ui/editor/editor";
import { SkillTree } from "./ui/skill_tree/skill_tree";
import { BehaviorTreeBuilderStore, DrawerState, StoreUIType } from "./behavior_tree_builder_store";
import { MainPage } from "../../../core/ui/pages/main_page";
import { CoreButton } from "../../../core/ui/button/button";
import { observer } from "mobx-react-lite";
import { match } from "ts-pattern";
import { Icon } from "../../../core/ui/icons/icons";
import { Drawer, theme } from "antd";
import { CoreInput } from "../../../core/ui/input/input";
import { CoreText, CoreTextType } from "../../../core/ui/text/text";
import { useNavigate, useParams } from "react-router-dom";
import { IForms, forms } from "./ui/forms/forms";
import { CoreSelect } from "../../../core/ui/select/select";
import { ButtonV2, ButtonV2Type } from "../../../core/ui/button/button_v2";
import { CoreCard } from "../../../core/ui/card/card";
import { themeStore } from "../../..";
import { CoreModal } from "../../../core/ui/modal/modal";
import { InputV2 } from "../../../core/ui/input/input_v2";
import { SelectV2 } from "../../../core/ui/select/select_v2";
import { BehaviorTreeModel } from "../model/behavior_tree_model";
import { MainPageV2 } from "../../../core/ui/pages/main_page_v2";
import { DrawerV2 } from "../../../core/ui/drawer/drawer";
export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path";
export interface DOMReact {
@ -42,24 +37,24 @@ export const BehaviorTreeBuilderPath = (id?: string) => {
}
return `/behavior/tree/`;
};
export const isBtScreen = () =>
Boolean(window.location.href.match(new RegExp(BehaviorTreeBuilderPath()))?.at(0)) ?? false;
export const BehaviorTreeBuilderScreen = observer(() => {
const navigate = useNavigate();
const { id } = useParams();
// @ts-expect-error
const [ref] = useRete<HTMLDivElement>(createEditor);
const store = behaviorTreeBuilderStore;
React.useEffect(() => {
if (ref.current) {
// @ts-expect-error
const domReact: DOMReact = ref.current.getBoundingClientRect();
store.dragZoneSetOffset(0, domReact.y, domReact.width, domReact.height);
}
}, [store.type]);
React.useEffect(() => {
store.init(navigate).then(() => {
store.initParam(id).then(() => {
if (ref.current) {
// @ts-expect-error
const domReact: DOMReact = ref.current.getBoundingClientRect();
store.dragZoneSetOffset(0, domReact.y, domReact.width, domReact.height);
}
});
store.initParam(id).then(() => {});
});
return () => {
store.dispose();
@ -67,7 +62,52 @@ export const BehaviorTreeBuilderScreen = observer(() => {
}, [id, navigate, ref, store]);
return (
<MainPageV2
style={{ position: "absolute", height: "100%" }}
rightChild={
<>
{match(store.type)
.with(StoreUIType.ViewBehaviorTree, () => (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "end",
alignItems: "center",
}}
>
<ButtonV2 style={{ height: 40 }} onClick={() => {}} text="Запуск" textColor={themeStore.theme.black} />
<div style={{ width: 10 }} />
<ButtonV2
style={{ height: 40 }}
onClick={() => {}}
text="Стоп"
type={ButtonV2Type.empty}
textColor={themeStore.theme.greenWhite}
/>
<div style={{ width: 10 }} />
<div
style={{
backgroundColor: store.isNeedSaveBtn ? themeStore.theme.greenWhite : undefined,
height: 40,
textAlign: "center",
alignContent: "center",
width: 40,
borderRadius: 100,
}}
>
{store.isNeedSaveBtn ? (
<Icon style={{ height: 21 }} onClick={() => store.onClickSaveBehaviorTree()} type="Floppy" />
) : undefined}
</div>
</div>
))
.with(StoreUIType.SelectBehaviorTree, () => <></>)
.otherwise(() => (
<></>
))}
</>
}
style={{ position: "absolute", height: "100%", overflow: "hidden" }}
bgColor={themeStore.theme.black}
children={
<>
@ -98,9 +138,6 @@ export const BehaviorTreeBuilderScreen = observer(() => {
</div>
{store.skillTemplates ? <SkillTree skills={store.skillTree} dragEnd={store.dragEnd} /> : null}
</div>
<div style={{ width: 100, height: 40 }}>
<CoreButton onClick={() => store.onClickSaveBehaviorTree()} text="Сохранить" />
</div>
</div>
<div
ref={ref}
@ -114,7 +151,7 @@ export const BehaviorTreeBuilderScreen = observer(() => {
</>
))
.with(StoreUIType.SelectBehaviorTree, () => (
<>
<div style={{ height: "100%", overflowY: "auto", overflowX: "hidden" }}>
<div style={{ height: 20 }} />
<ButtonV2
icon={<Icon type={"Plus"} style={{ alignSelf: "center", marginLeft: 10, marginRight: 10 }} />}
@ -124,7 +161,7 @@ export const BehaviorTreeBuilderScreen = observer(() => {
<div style={{ height: 50 }} />
<div style={{ display: "inline-grid", gridTemplateColumns: "1fr 1fr 1fr", width: "100%" }}>
{store.btTreeModels?.repeat(100).map((el, index) => (
{store.btTreeModels?.map((el, index) => (
<CoreCard
key={index}
clickDeleteIcon={() => store.deleteBt(el._id)}
@ -135,7 +172,7 @@ export const BehaviorTreeBuilderScreen = observer(() => {
/>
))}
</div>
</>
</div>
))
.otherwise(() => (
<></>
@ -187,14 +224,13 @@ export const BehaviorTreeBuilderScreen = observer(() => {
</>
}
/>
<Drawer
<DrawerV2
title={store.titleDrawer}
destroyOnClose={true}
onClose={() => store.editDrawer(DrawerState.editThreadBehaviorTree, false)}
open={store.drawers.find((el) => el.name === DrawerState.editThreadBehaviorTree)?.status}
isOpen={store.drawers.find((el) => el.name === DrawerState.editThreadBehaviorTree)?.status}
>
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}>
<div>
<div style={{ height: "100%" }}>
{store.skillTemplates?.getForms(store.selected ?? "").map((formType, index) =>
forms(
store.filledOutTemplates?.getDependencyBySkillLabelAndType(
@ -206,13 +242,21 @@ export const BehaviorTreeBuilderScreen = observer(() => {
)
.rFind<IForms>((form) => form.name.isEqual(formType))
.fold(
(s) => <div key={index}>{s.component}</div>,
() => <div key={index + "error"}>Error: Unknown form type {formType}</div>
(s) => (
<div key={index} style={{ height: "100%" }}>
{s.component}
</div>
),
() => (
<div key={index + "error"} style={{ height: "100%" }}>
Error: Unknown form type {formType}
</div>
)
)
)}
</div>
</div>
</Drawer>
</DrawerV2>
</>
}
/>

View file

@ -1,5 +1,9 @@
import React from "react";
import { message } from "antd";
import { NavigateFunction } from "react-router-dom";
import makeAutoObservable from "mobx-store-inheritance";
import { v4 } from "uuid";
import clone from "just-clone";
import { CoreError, UiDrawerFormState } from "../../../core/store/base_store";
import {
BehaviorTreeBuilderModel,
@ -13,15 +17,11 @@ import { AreaExtra, Schemes } from "./ui/editor/editor";
import { AreaPlugin } from "rete-area-plugin";
import { NodeBehaviorTree } from "../model/node_behavior_tree";
import { BehaviorTreeBuilderHttpRepository } from "../data/behavior_tree_builder_http_repository";
import { IParam, Skills } from "../../../core/model/skill_model";
import { DependencyViewModel, IParam, Skills } from "../../../core/model/skill_model";
import { ISkillView } from "./ui/skill_tree/skill_tree";
import { message } from "antd";
import { NavigateFunction } from "react-router-dom";
import { BehaviorTreeViewModel } from "../model/behavior_tree_view_model";
import { UiBaseError } from "../../../core/model/ui_base_error";
import { v4 } from "uuid";
import { BehaviorTreeBuilderPath, behaviorTreeBuilderStore } from "./behavior_tree_builder_screen";
import clone from "just-clone";
import { BehaviorTreeModel } from "../model/behavior_tree_model";
import { PrimitiveViewModel, SystemPrimitive } from "../model/primitive_view_model";
import { SceneModel } from "../../scene_manager/model/scene_model";
@ -60,6 +60,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
activeProject: string = "";
behaviorTreeBuilderHttpRepository = new BehaviorTreeBuilderHttpRepository();
canRun = true;
isNeedSaveBtn = true;
selected: string = "";
selectedSid: string = "";
nodeBehaviorTree: NodeBehaviorTree[] = [];
@ -83,7 +84,6 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
constructor() {
super(DrawerState);
makeAutoObservable(this);
this.primitiveViewModel = new PrimitiveViewModel();
this.skillTree.children?.push({
name: "Примитивы BT",
@ -96,7 +96,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
this.editor = editor;
this.areaPlugin = area;
};
getAllTopics = () => this.filledOutTemplates.topicsStack;
errorHandingStrategy = (_: CoreError) => {};
dragEnd = (e: EventTarget) => {
@ -125,13 +125,19 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
name: name,
id: sid,
});
this.isNeedSaveBtn = true;
if (!name.isEqualMany(Object.keys(SystemPrimitive))) {
this.skillTemplates?.getSkill(name).fold(
(m) => {
const modelSetId = clone(m).setSid(sid);
modelSetId.BTAction = m.BTAction.filter((el) => el.name.isEqual(name));
(skill) => {
const skillModel = clone(skill).setSid(sid);
skillModel.BTAction = skill.BTAction.filter((el) => el.name.isEqual(name));
this.filledOutTemplates?.skills.push(modelSetId);
this.filledOutTemplates?.skills.push(skillModel);
if (skill.emptyParam()) {
this.filledOutTemplates.topicsStack.push(
...skillModel.topicsOut.updateAll({ sid: skillModel.sid ?? "" })
);
}
},
() => console.log("error")
);
@ -139,7 +145,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
}
};
async init(navigate: NavigateFunction): Promise<any> {
async init(navigate: NavigateFunction): Promise<void> {
(await this.behaviorTreeBuilderHttpRepository.getActiveProjectId())
// eslint-disable-next-line array-callback-return
.map((el) => {
@ -175,14 +181,15 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
await this.mapOk("sceneAsset", this.behaviorTreeBuilderHttpRepository.getSceneAsset(model.sceneId));
this.isLoading = false;
this.reteForceUpdateObserver?.emit("");
this.type = StoreUIType.ViewBehaviorTree;
},
async () => {
this.errors.push(new UiBaseError(`не найдено дерево с id:${id}`));
}
);
} else {
this.type = StoreUIType.SelectBehaviorTree;
await this.mapOk("scenes", this.behaviorTreeBuilderHttpRepository.getAllScenes());
// this.type = StoreUIType.SelectBehaviorTree;
}
};
dragZoneSetOffset(offsetTop: number, offsetWidth: number, width: number, height: number) {
@ -193,11 +200,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
h: height,
};
}
dispose(): void {
document.removeEventListener("keyup", () => {});
document.removeEventListener("keydown", () => {});
}
onClickSaveBehaviorTree = async (): Promise<void> => {
onClickSaveBehaviorTree = async () =>
this.filledOutTemplates.validation().fold(
async () => {
(
@ -224,7 +227,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
},
async () => message.error(`Дерево поведения не заполнено`)
);
};
validateBt() {}
createNewBehaviorTree = async () => {
this.viewModel.project = this.activeProject;
@ -239,21 +242,24 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
// );
};
setSelected = (label: string, selected: boolean, sid: string) => {
// this.selectedSid = sid;
// this.selected = label;
this.selectedSid = sid;
this.selected = label;
// if (!Object.keys(SystemPrimitive).includes(label) && this.skillTemplates.skillHasForm(label)) {
this.editDrawer(DrawerState.editThreadBehaviorTree, true);
this.selected = label;
// }
};
deleteBtAction = (sid: string) => {
this.nodeBehaviorTree = this.nodeBehaviorTree.filter((el) => !el.id.isEqual(sid));
this.filledOutTemplates.skills = this.filledOutTemplates.deleteSid(sid);
this.filledOutTemplates.deleteTopic(sid);
this.nodeUpdateObserver?.emit({ type: UpdateEvent.DELETE, id: sid });
Array.from(this.areaPlugin!.nodeViews.keys()).forEach((id) => {
this.areaPlugin?.update("node", id);
});
};
formUpdateDependency = (dependency: Object, formType: string) => {
formUpdateDependency = (dependency: DependencyViewModel, formType: string) => {
this.filledOutTemplates?.skillBySid(this.selectedSid ?? "").fold(
(m) => {
const model = clone(m);
@ -263,15 +269,15 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
const paramClone = clone(param);
if (param.type.isEqual(formType)) {
paramClone.dependency = dependency;
paramClone.isFilled = true;
}
result.push(paramClone);
});
action.param = result;
return action;
});
this.filledOutTemplates.updateSkill(model);
this.editDrawer(DrawerState.editThreadBehaviorTree, false);
},
() => console.log("UNKNOWN SID: " + this.selectedSid)
);
@ -312,27 +318,19 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
: themeStore.theme.error50
}`,
}}
></div>
</div>
))}
{/* {this.skillTemplates?.getSkilsOut(label).map((el, index) => (
<div
style={{ display: "flex", flexDirection: "row", width: "100%", marginBottom: 5, padding: 5 }}
key={index}
>
<div style={{ marginRight: 8, padding: 5 }}>OUT</div>
<div style={{ display: "flex", width: "100%", backgroundColor: "rgba(255, 255, 255, 0.33)", padding: 5 }}>
{el}
>
{behaviorTreeBuilderStore.isFilledInput(el.type, sid)
? this.filledOutTemplates.getDependencyBySkillLabelAndType(label, sid).toView(behaviorTreeBuilderStore)
: undefined}
</div>
</div>
))} */}
))}
</div>
);
}
isFilledInput = (type: string, sid: string): boolean => {
return this.filledOutTemplates?.dependencyIsFilled(type, sid) ?? false;
};
isFilledInput = (type: string, sid: string): boolean =>
this.filledOutTemplates?.dependencyIsFilled(type, sid) ?? false;
getInputs(name: string) {
const result = this.primitiveViewModel.getPrimitiveAtLabel(name);
if (result) {
@ -347,19 +345,10 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
};
}
getStylesByLabelNode(label: string): React.CSSProperties | undefined {
if (label.isEqual(SystemPrimitive.Fallback)) {
return {
border: "1px solid #003E61",
borderRadius: 33,
background: "#BDE8AE",
};
}
if (label.isEqual(SystemPrimitive.Sequence)) {
return {
border: "1px solid #003E61",
background: "rgba(189, 232, 174, 1)",
};
for (const el of this.primitiveViewModel.primitives) {
if (el.label.isEqual(label)) return el.css;
}
return this.filledOutTemplates.getCssAtLabel(label);
}
changeSceneViewModel = (text: string) => {};
@ -370,7 +359,10 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
modalCancel = () => {
this.isModalOpen = false;
};
deleteBt = (id: string) => this.behaviorTreeBuilderHttpRepository.deleteBt(id);
deleteBt = async (id: string) => (
await this.behaviorTreeBuilderHttpRepository.deleteBt(id),
await this.mapOk("btTreeModels", this.behaviorTreeBuilderHttpRepository.getAllBtInstances())
);
saveNewBt = async () =>
(await this.viewModel.valid<BehaviorTreeViewModel>()).fold(
async (model) => {

View file

@ -5,6 +5,7 @@ import { $nodewidth, $socketmargin, $socketsize } from "./vars";
import { behaviorTreeBuilderStore } from "../../../behavior_tree_builder_screen";
import { themeStore } from "../../../../../..";
import { Icon } from "../../../../../../core/ui/icons/icons";
import { SystemPrimitive } from "../../../../model/primitive_view_model";
const { RefSocket, RefControl } = Presets.classic;
type NodeExtraData = { width?: number; height?: number };
@ -152,18 +153,51 @@ export function SequenceNode<Scheme extends ClassicScheme>(props: Props<Scheme>)
<div style={{ display: "flex", width: "100%", justifyContent: "space-between" }}>
<div style={{ paddingLeft: 5 }}>{label}</div>
<div style={{ display: "flex" }}>
<div ref={refSelect}>
<Icon type="SettingGear" />
</div>
<div style={{ width: 2 }} />
{Object.keys(SystemPrimitive)
.rFind<string>((el) => el.isEqual(label))
.fold(
() => (
<>
<div ref={refDelete}>
<Icon
style={{ height: "100%", marginRight: 10 }}
iconStyle={{ height: "100%" }}
type="Bucket"
color={themeStore.theme.onSurfaceVariant}
onClick={() => behaviorTreeBuilderStore.deleteBtAction(id)}
/>
</div>
</>
),
() => (
<>
<div
style={{
display: behaviorTreeBuilderStore.skillTemplates!.skillIsDependencyFilled(label).fold(
(result) => {
if (result) {
return "none";
}
},
() => undefined
),
}}
ref={refSelect}
>
<Icon type="SettingGear" />
</div>
<div style={{ width: 2 }} />
<div ref={refDelete}>
<Icon
type="Bucket"
color={themeStore.theme.onSurfaceVariant}
onClick={() => behaviorTreeBuilderStore.deleteBtAction(id)}
/>
</div>
<div ref={refDelete}>
<Icon
type="Bucket"
color={themeStore.theme.onSurfaceVariant}
onClick={() => behaviorTreeBuilderStore.deleteBtAction(id)}
/>
</div>
</>
)
)}
</div>
</div>
<div>{behaviorTreeBuilderStore.getBodyNode(label, id)}</div>

View file

@ -29,7 +29,7 @@ export const CameraDeviceForm = observer((props: IPropsForm<Partial<IDeviceDepen
onClick={async () => {
(await store.viewModel.valid<SidViewModel>()).fold(
(s) => {
props.onChange(s);
// props.onChange(s);
},
(e) => message.error(e)
);

View file

@ -1,3 +1,4 @@
import { DependencyViewModel } from "../../../../../core/model/skill_model";
import { BehaviorTreeBuilderStore } from "../../behavior_tree_builder_store";
import { CameraDeviceForm } from "./camera_device_form/camera_device_form_form";
import { MoveToPose } from "./move_to_pose/move_to_pose_form";
@ -9,7 +10,7 @@ import { WeightsForm } from "./weights_form/weights_form";
export interface IPropsForm<T> {
dependency: T;
store?: BehaviorTreeBuilderStore;
onChange: (dependency: Object) => void;
onChange: (dependency: DependencyViewModel) => void;
}
export interface IForms {
@ -28,7 +29,7 @@ export interface BtDependencyFormBuilder {
component?: React.ReactNode;
}
export const btDependencyFormBuilder = (onChange: (dependency: Object) => void) => [
export const btDependencyFormBuilder = (onChange: (dependency: DependencyViewModel) => void) => [
{ form: Form.weights, component: null },
{
form: Form.topic,
@ -37,12 +38,12 @@ export const btDependencyFormBuilder = (onChange: (dependency: Object) => void)
];
export const forms = (
props: any,
onChange: (dependency: Object) => void,
onChange: (dependency: DependencyViewModel) => void,
store: BehaviorTreeBuilderStore
): IForms[] => [
{ name: Form.weights, component: <WeightsForm dependency={props} onChange={onChange} /> },
{ name: Form.robotName, component: <RobotDeviceForm store={store} dependency={props} onChange={onChange} /> },
{ name: Form.cameraDeviceForm, component: <CameraDeviceForm store={store} dependency={props} onChange={onChange} /> },
// { name: Form.weights, component: <WeightsForm dependency={props} onChange={onChange} /> },
// { name: Form.robotName, component: <RobotDeviceForm store={store} dependency={props} onChange={onChange} /> },
// { name: Form.cameraDeviceForm, component: <CameraDeviceForm store={store} dependency={props} onChange={onChange} /> },
{ name: Form.topic, component: <TopicsForm store={store} dependency={props} onChange={onChange} /> },
{ name: Form.moveToPose, component: <MoveToPose store={store} dependency={props} onChange={onChange} /> },
// { name: Form.moveToPose, component: <MoveToPose store={store} dependency={props} onChange={onChange} /> },
];

View file

@ -39,7 +39,7 @@ export const MoveToPose = observer((props: IPropsForm<MoveToPoseRobotModel | und
onClick={() => {
store.viewModel.valid().fold(
(s) => {
props.onChange(s);
// props.onChange(s);
},
(e) => message.error(e)
);

View file

@ -1,21 +0,0 @@
import makeAutoObservable from "mobx-store-inheritance";
import { ValidationModel } from "../../../../../../core/model/validation_model";
import { IsNotEmpty, IsString } from "class-validator";
import { StoreTopicType } from "./topics_form_store";
export class TopicDependencyViewModel extends ValidationModel {
@IsNotEmpty()
@IsString()
type: string;
mode?: StoreTopicType;
constructor(type: string, mode: StoreTopicType) {
super();
makeAutoObservable(this);
this.type = type;
this.mode = mode;
}
static empty() {
return new TopicDependencyViewModel("", StoreTopicType.specifiesDependencies);
}
}

View file

@ -0,0 +1,36 @@
import makeAutoObservable from "mobx-store-inheritance";
import { ValidationModel } from "../../../../../../core/model/validation_model";
import { IsNotEmpty, IsString } from "class-validator";
import { StoreTopicType } from "./topics_form_store";
import { DependencyViewModel } from "../../../../../../core/model/skill_model";
import { BehaviorTreeBuilderStore } from "../../../behavior_tree_builder_store";
import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text";
export class TopicDependencyViewModel extends ValidationModel implements DependencyViewModel {
@IsNotEmpty()
@IsString()
type: string;
mode?: StoreTopicType;
sid?: string;
topicOut?: string;
constructor(type: string, mode: StoreTopicType) {
super();
makeAutoObservable(this);
this.type = type;
this.mode = mode;
}
toView = (store: BehaviorTreeBuilderStore | undefined): React.ReactNode => {
if (store && this.sid) {
if (store.filledOutTemplates.topicsStack.some((el) => el.sid?.isEqual(this.sid ?? ""))) {
return <CoreText type={CoreTextType.header} text={this.topicOut} color="red" />;
} else {
return <CoreText type={CoreTextType.header} text="Error" color="red" />;
}
}
return <CoreText type={CoreTextType.header} text="Error" color="red" />;
};
static empty() {
return new TopicDependencyViewModel("", StoreTopicType.specifiesDependencies);
}
}

View file

@ -8,22 +8,71 @@ import { match } from "ts-pattern";
import { CoreInput } from "../../../../../../core/ui/input/input";
import { CoreButton } from "../../../../../../core/ui/button/button";
import { message } from "antd";
import { themeStore } from "../../../../../..";
import { isBtScreen } from "../../../behavior_tree_builder_screen";
export const TopicsForm = observer((props: IPropsForm<Partial<TopicDependencyViewModel>>) => {
const [store] = React.useState(() => new TopicsFormStore());
React.useEffect(() => {
store.init();
store.initParam(isBtScreen(), props.store);
store.loadClassInstance(TopicDependencyViewModel, props.dependency as TopicDependencyViewModel);
}, [props]);
return (
<div>
<CoreText text={"Topics"} type={CoreTextType.header} style={{ padding: 10 }} />
{match(store.viewModel?.mode ?? "")
<div style={{ height: "100%", overflow: "scroll" }}>
<CoreText
text={"Topics"}
type={CoreTextType.header}
style={{ padding: 10 }}
color={themeStore.theme.darkOnSurfaceVariant}
/>
{match(store.type)
.with(StoreTopicType.btExecute, () => (
<>
<CoreText text={"Выберите точку размещения"} type={CoreTextType.header} />
<CoreText
text={"Выберите тип топика"}
type={CoreTextType.header}
style={{ padding: 10 }}
color={themeStore.theme.darkOnSurfaceVariant}
/>
{store
.getAllTopicsMatched()
?.map((el) => (
<div
style={{
display: "flex",
border: el.name.isEqual(store.viewModel.topicOut ?? "") ? "1px solid beige" : undefined,
}}
onClick={() => (store.updateForm({ topicOut: el.name }), store.updateForm({ sid: el.sid }))}
>
<CoreText
text={`name:${el.name}`}
type={CoreTextType.header}
style={{ padding: 10 }}
color={themeStore.theme.darkOnSurfaceVariant}
/>
<CoreText
text={`type:${el.type}`}
type={CoreTextType.header}
style={{ padding: 10 }}
color={themeStore.theme.darkOnSurfaceVariant}
/>
</div>
))}
<CoreButton
text="OK"
style={{ width: 100 }}
onClick={async () => {
(await store.viewModel.valid<TopicDependencyViewModel>()).fold(
(model) => props.onChange(model),
(e) => message.error(e)
);
}}
/>
</>
))
.with(StoreTopicType.specifiesDependencies, () => (

View file

@ -4,20 +4,30 @@ import { TopicDependencyViewModel } from "./topic_dependency_view_model";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { TopicsFormHttpRepository } from "./topics_form_http_repository";
import { Topics } from "../../../../../../core/model/topics";
import { BehaviorTreeBuilderStore } from "../../../behavior_tree_builder_store";
import { ITopicModel } from "../../../../../topics/topic_view_model";
export enum StoreTopicType {
specifiesDependencies = "specifiesDependencies",
btExecute = "btExecute",
}
export class TopicsFormStore extends FormState<TopicDependencyViewModel, CoreError> {
type: StoreTopicType = StoreTopicType.btExecute;
topics?: ITopicModel[];
behaviorTreeBuilderStore?: BehaviorTreeBuilderStore;
viewModel: TopicDependencyViewModel = TopicDependencyViewModel.empty();
constructor() {
super();
makeAutoObservable(this);
}
topics?: Topics;
viewModel: TopicDependencyViewModel = TopicDependencyViewModel.empty();
cameraDeviceHttpRepository: TopicsFormHttpRepository = new TopicsFormHttpRepository();
topicsFormHttpRepository: TopicsFormHttpRepository = new TopicsFormHttpRepository();
errorHandingStrategy = (error: CoreError) => {};
init = async (navigate?: NavigateFunction | undefined) => {
// await this.mapOk('topics', this.cameraDeviceHttpRepository.getAllTopics())
};
getAllTopicsMatched = () => this.topics?.filter((el) => el.type.isEqual(this.viewModel.type));
initParam = (isBtScreen: boolean, behaviorTreeBuilderStore: BehaviorTreeBuilderStore | undefined) => {
isBtScreen ? (this.type = StoreTopicType.btExecute) : (this.type = StoreTopicType.specifiesDependencies);
this.behaviorTreeBuilderStore = behaviorTreeBuilderStore;
this.topics = behaviorTreeBuilderStore?.getAllTopics();
};
}

View file

@ -14,6 +14,7 @@ import { match } from "ts-pattern";
import { TemplateModelCard } from "./ui/template_model_card";
import { Icon } from "../../../core/ui/icons/icons";
import { FormBuilderValidationModel } from "../../dataset/dataset_model";
import { useStore } from "../../../core/helper/use_store";
interface IItem {
name: string;
@ -25,11 +26,8 @@ const skills: IItem[] = [{ name: "ML", isActive: true }];
export const CalculationInstanceScreenPath = "/calculation";
export const CalculationInstanceScreen = observer(() => {
const [store] = React.useState(() => new CalculationInstanceStore());
const store = useStore(CalculationInstanceStore);
React.useEffect(() => {
store.init();
}, []);
return (
<>
<MainPage

View file

@ -1,13 +1,12 @@
import { observer } from "mobx-react-lite";
import { CalculationsTemplateStore } from "./calculations_template_store";
import React from "react";
import { useStore } from "../../core/helper/use_store";
export const CalculationsTemplateScreenPath = "/calculations/template";
export const CalculationsTemplateScreen = observer(() => {
const [store] = React.useState(() => new CalculationsTemplateStore());
React.useEffect(() => {
store.init();
}, []);
const store = useStore(CalculationsTemplateStore);
return <></>;
});

View file

@ -7,15 +7,13 @@ import { MainPage } from "../../core/ui/pages/main_page";
import { CardDataSet, CardDataSetType } from "./card_dataset";
import { CoreInput } from "../../core/ui/input/input";
import { CoreButton } from "../../core/ui/button/button";
import { useStore } from "../../core/helper/use_store";
export const DatasetsScreenPath = "/dataset";
export const DataSetScreen: React.FunctionComponent = observer(() => {
const [store] = React.useState(() => new DataSetStore());
const store = useStore(DataSetStore);
React.useEffect(() => {
store.init();
}, []);
return (
<>
<MainPage
@ -100,7 +98,6 @@ export const DataSetScreen: React.FunctionComponent = observer(() => {
filled={true}
/>
</div>
</div>
<div style={{ display: "flex" }}>

View file

@ -6,11 +6,12 @@ import { CoreText, CoreTextType } from "../../core/ui/text/text";
import { CoreButton } from "../../core/ui/button/button";
import { Drawer, Upload } from "antd";
import { CoreInput } from "../../core/ui/input/input";
import { useStore } from "../../core/helper/use_store";
export const DetailsScreenPath = "/details";
export const DetailsScreen = observer(() => {
const [store] = React.useState(() => new DetailsStore());
const store = useStore(DetailsStore);
React.useEffect(() => {
store.loadScene(canvasRef.current!);
}, []);

View file

@ -8,14 +8,13 @@ import { CoreInput } from "../../core/ui/input/input";
import { CoreSelect } from "../../core/ui/select/select";
import { DigitalTwinsModel, DigitalTwinsTypes } from "./digital_twins_model";
import { match } from "ts-pattern";
import { useStore } from "../../core/helper/use_store";
export const DigitalTwinsScreenPath = "/digital_twins";
export const DigitalTwinsScreen = observer(() => {
const [store] = React.useState(() => new DigitalTwinsStore());
React.useEffect(() => {
store.init();
}, []);
const store = useStore(DigitalTwinsStore);
return (
<>
<div style={{ display: "flex" }}>

View file

@ -43,8 +43,7 @@ export class DigitalTwinsStore extends UiDrawerFormState<DigitalTwinsModel, Http
this.editDrawer(DrawersDigitalTwin.newInstanceTwinTemplate, false);
})
.with(DigitalTwinStoreType.selectTwinTemplate, () => {
console.log(200);
// this.editDrawer(DrawersDigitalTwin.newInstanceTwinTemplate, false),
// await (
// await this.digitalTwinsHttpRepository.createNewDigitalTwinsInstance(this.viewModel)

View file

@ -11,18 +11,18 @@ import { DrawersDataset } from "../../dataset/dataset_store";
import { Icon } from "../../../core/ui/icons/icons";
import { sceneManagerForms } from "./forms/scene_manager_forms";
import { SceneMode } from "../model/scene_view";
import { useStore } from "../../../core/helper/use_store";
export const SceneManagerPath = "/scene/manager/";
export const SceneManger = observer(() => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const [store] = React.useState(() => new SceneMangerStore());
const store = useStore(SceneMangerStore);
const id = useParams().id as string;
const navigate = useNavigate();
store.initParam(id);
React.useEffect(() => {
store.init();
React.useEffect(() => {
store.loadScene(canvasRef.current!);
document.body.style.overflow = "hidden";
return () => {

View file

@ -1,15 +1,15 @@
import * as React from "react";
import { MainPage } from "../../core/ui/pages/main_page";
import { SimulationStore } from "./simulations_store";
interface ISimulationScreenProps { }
import { useStore } from "../../core/helper/use_store";
interface ISimulationScreenProps {}
export const SimulationScreenPath = "/simulation";
export const SimulationScreen = (props: ISimulationScreenProps) => {
const [store] = React.useState(() => new SimulationStore());
const store = useStore(SimulationStore);
const canvasRef = React.useRef<HTMLCanvasElement>(null);
React.useEffect(() => {
store.init();
store.loadScene(canvasRef.current!);
}, []);
return <MainPage page={"Симуляция"} bodyChildren={<canvas ref={canvasRef} style={{ overflow: "hidden" }} />} />;
}
};

View file

@ -4,5 +4,6 @@ import { HttpMethod, HttpRepository } from "../../core/repository/core_http_repo
export class SkillsHttpRepository extends HttpRepository {
featureApi = "/skills";
createNewSkill = (model: SkillModel) => this._jsonRequest(HttpMethod.POST, this.featureApi, model);
deleteSkill = (id: string) => this._jsonRequest(HttpMethod.DELETE, `${this.featureApi}?id=${id}`);
getAllSkills = () => this._arrayJsonToClassInstanceRequest(HttpMethod.GET, this.featureApi, SkillModel);
}

View file

@ -9,14 +9,12 @@ import { BtAction, BtActionViewModel } from "../../core/model/skill_model";
import { btDependencyFormBuilder } from "../behavior_tree_builder/presentation/ui/forms/forms";
import { CoreButton } from "../../core/ui/button/button";
import { CoreSelect } from "../../core/ui/select/select";
import { useStore } from "../../core/helper/use_store";
export const SkillsScreenPath = "/skills";
export const SkillsScreen = observer(() => {
const [store] = React.useState(() => new SkillsStore());
React.useEffect(() => {
store.init();
}, []);
const store = useStore(SkillsStore);
return (
<>
@ -36,10 +34,17 @@ export const SkillsScreen = observer(() => {
borderRadius: 20,
}}
>
<CoreText text="Delete" type={CoreTextType.header} onClick={() => store.deleteSkill(el._id as string)} />
<CoreText text={`skill server name:${el.SkillPackage.name}`} type={CoreTextType.header} />
<div>
<CoreText text={"Topics"} type={CoreTextType.header} />
{el.topicsOut.map((el) => (
<div>
<div>{el.name}</div>
<div>{el.type}</div>
</div>
))}
<CoreText text={"actions"} type={CoreTextType.header} />
{el.BTAction.map((el) => (
<div>
<CoreText text={el.name} type={CoreTextType.medium} />
@ -83,6 +88,16 @@ export const SkillsScreen = observer(() => {
store.updateForm({ SkillPackage: Object.assign(store.viewModel.SkillPackage, { version: text }) })
}
/>
<CoreInput
label={"Module description"}
onChange={(text) =>
store.updateForm({ Module: Object.assign(store.viewModel.Module, { description: text }) })
}
/>
<CoreInput
label={"Module name"}
onChange={(text) => store.updateForm({ Module: Object.assign(store.viewModel.Module, { name: text }) })}
/>
<CoreText
text={`Topics ${store.viewModel.topicsOut.length}`}
type={CoreTextType.large}

View file

@ -2,7 +2,7 @@ import makeAutoObservable from "mobx-store-inheritance";
import { UiDrawerFormState } from "../../core/store/base_store";
import { NavigateFunction } from "react-router-dom";
import { HttpError } from "../../core/repository/core_http_repository";
import { ParamViewModel, SkillModel } from "../../core/model/skill_model";
import { DependencyViewModel, ParamViewModel, SkillModel } from "../../core/model/skill_model";
import { Form } from "../behavior_tree_builder/presentation/ui/forms/forms";
import { message } from "antd";
import { SkillsHttpRepository } from "./skills_http_repository";
@ -26,7 +26,9 @@ export class SkillsStore extends UiDrawerFormState<SkillModel, HttpError> {
}
init = async (navigate?: NavigateFunction | undefined) => {
this.mapOk("skills", this.skillsHttpRepository.getAllSkills());
};
deleteSkill = async (id: string) => (await this.skillsHttpRepository.deleteSkill(id), this.init());
modalShow = () => {
this.isModalOpen = true;
};
@ -42,7 +44,7 @@ export class SkillsStore extends UiDrawerFormState<SkillModel, HttpError> {
this.activeIndex = index;
this.modalShow();
};
onChangeBtDependency = (dependency: Object) =>
onChangeBtDependency = (dependency: DependencyViewModel) =>
this.viewModel.BTAction.at(this.activeIndex ?? 0)
?.validParam(this.selectParam?.form ?? "")
.fold(
@ -67,11 +69,11 @@ export class SkillsStore extends UiDrawerFormState<SkillModel, HttpError> {
clickParam(el: { form: Form; component: null } | { form: Form; component: React.JSX.Element }): void {
this.selectParam = el;
if (el.component === null) this.onChangeBtDependency({});
if (el.component === null) this.onChangeBtDependency(DependencyViewModel.empty());
}
saveNewSkill = async () => {
(await this.viewModel.valid<SkillModel>()).fold(
async (model) => this.skillsHttpRepository.createNewSkill(model),
async (model) => (this.skillsHttpRepository.createNewSkill(model), this.handleCancel(), this.init()),
async (e) => message.error(e)
);
};

View file

@ -3,11 +3,7 @@ import { socketListenerStore } from "./socket_listener_store";
import { observer } from "mobx-react-lite";
import { CoreText, CoreTextType } from "../../core/ui/text/text";
export interface ISocketListenerProps {
children?: JSX.Element;
}
export const SocketListener = observer((props: ISocketListenerProps) => {
export const SocketListener: React.FC<{ children?: JSX.Element }> = observer(({ children }) => {
React.useEffect(() => {
socketListenerStore.init();
}, []);
@ -18,7 +14,7 @@ export const SocketListener = observer((props: ISocketListenerProps) => {
<CoreText
text="Server error reconnect..."
type={CoreTextType.header}
style={{ color: "white", textAlign: "center",marginTop:1 }}
style={{ color: "white", textAlign: "center", marginTop: 1 }}
/>
<div style={{ width: 20 }} />
<div className="loading">Loading&#8230;</div>
@ -27,7 +23,7 @@ export const SocketListener = observer((props: ISocketListenerProps) => {
<></>
)}
{props.children}
{children}
</>
);
});

View file

@ -8,6 +8,7 @@ export class TopicViewModel extends ValidationModel {
@IsNotEmpty()
@IsString()
type: string;
sid?: string;
constructor(name: string, type: string) {
super();
this.name = name;
@ -19,6 +20,7 @@ export class TopicViewModel extends ValidationModel {
}
export interface ITopicModel {
digitalTwinId?: string;
sid?: string;
name: string;
type: string;
}

View file

@ -1,14 +1,13 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { TopicsStore } from "./topics_store";
import React from "react";
import { useStore } from "../../core/helper/use_store";
export const TopicsScreenPath = "/topics";
export const TopicsScreen = observer(() => {
const [store] = React.useState(() => new TopicsStore());
React.useEffect(() => {
store.init();
}, []);
const store = useStore(TopicsStore);
return (
<>
{store.topics?.map((el) => (

View file

@ -16,6 +16,7 @@ extensions();
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
export const themeStore = new ThemeStore();
root.render(
<>
<SocketListener>