alex find bugs

This commit is contained in:
IDONTSUDO 2024-09-24 17:18:56 +03:00
parent d804aa2edf
commit 4e6132a872
27 changed files with 485 additions and 296 deletions

View file

@ -1,15 +1,17 @@
import { ClassConstructor } from "class-transformer";
import React from "react";
import { NavigateFunction, useNavigate } from "react-router-dom";
interface LifeCycleStore {
init?: () => void;
init?: (navigate?: NavigateFunction | undefined) => void;
dispose?: () => void;
}
export const useStore = <S extends LifeCycleStore>(storeConstructor: ClassConstructor<S>) => {
const [store] = React.useState(new storeConstructor());
const navigate = useNavigate();
React.useEffect(() => {
store?.init?.();
store?.init?.(navigate);
return () => {
store?.dispose?.();
};

View file

@ -0,0 +1,58 @@
import { IsNotEmpty, IsString } from "class-validator";
import { BehaviorTreeBuilderStore } from "../../features/behavior_tree_builder/presentation/behavior_tree_builder_store";
import { datasetFormMockContext, datasetFormMockResult, defaultFormValue } from "../../features/dataset/dataset_model";
import { DependencyViewModel } from "./skill_model";
import { ValidationModel } from "./validation_model";
export class FormBuilderValidationModel extends ValidationModel implements DependencyViewModel {
@IsNotEmpty()
@IsString()
public result: string;
@IsNotEmpty()
@IsString()
public context: string;
public form: string[];
public output: any;
constructor(context: string, result: string, form: string[], output: string) {
super();
this.context = context;
this.result = result;
this.form = form;
this.output = output;
}
toView = (store: BehaviorTreeBuilderStore | undefined) => <>IsFilled</>;
static isEmpty = (formBuilderValidationModel: FormBuilderValidationModel) =>
formBuilderValidationModel.context.isEmpty() &&
formBuilderValidationModel.result.isEmpty() &&
formBuilderValidationModel.form.isEmpty();
static datasetEmpty() {
return new FormBuilderValidationModel(datasetFormMockContext, datasetFormMockResult, [], defaultFormValue);
}
static empty() {
return new FormBuilderValidationModel("", "", [], "");
}
static emptyTest() {
return new FormBuilderValidationModel(``, ``, [], defaultFormValue);
}
static creteDataSetTest() {
return new FormBuilderValidationModel(``, scene, [], "");
}
static vision(): FormBuilderValidationModel {
return new FormBuilderValidationModel(
`ENUM PRETRAIN = "true","false";`,
`{
"numberOfEpochs": \${numberOfEpochs:number:10},
"selectDataset": \${<SelectDataset/>:OBJECT:{"dataset": {}},
"pretrain": \${pretrain:Enum<PRETRAIN>:true}
}`,
[],
""
);
}
}
export const scene = `{
"center_shell": [\${CENTER_SHELL_1:number:0}, \${CENTER_SHELL_2:number:0}, \${CENTER_SHELL_3:number:0}],
"scene":\${<SelectScene/>:OBJECT:{"details": []}
}`;

View file

@ -70,9 +70,7 @@ export class BtActionViewModel extends ValidationModel implements IBTAction {
@IsNotEmpty()
@IsString()
name: string;
@IsNotEmpty()
@IsString()
format: string;
@IsNotEmpty()
@IsString()
type: string;
@ -84,16 +82,15 @@ export class BtActionViewModel extends ValidationModel implements IBTAction {
@IsNotEmpty()
@IsEnum(BtAction)
typeAction: BtAction;
constructor(name: string, format: string, type: string, param: IParam[], result: string[], typeAction: BtAction) {
constructor(name: string, type: string, param: IParam[], result: string[], typeAction: BtAction) {
super();
this.name = name;
this.format = format;
this.type = type;
this.param = param;
this.result = result;
this.typeAction = typeAction;
}
static empty = () => new BtActionViewModel("", "", "", [], [], BtAction.ACTION);
static empty = () => new BtActionViewModel("", "", [], [], BtAction.ACTION);
public validParam = (type: string) => this.param.someR((param) => param.type === type);
}
export interface IInterface {
@ -228,6 +225,8 @@ export class SkillModel extends ValidationModel implements ISkill {
@ValidateNested()
@Type(() => Module)
Module: IModule;
@IsNotEmpty()
@IsArray()
@Type(() => BtActionViewModel)
BTAction: BtActionViewModel[];
topicsOut: TopicViewModel[] = [];
@ -337,7 +336,7 @@ export class Skills {
toSkillView = (): ISkillView[] =>
this.skills.map((el) => {
return {
name: el.SkillPackage.name,
name: el.Module.name,
children: el.BTAction.map((act) => {
return { name: act.name, uuid: v4() };
}),
@ -487,4 +486,3 @@ export class Skills {
() => Result.error(false)
);
}

View file

@ -18,6 +18,7 @@ import { DigitalTwinsScreen, DigitalTwinsScreenPath } from "../../features/digit
import { TopicsScreen, TopicsScreenPath } from "../../features/topics/topics_screen";
import { SkillsScreen, SkillsScreenPath } from "../../features/skills/skills_screen";
import { CalculationsTemplateScreenPath } from "../../features/calculations_template/calculations_template_screen";
import { BehaviorTreeManagerScreen, BehaviorTreeManagerScreenPath } from "../../features/behavior_tree_manager/behavior_tree_manager_screen";
const idURL = ":id";
export const router = createBrowserRouter([
@ -85,4 +86,8 @@ export const router = createBrowserRouter([
path: CalculationsTemplateScreenPath,
element: <CalculationInstanceScreen />,
},
{
path: BehaviorTreeManagerScreenPath,
element: <BehaviorTreeManagerScreen />,
},
]);

View file

@ -2,12 +2,12 @@ import * as React from "react";
import { FormViewModel, InputBuilderViewModel, InputType } from "./form_view_model";
import { observer } from "mobx-react-lite";
import { FormBuilderStore } from "./form_builder_store";
import { FormBuilderValidationModel } from "../../../features/dataset/dataset_model";
import { CoreSelect } from "../select/select";
import { CoreSelect } from "../select/select";
import { CoreInput } from "../input/input";
import { Icon } from "../icons/icons";
import { CoreText, CoreTextType } from "../text/text";
import { getFormBuilderComponents } from "./forms/form_builder_components";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
export interface IFormBuilder {
formBuilder: FormBuilderValidationModel;

View file

@ -1,8 +1,8 @@
import { makeAutoObservable } from "mobx";
import { FormViewModel } from "./form_view_model";
import { TypedEvent } from "../../helper/typed_event";
import { FormBuilderValidationModel } from "../../../features/dataset/dataset_model";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
export class ChangerForm extends TypedEvent<FormBuilderValidationModel | undefined> {}
export class FormBuilderStore {

View file

@ -1,8 +1,8 @@
import { makeAutoObservable, observable } from "mobx";
import { Result } from "../../helper/result";
import { v4 as uuidv4 } from "uuid";
import { FormBuilderValidationModel } from "../../../features/dataset/dataset_model";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
export enum InputType {
NUMBER = "number",
STRING = "string",

View file

@ -14,6 +14,7 @@ interface IInputProps extends IStyle {
validation?: (value: string) => boolean;
error?: string;
type?: CoreInputType;
trim?: boolean;
}
export const CoreInput = (props: IInputProps) => {
@ -49,7 +50,7 @@ export const CoreInput = (props: IInputProps) => {
<div
ref={ref}
contentEditable={true}
style={{
style={{
backgroundColor: "#00008000",
border: 1,
fontSize: isSmall ? 12 : 16,
@ -63,10 +64,13 @@ export const CoreInput = (props: IInputProps) => {
top: isSmall ? -8 : undefined,
}}
onInput={(e) => {
const val = e.currentTarget.innerText;
let val = e.currentTarget.innerText;
setValue(val);
if (val) {
if (props.trim) {
val = val.trim().replaceAll("\n", "");
}
if (props.validation !== undefined && props.validation(val) && props.onChange) {
props.onChange(val);
return;

View file

@ -5,10 +5,11 @@ import { CoreText, CoreTextType, FontType } from "../text/text";
interface InputV2Props {
label: string;
value?: string;
trim?: boolean;
height?: number;
onChange?: (text: string) => void;
}
export const InputV2: React.FC<InputV2Props> = ({ label, height, value, onChange }) => {
export const InputV2: React.FC<InputV2Props> = ({ label, height, value, onChange, trim }) => {
return (
<div
style={{
@ -29,7 +30,9 @@ export const InputV2: React.FC<InputV2Props> = ({ label, height, value, onChange
/>
<CoreText
onChange={(text) => {
if (onChange) onChange(text);
let result = text;
if (trim) result = result.trim().replaceAll("\n", "");
if (onChange) onChange(result);
}}
style={{
backgroundColor: themeStore.theme.surfaceContainerHighest,

View file

@ -15,7 +15,6 @@ export class BehaviorTreeBuilderHttpRepository extends CoreHttpRepository {
SkillModel
)) as unknown as Promise<Result<HttpError, SkillModel[]>>;
};
saveNewBt = async (model: BehaviorTreeViewModel) => this._jsonRequest(HttpMethod.POST, this.featureApi, model);
getBtById = async (id: string): Promise<Result<HttpError, BehaviorTreeModel>> =>
this._jsonToClassInstanceRequest<BehaviorTreeModel>(
HttpMethod.GET,

View file

@ -2,7 +2,7 @@ import * as React from "react";
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 { BehaviorTreeBuilderStore, DrawerState } from "./behavior_tree_builder_store";
import { observer } from "mobx-react-lite";
import { match } from "ts-pattern";
import { Icon } from "../../../core/ui/icons/icons";
@ -51,179 +51,97 @@ export const BehaviorTreeBuilderScreen = observer(() => {
const domReact: DOMReact = ref.current.getBoundingClientRect();
store.dragZoneSetOffset(0, domReact.y, domReact.width, domReact.height);
}
}, [store.type]);
}, [ref.current]);
React.useEffect(() => {
store.init(navigate).then(() => {
store.initParam(id).then(() => {});
store.initParam(id);
});
return () => {
store.dispose();
};
}, [id, navigate, ref, store]);
}, []);
return (
<MainPageV2
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(() => (
<></>
))}
<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>
</>
}
style={{ position: "absolute", height: "100%", overflow: "hidden" }}
bgColor={themeStore.theme.black}
children={
<>
{match(store.type)
.with(StoreUIType.ViewBehaviorTree, () => (
<>
<div
style={{
height: "100%",
width: 300,
zIndex: 10,
position: "absolute",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
backgroundColor: themeStore.theme.surfaceContainerLow,
}}
>
<div style={{ margin: 20 }}>
<div style={{ height: 58, alignContent: "center" }}>
<CoreText
text="Поведение"
type={CoreTextType.mediumV2}
color={themeStore.theme.onSurfaceVariant}
style={{ marginLeft: 20 }}
/>
<div style={{ width: "100%", height: 1, backgroundColor: themeStore.theme.outlineVariant }} />
</div>
{store.skillTemplates ? <SkillTree skills={store.skillTree} dragEnd={store.dragEnd} /> : null}
</div>
</div>
<div
ref={ref}
className="dotted"
style={{
width: "100%",
height: "100%",
backgroundSize: "20px 20px",
}}
/>
</>
))
.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 }} />}
text="СОЗДАТЬ ДЕРЕВО ПОВЕДЕНИЯ"
onClick={() => store.modalShow()}
/>
<div style={{ height: 50 }} />
<div style={{ display: "inline-grid", gridTemplateColumns: "1fr 1fr 1fr", width: "100%" }}>
{store.btTreeModels?.map((el, index) => (
<CoreCard
key={index}
clickDeleteIcon={() => store.deleteBt(el._id)}
clickGoToIcon={() => store.goToBt(el._id)}
descriptionMiddle={el.description}
descriptionTop={el.name}
date={el.unixTime}
/>
))}
<>
<div
style={{
height: "100%",
width: 300,
zIndex: 10,
position: "absolute",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
backgroundColor: themeStore.theme.surfaceContainerLow,
}}
>
<div style={{ margin: 20 }}>
<div style={{ height: 58, alignContent: "center" }}>
<CoreText
text="Поведение"
type={CoreTextType.mediumV2}
color={themeStore.theme.onSurfaceVariant}
style={{ marginLeft: 20 }}
/>
<div style={{ width: "100%", height: 1, backgroundColor: themeStore.theme.outlineVariant }} />
</div>
{store.skillTemplates ? <SkillTree skills={store.skillTree} dragEnd={store.dragEnd} /> : null}
</div>
))
.otherwise(() => (
<></>
))}
<CoreModal
isOpen={store.isModalOpen}
onClose={() => store.modalCancel()}
children={
<>
<div style={{ height: 30 }} />
<CoreText
text="Создание дерева поведения"
type={CoreTextType.header}
color={themeStore.theme.white}
style={{ textAlignLast: "center" }}
/>
<div style={{ height: 30 }} />
<div
style={{
height: 1,
width: "calc(100% - 30px)",
backgroundColor: themeStore.theme.outlineVariant,
}}
/>
<div style={{ height: 20 }} />
<InputV2 label={"Название"} onChange={(text) => store.updateForm({ name: text })} />
<div style={{ height: 20 }} />
<InputV2 label={"Описание"} onChange={(text) => store.updateForm({ description: text })} />
<div style={{ height: 20 }} />
<SelectV2
items={store.scenes?.map((el) => ({ name: el.name, value: el._id })) ?? []}
initialValue={""}
label={"Сцена"}
onChange={(value: string) => store.updateForm({ sceneId: value })}
/>
<div style={{ height: 30 }} />
<div
style={{
height: 1,
width: "calc(100% - 30px)",
backgroundColor: themeStore.theme.outlineVariant,
}}
/>
<div style={{ display: "flex", paddingTop: 20, justifyContent: "center" }}>
<ButtonV2 text="Сохранить" onClick={() => store.saveNewBt()} type={ButtonV2Type.default} />
<div style={{ width: 20 }} />
<ButtonV2 text="Отменить" onClick={() => store.modalCancel()} type={ButtonV2Type.default} />
</div>
</>
}
/>
</div>
<div
ref={ref}
className="dotted"
style={{
width: "100%",
height: "100%",
backgroundSize: "20px 20px",
}}
/>
</>
<DrawerV2
title={store.titleDrawer}
onClose={() => store.editDrawer(DrawerState.editThreadBehaviorTree, false)}

View file

@ -24,7 +24,6 @@ import { UiBaseError } from "../../../core/model/ui_base_error";
import { BehaviorTreeBuilderPath, behaviorTreeBuilderStore } from "./behavior_tree_builder_screen";
import { BehaviorTreeModel } from "../model/behavior_tree_model";
import { PrimitiveViewModel, SystemPrimitive } from "../model/primitive_view_model";
import { SceneModel } from "../../scene_manager/model/scene_model";
import { SceneAsset } from "../../../core/model/scene_asset";
import { themeStore } from "../../..";
@ -36,20 +35,12 @@ interface I2DArea {
}
export enum DrawerState {
newBehaviorTree = "Новое дерево поведения",
editThreadBehaviorTree = "Редактирование",
}
export enum StoreUIType {
SelectBehaviorTree,
ViewBehaviorTree,
}
export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> {
type: StoreUIType = StoreUIType.ViewBehaviorTree;
sceneAsset?: SceneAsset;
viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty();
scenes?: SceneModel[];
behaviorTreeModel: BehaviorTreeModel = BehaviorTreeModel.empty();
skillTemplates: Skills = Skills.empty();
filledOutTemplates: Skills = Skills.empty();
@ -68,7 +59,6 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
editor?: NodeEditor<Schemes>;
areaPlugin?: AreaPlugin<Schemes, AreaExtra>;
nodeUpdateObserver?: NodeRerenderObserver;
isModalOpen: boolean = false;
primitiveViewModel: PrimitiveViewModel;
skillTree: ISkillView = {
name: "",
@ -144,7 +134,20 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
}
}
};
dispose(): void {
this.sceneAsset = undefined;
this.behaviorTreeModel = BehaviorTreeModel.empty();
this.skillTemplates = Skills.empty();
this.filledOutTemplates = Skills.empty();
this.area = undefined;
this.reteForceUpdateObserver = undefined;
this.btTreeModels = [];
this.nodeBehaviorTree = [];
this.editor = undefined;
this.areaPlugin = undefined;
this.nodeUpdateObserver = undefined;
}
async init(navigate: NavigateFunction): Promise<void> {
(await this.behaviorTreeBuilderHttpRepository.getActiveProjectId())
// eslint-disable-next-line array-callback-return
@ -180,16 +183,13 @@ 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());
}
};
dragZoneSetOffset(offsetTop: number, offsetWidth: number, width: number, height: number) {
@ -211,7 +211,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
)
).fold(
(xml) => {
console.log(xml)
console.log(xml);
this.behaviorTreeModel.skills = this.filledOutTemplates;
this.behaviorTreeModel.scene = NodeBehaviorTree.fromReteScene(
this.editor as NodeEditor<Schemes>,
@ -356,29 +356,4 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
return this.filledOutTemplates.getCssAtLabel(label);
}
changeSceneViewModel = (text: string) => {};
modalShow = () => {
this.isModalOpen = true;
};
modalCancel = () => {
this.isModalOpen = false;
};
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) => {
await this.messageHttp(this.behaviorTreeBuilderHttpRepository.saveNewBt(model), {
successMessage: "Новое дерево создано",
});
await this.mapOk("btTreeModels", this.behaviorTreeBuilderHttpRepository.getAllBtInstances());
this.modalCancel();
},
async (error) => message.error(error)
);
goToBt = (id: string) => {
if (this.navigate) this.navigate(BehaviorTreeBuilderPath(id));
};
}

View file

@ -0,0 +1,56 @@
import React, { useEffect } from "react";
import { observer } from "mobx-react-lite";
import { FormBuilderStore } from "./form_builder_store";
import { IPropsForm } from "../forms";
import { useStore } from "../../../../../../core/helper/use_store";
import { isBtScreen } from "../../../behavior_tree_builder_screen";
import { CoreInput } from "../../../../../../core/ui/input/input";
import { CoreButton } from "../../../../../../core/ui/button/button";
import { Modal, message } from "antd";
import { FormBuilder } from "../../../../../../core/ui/form_builder/form_builder";
import { FormBuilderValidationModel } from "../../../../../../core/model/form_builder_validation_model";
export const FormBuilderForm = observer((props: IPropsForm<Partial<FormBuilderValidationModel>>) => {
const store = useStore(FormBuilderStore);
useEffect(() => {
store.initParam(isBtScreen(), props.store);
}, []);
return (
<div>
<div>FormBuilder</div>
{store.isBtScreen ? (
<>
<CoreInput label="Result" onChange={(text) => store.updateForm({ context: text })} />
<CoreInput label="Context" onChange={(text) => store.updateForm({ result: text })} />
<div style={{ width: 100 }}>
<CoreButton text="preview" onClick={() => store.openModal()} />
<CoreButton
text="save"
onClick={async () =>
(await store.viewModel.valid<FormBuilderValidationModel>()).fold(
(model) => props.onChange(model),
(error) => message.error(error)
)
}
/>
</div>
<Modal
destroyOnClose={true}
open={store.isModalOpen}
footer={null}
closable={false}
closeIcon={null}
onCancel={() => {
store.isModalOpen = false;
}}
>
<FormBuilder formBuilder={store.viewModel} onChange={() => {}} />
</Modal>
</>
) : (
<></>
)}
</div>
);
});

View file

@ -0,0 +1,23 @@
import makeAutoObservable from "mobx-store-inheritance";
import { NavigateFunction } from "react-router-dom";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { BehaviorTreeBuilderStore } from "../../../behavior_tree_builder_store";
import { FormBuilderValidationModel } from "../../../../../../core/model/form_builder_validation_model";
export class FormBuilderStore extends FormState<FormBuilderValidationModel, CoreError> {
openModal() {
this.isModalOpen = true;
}
isBtScreen = false;
isModalOpen = false;
viewModel: FormBuilderValidationModel = FormBuilderValidationModel.empty();
constructor() {
super();
makeAutoObservable(this);
}
errorHandingStrategy = (error: CoreError) => {};
init = async (navigate?: NavigateFunction | undefined) => {};
initParam = (isSkillScreen: boolean, store: BehaviorTreeBuilderStore | undefined) => {
this.isBtScreen = true;
};
}

View file

@ -1,11 +1,9 @@
import { FormBuilderValidationModel } from "../../../../../core/model/form_builder_validation_model";
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";
import { RobotDeviceForm } from "./robot_device_form/robot_device_form_form";
import { BehaviorTreeBuilderStore } from "../../behavior_tree_builder_store";
import { FormBuilderForm } from "./form_builder/form_builder_form";
import { TopicDependencyViewModel } from "./topics_form/topic_dependency_view_model";
import { TopicsForm } from "./topics_form/topics_form";
import { WeightsForm } from "./weights_form/weights_form";
export interface IPropsForm<T> {
dependency: T;
@ -22,6 +20,7 @@ export enum Form {
robotName = "robot_name",
cameraDeviceForm = "camera",
topic = "topic",
formBuilder = "formBuilder",
moveToPose = "move_to_pose",
}
export interface BtDependencyFormBuilder {
@ -35,15 +34,18 @@ export const btDependencyFormBuilder = (onChange: (dependency: DependencyViewMod
form: Form.topic,
component: <TopicsForm store={undefined} dependency={TopicDependencyViewModel.empty()} onChange={onChange} />,
},
{
form: Form.formBuilder,
component: (
<FormBuilderForm store={undefined} dependency={FormBuilderValidationModel.empty()} onChange={onChange} />
),
},
];
export const forms = (
props: any,
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.topic, component: <TopicsForm store={store} dependency={props} onChange={onChange} /> },
// { name: Form.moveToPose, component: <MoveToPose store={store} dependency={props} onChange={onChange} /> },
{ name: Form.formBuilder, component: <FormBuilderForm store={store} dependency={props} onChange={onChange} /> },
];

View file

@ -22,7 +22,7 @@ export class TopicDependencyViewModel extends ValidationModel implements Depende
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" />;
return <div style={{ color: "white" }}>{this.topicOut}</div>;
} else {
return <CoreText type={CoreTextType.header} text="Error" color="red" />;
}

View file

@ -33,7 +33,7 @@ export const TopicsForm = observer((props: IPropsForm<Partial<TopicDependencyVie
.with(StoreTopicType.btExecute, () => (
<>
<CoreText
text={"Выберите тип топика"}
text={"Выберите топик"}
type={CoreTextType.header}
style={{ padding: 10 }}
color={themeStore.theme.darkOnSurfaceVariant}

View file

@ -0,0 +1,10 @@
import { CoreHttpRepository, HttpMethod } from "../../core/repository/core_http_repository";
import { BehaviorTreeModel } from "../behavior_tree_builder/model/behavior_tree_model";
import { BehaviorTreeViewModel } from "../behavior_tree_builder/model/behavior_tree_view_model";
export class BehaviorTreeManagerHttpRepository extends CoreHttpRepository {
featureApi = `/behavior/trees`;
deleteBt = (id: string) => this._jsonRequest(HttpMethod.DELETE, `${this.featureApi}?id=${id}`);
saveNewBt = async (model: BehaviorTreeViewModel) => this._jsonRequest(HttpMethod.POST, this.featureApi, model);
getAllBtInstances = async () => this._jsonRequest<BehaviorTreeModel[]>(HttpMethod.GET, this.featureApi);
}

View file

@ -0,0 +1,100 @@
import { observer } from "mobx-react-lite";
import { BehaviorTreeManagerStore } from "./behavior_tree_manager_store";
import React from "react";
import { useStore } from "../../core/helper/use_store";
import { ButtonV2, ButtonV2Type } from "../../core/ui/button/button_v2";
import { CoreCard } from "../../core/ui/card/card";
import { Icon } from "../../core/ui/icons/icons";
import { themeStore } from "../..";
import { InputV2 } from "../../core/ui/input/input_v2";
import { CoreModal } from "../../core/ui/modal/modal";
import { SelectV2 } from "../../core/ui/select/select_v2";
import { CoreText, CoreTextType } from "../../core/ui/text/text";
import { MainPageV2 } from "../../core/ui/pages/main_page_v2";
export const BehaviorTreeManagerScreenPath = "/behavior/tree/manager";
export const BehaviorTreeManagerScreen = observer(() => {
const store = useStore(BehaviorTreeManagerStore);
return (
<>
<MainPageV2
children={
<>
<div style={{ height: "100%", overflowY: "auto", overflowX: "hidden" }}>
<div style={{ height: 20 }} />
<ButtonV2
icon={<Icon type={"Plus"} style={{ alignSelf: "center", marginLeft: 10, marginRight: 10 }} />}
text="СОЗДАТЬ ДЕРЕВО ПОВЕДЕНИЯ"
onClick={() => store.modalShow()}
/>
<div style={{ height: 50 }} />
<div style={{ display: "inline-grid", gridTemplateColumns: "1fr 1fr 1fr", width: "100%" }}>
{store.btTreeModels?.map((el, index) => (
<CoreCard
key={index}
clickDeleteIcon={() => store.deleteBt(el._id)}
clickGoToIcon={() => store.goToBt(el._id)}
descriptionMiddle={el.description}
descriptionTop={el.name}
date={el.unixTime}
/>
))}
</div>
</div>
<CoreModal
isOpen={store.isModalOpen}
onClose={() => store.modalCancel()}
children={
<>
<div style={{ height: 30 }} />
<CoreText
text="Создание дерева поведения"
type={CoreTextType.header}
color={themeStore.theme.white}
style={{ textAlignLast: "center" }}
/>
<div style={{ height: 30 }} />
<div
style={{
height: 1,
width: "calc(100% - 30px)",
backgroundColor: themeStore.theme.outlineVariant,
}}
/>
<div style={{ height: 20 }} />
<InputV2 trim={true} label={"Название"} onChange={(text) => store.updateForm({ name: text })} />
<div style={{ height: 20 }} />
<InputV2 trim={true} label={"Описание"} onChange={(text) => store.updateForm({ description: text })} />
<div style={{ height: 20 }} />
<SelectV2
items={store.scenes?.map((el) => ({ name: el.name, value: el._id })) ?? []}
initialValue={""}
label={"Сцена"}
onChange={(value: string) => store.updateForm({ sceneId: value })}
/>
<div style={{ height: 30 }} />
<div
style={{
height: 1,
width: "calc(100% - 30px)",
backgroundColor: themeStore.theme.outlineVariant,
}}
/>
<div style={{ display: "flex", paddingTop: 20, justifyContent: "center" }}>
<ButtonV2 text="Сохранить" onClick={() => store.saveNewBt()} type={ButtonV2Type.default} />
<div style={{ width: 20 }} />
<ButtonV2 text="Отменить" onClick={() => store.modalCancel()} type={ButtonV2Type.default} />
</div>
</>
}
/>
</>
}
bgColor={themeStore.theme.black}
/>
</>
);
});

View file

@ -0,0 +1,60 @@
import makeAutoObservable from "mobx-store-inheritance";
import { UiDrawerFormState, CoreError } from "../../core/store/base_store";
import { BehaviorTreeViewModel } from "../behavior_tree_builder/model/behavior_tree_view_model";
import { NavigateFunction } from "react-router-dom";
import { BehaviorTreeBuilderPath } from "../behavior_tree_builder/presentation/behavior_tree_builder_screen";
import { BehaviorTreeModel } from "../behavior_tree_builder/model/behavior_tree_model";
import { SceneModel } from "../scene_manager/model/scene_model";
import { BehaviorTreeManagerHttpRepository } from "./behavior_tree_manager_repository";
import { message } from "antd";
export enum DrawerState {
newBehaviorTree = "Новое дерево поведения",
}
export class BehaviorTreeManagerStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> {
viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty();
navigate?: NavigateFunction;
btTreeModels: BehaviorTreeModel[] = [];
scenes?: SceneModel[];
behaviorTreeManagerHttpRepository = new BehaviorTreeManagerHttpRepository();
isModalOpen: boolean = false;
activeProject?: string;
constructor() {
super(DrawerState);
makeAutoObservable(this);
}
init = async (navigate?: NavigateFunction | undefined): Promise<void> => {
this.navigate = navigate;
(await this.behaviorTreeManagerHttpRepository.getActiveProjectId()).map((el) => {
this.activeProject = el.id;
this.viewModel.project = this.activeProject;
});
await this.mapOk("btTreeModels", this.behaviorTreeManagerHttpRepository.getAllBtInstances());
await this.mapOk("scenes", this.behaviorTreeManagerHttpRepository.getAllScenes());
};
deleteBt = async (id: string) => (
await this.behaviorTreeManagerHttpRepository.deleteBt(id),
await this.mapOk("btTreeModels", this.behaviorTreeManagerHttpRepository.getAllBtInstances())
);
saveNewBt = async () =>
(await this.viewModel.valid<BehaviorTreeViewModel>()).fold(
async (model) => {
await this.messageHttp(this.behaviorTreeManagerHttpRepository.saveNewBt(model), {
successMessage: "Новое дерево создано",
});
await this.mapOk("btTreeModels", this.behaviorTreeManagerHttpRepository.getAllBtInstances());
},
async (error) => message.error(error)
);
goToBt = (id: string) => {
console.log(this.navigate);
if (this.navigate) this.navigate(BehaviorTreeBuilderPath(id));
};
modalShow = () => {
this.isModalOpen = true;
};
modalCancel = () => {
this.isModalOpen = false;
};
}

View file

@ -0,0 +1,11 @@
import { Result } from "../../core/helper/result";
export class NumberTriviaModel {
constructor() {}
isValid(): Result<string, void> {
return Result.ok();
}
static empty() {
return new NumberTriviaModel();
}
}

View file

@ -2,7 +2,7 @@ import { TypedEvent } from "../../../core/helper/typed_event";
import { SocketRepository, socketRepository } from "../../../core/repository/core_socket_repository";
import { ProcessStatus } from "../../dataset/dataset_model";
export interface ProcessUpdate {
export interface ProcessUpdate {
id: string;
status: ProcessStatus;
}

View file

@ -1,6 +1,6 @@
import { FormBuilderValidationModel } from "../../dataset/dataset_model";
import { IsNotEmpty, IsString } from "class-validator";
import { IsNotEmpty, IsString } from "class-validator";
import { ValidationModel } from "../../../core/model/validation_model";
import { FormBuilderValidationModel } from "../../../core/model/form_builder_validation_model";
export enum ModelMachineLearningTypes {
OBJECT_DETECTION = "OBJECT_DETECTION",

View file

@ -13,8 +13,8 @@ import { FormBuilder } from "../../../core/ui/form_builder/form_builder";
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";
import { useStore } from "../../../core/helper/use_store";
import { FormBuilderValidationModel } from "../../../core/model/form_builder_validation_model";
interface IItem {
name: string;

View file

@ -1,5 +1,6 @@
import { Result } from "../../core/helper/result";
import makeAutoObservable from "mobx-store-inheritance";
import { FormBuilderValidationModel } from "../../core/model/form_builder_validation_model";
export enum ProcessStatus {
END = "END",
@ -37,52 +38,7 @@ export interface Asset {
image: string;
}
export class FormBuilderValidationModel {
public result: string;
public context: string;
public form: string[];
public output: any;
constructor(context: string, result: string, form: string[], output: string) {
this.context = context;
this.result = result;
this.form = form;
this.output = output;
}
static isEmpty = (formBuilderValidationModel: FormBuilderValidationModel) =>
formBuilderValidationModel.context.isEmpty() &&
formBuilderValidationModel.result.isEmpty() &&
formBuilderValidationModel.form.isEmpty();
static datasetEmpty() {
return new FormBuilderValidationModel(datasetFormMockContext, datasetFormMockResult, [], defaultFormValue);
}
static empty() {
return new FormBuilderValidationModel("", "", [], "");
}
static emptyTest() {
return new FormBuilderValidationModel(``, ``, [], defaultFormValue);
}
static creteDataSetTest() {
return new FormBuilderValidationModel(``, scene, [], "");
}
static vision(): FormBuilderValidationModel {
return new FormBuilderValidationModel(
`ENUM PRETRAIN = "true","false";`,
`{
"numberOfEpochs": \${numberOfEpochs:number:10},
"selectDataset": \${<SelectDataset/>:OBJECT:{"dataset": {}},
"pretrain": \${pretrain:Enum<PRETRAIN>:true}
}`,
[],
""
);
}
}
export const scene = `{
"center_shell": [\${CENTER_SHELL_1:number:0}, \${CENTER_SHELL_2:number:0}, \${CENTER_SHELL_3:number:0}],
"scene":\${<SelectScene/>:OBJECT:{"details": []}
}`;
export class DataSetModel {
dataSetObjects: string[];
datasetType: string;
@ -209,5 +165,3 @@ export const defaultFormValue: any = {
camera_position: { center_shell: [0, 0, 0], radius_range: [1, 1.4], elevation_range: [10, 90] },
generation: { n_cam_pose: 5, n_sample_on_pose: 3, n_series: 100, image_format: "JPEG", image_size_wh: [640, 480] },
};

View file

@ -1,8 +1,8 @@
import { ValidationModel } from "../../core/model/validation_model";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmpty, IsString } from "class-validator";
import { FormBuilderValidationModel } from "../dataset/dataset_model";
import { FormBuilderValidationModel } from "../../core/model/form_builder_validation_model";
export enum DigitalTwinsTypes {
CAMERA = "CAMERA",
ROBOT = "ROBOT",

View file

@ -1,4 +1,3 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { DrawersSkills, SkillsStore } from "./skills_store";
import { Drawer, Modal } from "antd";
@ -73,44 +72,58 @@ export const SkillsScreen = observer(() => {
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}>
<div>
<CoreInput
trim={true}
label={"name"}
onChange={(text) =>
store.updateForm({ SkillPackage: Object.assign(store.viewModel.SkillPackage, { name: text }) })
}
/>
<CoreInput
trim={true}
label={"format"}
onChange={(text) =>
store.updateForm({ SkillPackage: Object.assign(store.viewModel.SkillPackage, { format: text }) })
}
/>
<CoreInput
trim={true}
label="version"
onChange={(text) =>
store.updateForm({ SkillPackage: Object.assign(store.viewModel.SkillPackage, { version: text }) })
}
/>
<CoreInput
trim={true}
label={"Module description"}
onChange={(text) =>
store.updateForm({ Module: Object.assign(store.viewModel.Module, { description: text }) })
}
/>
<CoreInput
trim={true}
label={"Module name"}
onChange={(text) => store.updateForm({ Module: Object.assign(store.viewModel.Module, { name: text }) })}
/>
<CoreInput
<CoreInput
trim={true}
label={"Module node name"}
onChange={(text) => store.updateForm({ Module: Object.assign(store.viewModel.Module, { node_name: text }) })}
onChange={(text) =>
store.updateForm({ Module: Object.assign(store.viewModel.Module, { node_name: text }) })
}
/>
<CoreInput
trim={true}
label="Launch package"
onChange={(text) => store.updateForm({ Launch: Object.assign(store.viewModel.Launch, { package: text }) })}
onChange={(text) =>
store.updateForm({ Launch: Object.assign(store.viewModel.Launch, { package: text }) })
}
/>
<CoreInput
trim={true}
label="Launch executable"
onChange={(text) => store.updateForm({ Launch: Object.assign(store.viewModel.Launch, { executable: text }) })}
onChange={(text) =>
store.updateForm({ Launch: Object.assign(store.viewModel.Launch, { executable: text }) })
}
/>
<CoreText
@ -122,6 +135,7 @@ export const SkillsScreen = observer(() => {
{store.viewModel.topicsOut.map((el, index) => (
<div key={index} style={{ marginTop: 10 }}>
<CoreInput
trim={true}
value={el.name}
label={"name"}
onChange={(text) =>
@ -129,6 +143,7 @@ export const SkillsScreen = observer(() => {
}
/>
<CoreInput
trim={true}
value={el.type}
label={"type"}
onChange={(text) =>
@ -146,6 +161,7 @@ export const SkillsScreen = observer(() => {
{store.viewModel.BTAction.map((el, index) => (
<div key={index} style={{ marginTop: 10 }}>
<CoreInput
trim={true}
value={el.name}
label={"name"}
onChange={(text) =>
@ -153,20 +169,14 @@ export const SkillsScreen = observer(() => {
}
/>
<CoreInput
trim={true}
value={el.type}
label={"type"}
onChange={(text) =>
store.updateForm({ BTAction: store.viewModel.BTAction.replacePropIndex({ type: text }, index) })
}
/>
<CoreInput
value={el.format}
label={"format"}
onChange={(text) =>
store.updateForm({ BTAction: store.viewModel.BTAction.replacePropIndex({ format: text }, index) })
}
/>
<CoreSelect
items={Object.keys(BtAction)}
value={el.typeAction}
@ -214,3 +224,4 @@ export const SkillsScreen = observer(() => {
</>
);
});