diff --git a/server/src/features/behavior_trees/behavior_trees_presentation.ts b/server/src/features/behavior_trees/behavior_trees_presentation.ts index dd1618c..1ea112a 100644 --- a/server/src/features/behavior_trees/behavior_trees_presentation.ts +++ b/server/src/features/behavior_trees/behavior_trees_presentation.ts @@ -5,6 +5,9 @@ import { BehaviorTreeDBModel } from "./models/behavior_tree_database_model"; import { ReadByIdDataBaseModelScenario } from "../../core/scenarios/read_by_id_database_model_scenario"; import { GetBehaviorTreeActiveProjectScenario } from "./domain/get_behavior_tree_active_project_scenario"; import { SaveBtScenario as FillBtScenario } from "./domain/save_bt_scenario"; +import { GetCameraUseCase } from "./domain/get_cameras_usecase"; +import { GetRobotsUseCase } from "./domain/get_robots_usecase"; +import { GetTopicsUseCase } from "./domain/get_topics_usecase"; export class BehaviorTreesPresentation extends CrudController { constructor() { @@ -25,5 +28,21 @@ export class BehaviorTreesPresentation extends CrudController(BehaviorTreeDBModel), }); + this.subRoutes.push({ + method: "GET", + subUrl: "cameras", + fn: new GetCameraUseCase() + }) + this.subRoutes.push({ + method: "GET", + subUrl: "robots", + fn: new GetRobotsUseCase() + }) + this.subRoutes.push({ + method: "GET", + subUrl: "topics", + fn: new GetTopicsUseCase() + }) } } + diff --git a/server/src/features/behavior_trees/domain/get_bt_skills_templates_usecase.ts b/server/src/features/behavior_trees/domain/get_bt_skills_templates_usecase.ts index cb5e367..558f95f 100644 --- a/server/src/features/behavior_trees/domain/get_bt_skills_templates_usecase.ts +++ b/server/src/features/behavior_trees/domain/get_bt_skills_templates_usecase.ts @@ -6,7 +6,7 @@ export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithE return Result.ok({ skills: [ { - SkillPackage: { name: "Robossembler", version: "1.0", format: "1" }, + SkillPackage: { name: "Robossembler", version: "1.0", format: "1", type: "Action" }, Module: { name: "PoseEstimation", description: "Pose Estimation skill with DOPE" }, Launch: { package: "rbs_perception", executable: "pe_dope_lc.py", name: "lc_dope" }, BTAction: [ @@ -18,6 +18,14 @@ export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithE type: "weights", dependency: {}, }, + { + type: "camera", + dependency: {} + }, + { + type: "topic", + dependency: {} + } ], result: ["POSE"], }, @@ -38,6 +46,28 @@ export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithE { name: "mesh_scale", value: 0.001 }, ], }, + { + SkillPackage: { name: "Robossembler", version: "1.0", format: "1" }, + Module: { name: "MoveToPose", description: "Move to Pose skill with ? controllers" }, + Launch: { package: "rbss_movetopose", executable: "movetopose.py", name: "skill_move" }, + BTAction: [ + { + name: "move", + type: "action", + param: [{ type: "topic", dependency: { } }], + result: [] + } + ], + Interface: { + Input: [ + { name: "robotName", type: "ROBOT" }, + { name: "pose", type: "POSE" } + ], + Output: [] + }, + Settings: [] + } + ], }); }; diff --git a/server/src/features/behavior_trees/domain/get_cameras_usecase.ts b/server/src/features/behavior_trees/domain/get_cameras_usecase.ts new file mode 100644 index 0000000..a1ea345 --- /dev/null +++ b/server/src/features/behavior_trees/domain/get_cameras_usecase.ts @@ -0,0 +1,120 @@ +import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller" +import { Result } from "../../../core/helpers/result" + +export class GetCameraUseCase extends CallbackStrategyWithEmpty { + call = async (): ResponseBase => { + return Result.ok({ + "camera": [ + { + "sid": "1", + "DevicePackage": { + "name": "Robossembler", + "version": "1.0", + "format": "1" + }, + "Module": { + "name": "RealSense Dxx", + "description": "ROS Wrapper for Intel(R) RealSense(TM) Cameras" + }, + "Launch": { + "package": "realsense2_camera", + "executable": "rs_launch.py" + }, + "DTwin": [ + { + "interface": { + "input": [ + { + "camera_namespace": "robot1" + }, + { + "camera_name": "455_1" + }, + { + "serial_port": "USB_1" + }, + { + "pose": "[0.0,0.0,0.0,0.0,0.0,0.0]" + } + ] + } + } + ], + "Interface": { + "param": [ + { + "type": "camera", + "dependency": {} + } + ] + }, + "Settings": [ + { + "name": "camera_config", + "description": "Camera Config", + "type": "file", + "defaultValue": "{ rgb_camera.profile: 1280x720x15 }" + } + ] + }, + { + "sid": "2", + "DevicePackage": { + "name": "Robossembler", + "version": "1.0", + "format": "1" + }, + "Module": { + "name": "RealSense Dxx", + "description": "ROS Wrapper for Intel(R) RealSense(TM) Cameras" + }, + "Launch": { + "package": "realsense2_camera", + "executable": "rs_launch.py" + }, + + "DTwin": [ + { + "interface": { + "input": [ + { + "camera_namespace": "robot1" + }, + { + "camera_name": "455_1" + }, + { + "serial_port": "USB_1" + }, + { + "pose": "[0.0,0.0,0.0,0.0,0.0,0.0]" + } + ] + } + } + ], + "Interface": { + "param": [ + { + "type": "camera", + "dependency": {} + } + ] + }, + "Settings": [ + { + "name": "camera_config", + "description": "Camera Config", + "type": "file", + "defaultValue": "{ rgb_camera.profile: 1280x720x15 }" + } + ] + } + ] + }) + } + +} + + + \ No newline at end of file diff --git a/server/src/features/behavior_trees/domain/get_robots_usecase.ts b/server/src/features/behavior_trees/domain/get_robots_usecase.ts new file mode 100644 index 0000000..ce3ced1 --- /dev/null +++ b/server/src/features/behavior_trees/domain/get_robots_usecase.ts @@ -0,0 +1,50 @@ +import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller"; +import { Result } from "../../../core/helpers/result"; + +export class GetRobotsUseCase extends CallbackStrategyWithEmpty { + call = async (): ResponseBase => { + return Result.ok({ + "robots": [ + { + "sid": "1", + "DevicePackage": { + "name": "Robossembler", + "version": "1.0", + "format": "1" + }, + "Module": { + "name": "RBS", + "description": "Main Robot" + }, + "Launch": { + "package": "rbs_bringup", + "executable": "single_robot.launch.py" + }, + "DTwin": [ + { + "interface": { + "input": [ + { + "robot_namespace": "robot1" + }, + { + "dof": 6 + } + ] + } + } + ], + "Settings": [ + { + "name": "robot_type", + "description": "Type of robot by name", + "defaultValue": "rbs_arm" + } + ] + } + ] + }) + } + +} + diff --git a/server/src/features/behavior_trees/domain/get_topics_usecase.ts b/server/src/features/behavior_trees/domain/get_topics_usecase.ts new file mode 100644 index 0000000..55b1bd4 --- /dev/null +++ b/server/src/features/behavior_trees/domain/get_topics_usecase.ts @@ -0,0 +1,23 @@ +import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller"; +import { Result } from "../../../core/helpers/result"; + +export class GetTopicsUseCase extends CallbackStrategyWithEmpty { + call = async (): ResponseBase => { + return Result.ok({ + "topics": [ + { + "sid": "1", + "name": "topic camera 1", + "type": "sensor_msgs/Image" + }, + { + "sid": "2", + "name": "topic camera 2", + "type": "sensor_msgs/Image" + } + ] + } + ) + + } +} \ No newline at end of file diff --git a/ui/.mason/bricks.json b/ui/.mason/bricks.json new file mode 100644 index 0000000..e7243ef --- /dev/null +++ b/ui/.mason/bricks.json @@ -0,0 +1 @@ +{"base_feature":"/Users/idontsudo/ozon/ui/bricks/base_feature","form":"/Users/idontsudo/webservice/ui/bricks/form"} \ No newline at end of file diff --git a/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/number_trivia.ts b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/number_trivia.ts new file mode 100644 index 0000000..d017d38 --- /dev/null +++ b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/number_trivia.ts @@ -0,0 +1,11 @@ +import { Result } from "../../core/helper/result"; + +export class NumberTriviaModel { + constructor() {} + isValid(): Result { + return Result.ok(); + } + static empty() { + return new NumberTriviaModel(); + } +} diff --git a/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_repository.ts b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_repository.ts new file mode 100644 index 0000000..c210d0a --- /dev/null +++ b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_repository.ts @@ -0,0 +1,3 @@ +export class {{name.pascalCase()}}Repository { + +} diff --git a/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen.tsx b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen.tsx new file mode 100644 index 0000000..9e70c80 --- /dev/null +++ b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_screen.tsx @@ -0,0 +1,10 @@ +import { observer } from "mobx-react-lite"; +import { {{name.pascalCase()}}Store } from "./{{name.snakeCase()}}_store"; +import React from "react"; + +export const {{name.pascalCase()}}ScreenPath = "/auth"; + +export const {{name.pascalCase()}}Screen = observer(() => { + const [store] = React.useState(() => new {{name.pascalCase()}}Store()); + return <>; +}); diff --git a/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_store.ts b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_store.ts new file mode 100644 index 0000000..a76f1a8 --- /dev/null +++ b/ui/bricks/base_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_store.ts @@ -0,0 +1,7 @@ +import makeAutoObservable from "mobx-store-inheritance"; + +export class {{name.pascalCase()}}Store { + constructor() { + makeAutoObservable(this); + } +} diff --git a/ui/bricks/base_feature/brick.yaml b/ui/bricks/base_feature/brick.yaml new file mode 100644 index 0000000..98705d5 --- /dev/null +++ b/ui/bricks/base_feature/brick.yaml @@ -0,0 +1,16 @@ +name: base_feature +description: A brick to create Clean Architecture Feature + +version: 0.0.1 + +# The following defines the environment for the current brick. +# It includes the version of mason that the brick requires. +environment: + mason: ">=0.1.0-dev.26 <0.1.0" + +vars: + name: + type: string + description: The feature name + default: my feature + prompt: What's your feature name (e.g. dance school) \ No newline at end of file diff --git a/ui/bricks/form/__brick__/{{name.snakeCase()}}/number_trivia.ts b/ui/bricks/form/__brick__/{{name.snakeCase()}}/number_trivia.ts new file mode 100644 index 0000000..0f1ae90 --- /dev/null +++ b/ui/bricks/form/__brick__/{{name.snakeCase()}}/number_trivia.ts @@ -0,0 +1,11 @@ +import { Result } from "../../../../../../core/helper/result"; + +export class NumberTriviaModel { + constructor() {} + isValid(): Result { + return Result.ok(); + } + static empty() { + return new NumberTriviaModel(); + } +} diff --git a/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_form.tsx b/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_form.tsx new file mode 100644 index 0000000..d6420f8 --- /dev/null +++ b/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_form.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { observer } from "mobx-react-lite"; +import { {{ name.pascalCase() }}Store } from "./{{name.snakeCase()}}_store"; +import { IPropsForm } from "../forms"; +import { NumberTriviaModel } from "./number_trivia"; + +export const {{ name.pascalCase()}} = observer((props: IPropsForm>) => { + const [store] = React.useState(() => new {{ name.pascalCase() }}Store()); + React.useEffect(() => { + store.init(); + }, [store]); + + return <>; +}); diff --git a/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_http_repository.ts b/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_http_repository.ts new file mode 100644 index 0000000..fe4a0a5 --- /dev/null +++ b/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_http_repository.ts @@ -0,0 +1,5 @@ +import { HttpRepository } from "../../../../../../core/repository/http_repository"; + +export class {{name.pascalCase()}}HttpRepository extends HttpRepository { + +} diff --git a/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_store.ts b/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_store.ts new file mode 100644 index 0000000..8748265 --- /dev/null +++ b/ui/bricks/form/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}_store.ts @@ -0,0 +1,19 @@ +import makeAutoObservable from "mobx-store-inheritance"; +import { NavigateFunction } from "react-router-dom"; +import { NumberTriviaModel } from "./number_trivia"; +import { FormState, CoreError } from "../../../../../../core/store/base_store"; +import { {{name.pascalCase()}}HttpRepository } from "./{{name.snakeCase()}}_http_repository"; + +export class {{name.pascalCase()}}Store extends FormState { + constructor() { + super(); + makeAutoObservable(this); + } + viewModel: NumberTriviaModel = NumberTriviaModel.empty(); + cameraDeviceHttpRepository: {{name.pascalCase()}}HttpRepository = new {{name.pascalCase()}}HttpRepository(); + errorHandingStrategy = (error: CoreError) => { } + init = async (navigate?: NavigateFunction | undefined) => { + + } + +} diff --git a/ui/bricks/form/brick.yaml b/ui/bricks/form/brick.yaml new file mode 100644 index 0000000..348e9bc --- /dev/null +++ b/ui/bricks/form/brick.yaml @@ -0,0 +1,16 @@ +name: form +description: A brick to create form at behavior_tree_builder + +version: 0.0.1 + +# The following defines the environment for the current brick. +# It includes the version of mason that the brick requires. +environment: + mason: ">=0.1.0-dev.26 <0.1.0" + +vars: + name: + type: string + description: The feature name + default: my feature + prompt: What's your feature name (e.g. dance school) \ No newline at end of file diff --git a/ui/mason-lock.json b/ui/mason-lock.json new file mode 100644 index 0000000..9650a34 --- /dev/null +++ b/ui/mason-lock.json @@ -0,0 +1 @@ +{"bricks":{"base_feature":{"path":"bricks/base_feature"},"form":{"path":"bricks/form"}}} \ No newline at end of file diff --git a/ui/mason.yaml b/ui/mason.yaml new file mode 100644 index 0000000..68ec44a --- /dev/null +++ b/ui/mason.yaml @@ -0,0 +1,5 @@ +bricks: + base_feature: + path: base_feature + form: + path: form \ No newline at end of file diff --git a/ui/readme.md b/ui/readme.md new file mode 100644 index 0000000..2052a3f --- /dev/null +++ b/ui/readme.md @@ -0,0 +1,46 @@ +# Зависимости + +brew install mason + +### Инициализация + +В корне проекта вызовите команду `init`, которая создаст папку `.mason/` + +``` +mason init +``` + +### Использование 👷 + +Все готовые `brick'и` хранятся в папке `/bricks` + +Для примера попробуем использовать `brick` под названием `base_feature` + +``` +# Добавляем `brick` себе в mason (посмотреть уже добавленные можно через `mason ls/list`, а удалить через `mason remove`) +mason add base_feature --path bricks/base_feature + +# Используем `brick` для в интересующей нас папке (поскольку это шаблон фичи, выбрана папка src/features/) +mason make base_feature -o lib/features +``` + +Далее необходимо ответить на вопросы задаваемые в CLI и на основе ответов `brick` сгенериует фичу + +### Разработка + +В папке `bricks/` вы можете создать свой `brick` + +``` +# создать hello brick +mason new my_brick_name +``` + +Далее всю структуру папок и файлов необходимо описать в папке `__brick__` + +Для большей информации [читайте и смотрите примеры](https://github.com/felangel/mason/blob/master/packages/mason_cli/README.md) + + + +# Добавить новую форму +mason make form -o ./src/features/behavior_tree_builder/presentation/ui/forms +# Добавить новый экран diff --git a/ui/src/core/model/cameras.ts b/ui/src/core/model/cameras.ts new file mode 100644 index 0000000..84fd0ef --- /dev/null +++ b/ui/src/core/model/cameras.ts @@ -0,0 +1,63 @@ +export interface Cameras { + camera: Camera[]; +} + +export interface Camera { + sid: string; + DevicePackage: DevicePackage; + Module: Module; + Launch: Launch; + DTwin: DTwin[]; + Interface: InterfaceClass; + Settings: Setting[]; +} + +export interface DTwin { + interface: Interface; +} + +export interface Interface { + input: Input[]; +} + +export interface Input { + camera_namespace?: string; + camera_name?: string; + serial_port?: string; + pose?: string; +} + +export interface DevicePackage { + name: string; + version: string; + format: string; +} + +export interface InterfaceClass { + param: Param[]; +} + +export interface Param { + type: string; + dependency: Dependency; +} + +export interface Dependency { +} + +export interface Launch { + package: string; + executable: string; +} + +export interface Module { + name: string; + description: string; +} + +export interface Setting { + name: string; + description: string; + type: string; + defaultValue: string; +} diff --git a/ui/src/core/model/device_dependency_view_model.ts b/ui/src/core/model/device_dependency_view_model.ts new file mode 100644 index 0000000..124b879 --- /dev/null +++ b/ui/src/core/model/device_dependency_view_model.ts @@ -0,0 +1,18 @@ +import { Result } from "../helper/result"; +import { IDeviceDependency } from "./skill_model"; + +export class SidViewModel implements IDeviceDependency { + sid: string; + constructor(sid: string) { + this.sid = sid; + } + valid = (): Result => { + if (this.sid.isEmpty()) { + return Result.error('sid is empty') + } + return Result.ok(this) + } + static empty() { + return new SidViewModel('') + } +} \ No newline at end of file diff --git a/ui/src/core/model/robots.ts b/ui/src/core/model/robots.ts new file mode 100644 index 0000000..41c14fb --- /dev/null +++ b/ui/src/core/model/robots.ts @@ -0,0 +1,47 @@ +export interface Robots { + robots: Robot[]; +} + +export interface Robot { + sid: string; + DevicePackage: DevicePackage; + Module: Module; + Launch: Launch; + DTwin: DTwin[]; + Settings: Setting[]; +} + +export interface DTwin { + interface: Interface; +} + +export interface Interface { + input: Input[]; +} + +export interface Input { + robot_namespace?: string; + dof?: number; +} + +export interface DevicePackage { + name: string; + version: string; + format: string; +} + +export interface Launch { + package: string; + executable: string; +} + +export interface Module { + name: string; + description: string; +} + +export interface Setting { + name: string; + description: string; + defaultValue: string; +} diff --git a/ui/src/core/model/skill_model.ts b/ui/src/core/model/skill_model.ts index 57de3fa..8056bce 100644 --- a/ui/src/core/model/skill_model.ts +++ b/ui/src/core/model/skill_model.ts @@ -26,11 +26,14 @@ export interface ISkill { xxx: IXxx; } export interface IWeightsDependency { - weights_name:string; + weights_name: string; object_name: string; weights_file: string; dimensions: number[]; } +export interface IDeviceDependency { + sid: string; +} export interface IParam { type: string; @@ -195,7 +198,7 @@ export class SkillModel implements ISkill { } export class SkillDependency implements IDependency { - constructor(public skills: ISkillDependency[]) {} + constructor(public skills: ISkillDependency[]) { } static empty() { return new SkillDependency([]); } diff --git a/ui/src/core/model/topics.ts b/ui/src/core/model/topics.ts new file mode 100644 index 0000000..3612603 --- /dev/null +++ b/ui/src/core/model/topics.ts @@ -0,0 +1,9 @@ +export interface Topics { + topics: Topic[]; +} + +export interface Topic { + sid: string; + name: string; + type: string; +} diff --git a/ui/src/core/store/base_store.ts b/ui/src/core/store/base_store.ts index 706d3ce..13792a1 100644 --- a/ui/src/core/store/base_store.ts +++ b/ui/src/core/store/base_store.ts @@ -44,10 +44,10 @@ export abstract class UiLoader { }; messageHttp = async (callBack: Promise>, report?: IMessage) => { return (await this.httpHelper(callBack)).fold( - (s) => { + (_s) => { if (report && report.successMessage) message.success(report.successMessage); }, - (e) => { + (_e) => { if (report && report.errorMessage) message.error(report.errorMessage); } ); diff --git a/ui/src/core/ui/text/text.tsx b/ui/src/core/ui/text/text.tsx index faa8847..5ea6030 100644 --- a/ui/src/core/ui/text/text.tsx +++ b/ui/src/core/ui/text/text.tsx @@ -1,81 +1,75 @@ import * as React from "react"; +import { IStyle } from "../../model/style"; export enum CoreTextType { - header, - medium, - large, - small, + header = 'header', + medium = 'medium', + large = 'large', + small = 'small', } -export interface ITextProps { +export interface ITextProps extends IStyle { text: string; type: CoreTextType; color?: string; } -export function CoreText(props: ITextProps) { - if (props.type === CoreTextType.small) { - return ( -
- {props.text} -
- ); +const getStyle = (type: CoreTextType, color: string | undefined) => { + if (type.isEqual(CoreTextType.small)) return { + color: color ?? "rgba(73, 69, 79, 1)", + fontSize: 12, + fontFamily: "Roboto", + fontWeight: 400, + fontSizeAdjust: 14, + textOverflow: "ellipsis", } - if (props.type === CoreTextType.large) { - return ( -
- {props.text} -
- ); - } - if (props.type === CoreTextType.medium) - return ( -
- {props.text} -
- ); - if (props.type === CoreTextType.header) - return ( -
- {props.text} -
- ); - return
{props.text}
; + if (type.isEqual(CoreTextType.large)) return { + color: color ?? "#1D1B20", + fontSize: 16, + fontFamily: "Roboto", + fontWeight: 400, + fontSizeAdjust: 14, + textOverflow: "ellipsis", + } + if (type.isEqual(CoreTextType.medium)) return { + color: color ?? "#1D1B20", + fontSize: 14, + fontFamily: "Roboto", + fontWeight: 400, + textOverflow: "ellipsis", + fontSizeAdjust: 14, + } + if (type.isEqual(CoreTextType.header)) return { + color: color ?? "#1D1B20", + fontSize: 20, + fontFamily: "Roboto", + fontWeight: 500, + textOverflow: "ellipsis", + + fontSizeAdjust: 16, + } + return { + color: color ?? "rgba(73, 69, 79, 1)", + fontSize: 12, + fontFamily: "Roboto", + fontWeight: 400, + fontSizeAdjust: 14, + textOverflow: "ellipsis", + } +} +const appendStyle = (type: CoreTextType, color: string | undefined, style: React.CSSProperties | undefined) => { + return Object.assign(getStyle(type, color), style) +} +export function CoreText(props: ITextProps) { + return ( +
+ {props.text} +
+ ); + + + } diff --git a/ui/src/features/behavior_tree_builder/model/primitive_view_model.ts b/ui/src/features/behavior_tree_builder/model/primitive_view_model.ts new file mode 100644 index 0000000..9de192b --- /dev/null +++ b/ui/src/features/behavior_tree_builder/model/primitive_view_model.ts @@ -0,0 +1,80 @@ +import { extensions } from "../../../core/extensions/extensions"; +import { Form } from "../presentation/ui/forms/forms"; +import { ISkillView } from "../presentation/ui/skill_tree/skill_tree"; + +extensions(); + +export enum SystemPrimitive { + Sequence = 'Sequence', + Fallback = "Fallback", + ReactiveSequence = "ReactiveSequence", + SequenceWithMemory = 'SequenceWithMemory', + ReactiveFallback = "ReactiveFallback", + Decorator = "Decorator", + Inverter = "Inverter", + ForceSuccess = "ForceSuccess", + ForceFailure = "ForceFailure", + Repeat = "Repeat", + RetryUntilSuccessful = "RetryUntilSuccessful", + KeepRunningUntilFailure = "KeepRunningUntilFailure", + Delay = "Delay", + RunOnce = "RunOnce", +} +interface ISystemPrimitive { + label: string; + group: string; + css?: React.CSSProperties; + input: boolean; + output: boolean; + +} + + +export class PrimitiveViewModel { + primitives: ISystemPrimitive[] = [ + { + label: SystemPrimitive.Inverter, + group: SystemPrimitive.Decorator, + input: true, + output: true, + css: { + border: "1px solid #003E61", + borderRadius: 33, + background: "#BDE8AE", + }, + }, + { + label: SystemPrimitive.ForceSuccess, + group: SystemPrimitive.Decorator, + input: true, + output: true, + css: { + border: "1px solid #003E61", + borderRadius: 33, + background: "#BDE8AE", + }, + }, + { 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)); + } + toSkillView = () => this.primitives.reduce((acc, primitive) => { + acc.rFind((el) => el.name.isEqual(primitive.group)).fold(() => { + acc.at(acc.findIndex((eeee) => eeee.name === primitive.group))?.children?.push({ name: primitive.label }) + }, () => { acc.push({ name: primitive.group, children: [{ name: primitive.label }] }) }) + return acc; + }, []) + +} \ No newline at end of file diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx index fc57998..54ef37f 100644 --- a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx @@ -169,3 +169,4 @@ export const BehaviorTreeBuilderScreen = observer(() => { /> ); }); + \ No newline at end of file diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx index 9c8e2b5..9bef069 100644 --- a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx @@ -23,6 +23,8 @@ import { v4 } from "uuid"; import { 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"; + interface I2DArea { x: number; @@ -41,10 +43,6 @@ export enum StoreUIType { ViewBehaviorTree, } -export enum SystemPrimitive { - Sequence = "Sequence", - Fallback = "Fallback", -} export class BehaviorTreeBuilderStore extends UiFormState { type: StoreUIType = StoreUIType.ViewBehaviorTree; @@ -67,6 +65,8 @@ export class BehaviorTreeBuilderStore extends UiFormState; areaPlugin?: AreaPlugin; + nodeUpdateObserver?: NodeRerenderObserver; + primitiveViewModel: PrimitiveViewModel; skillTree: ISkillView = { name: "", children: [ @@ -74,20 +74,17 @@ export class BehaviorTreeBuilderStore extends UiFormState, area: AreaPlugin) => { @@ -95,7 +92,7 @@ export class BehaviorTreeBuilderStore extends UiFormState {}; + errorHandingStrategy = (_: CoreError) => { }; dragEnd = (e: EventTarget) => { if (this.canRun) { @@ -205,8 +202,8 @@ export class BehaviorTreeBuilderStore extends UiFormState {}); - document.removeEventListener("keydown", () => {}); + document.removeEventListener("keyup", () => { }); + document.removeEventListener("keydown", () => { }); } onClickSaveBehaviorTree = async (): Promise => { this.filledOutTemplates.validation().fold( @@ -236,7 +233,7 @@ export class BehaviorTreeBuilderStore extends UiFormState message.error(`Дерево поведения не заполнено`) ); }; - validateBt() {} + validateBt() { } createNewBehaviorTree = async () => { this.viewModel.project = this.activeProject; this.viewModel.valid().fold( @@ -267,7 +264,6 @@ export class BehaviorTreeBuilderStore extends UiFormState { - this.edtDrawer(DrawerState.editThreadBehaviorTree, false); this.filledOutTemplates?.skillBySid(this.selectedSid ?? "").fold( (m) => { const model = clone(m); @@ -286,6 +282,7 @@ export class BehaviorTreeBuilderStore extends UiFormState console.log("UNKNOWN SID: " + this.selectedSid) ); @@ -293,7 +290,8 @@ export class BehaviorTreeBuilderStore extends UiFormState this.filledOutTemplates?.dependencyIsFilled(type, sid); getInputs(name: string) { - if (Object.keys(SystemPrimitive).includes(name)) { + const result = this.primitiveViewModel.getPrimitiveAtLabel(name) + if (result) { return { - input: "a", - output: "a", + input: result.input ? "a" : null, + output: result.output ? "a" : null, }; } return { - input: "a", - output: null, - }; + input: 'a', + output: null + } + } getStylesByLabelNode(label: string): React.CSSProperties | undefined { + const result = this.primitiveViewModel.getPrimitiveAtLabel(label) + if (result) { + return clone(result.css as Object); + } if (label.isEqual(SystemPrimitive.Fallback)) { return { border: "1px solid #003E61", diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_form_form.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_form_form.tsx new file mode 100644 index 0000000..2118d18 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_form_form.tsx @@ -0,0 +1,38 @@ +import React from "react" +import { observer } from "mobx-react-lite" +import { message } from "antd"; +import { CameraDeviceStore } from "./camera_device_store" +import { IPropsForm } from "../forms"; +import { IDeviceDependency } from "../../../../../../core/model/skill_model"; +import { CoreButton } from "../../../../../../core/ui/button/button"; +import { CoreSelect } from "../../../../../../core/ui/select/select"; +import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text"; + + + + + +export const CameraDeviceForm = observer((props: IPropsForm>) => { + const [store] = React.useState(() => new CameraDeviceStore()); + React.useEffect(() => { + store.init(); + }, [store]); + return ( +
+ + el.sid) ?? []} + value={props.dependency?.sid ?? ""} + label={'Выберите камеру'} + onChange={(value: string) => + store.updateForm({ sid: value }) + + } /> + { + store.viewModel.valid().fold((s) => { + props.onChange(s); + }, (e) => message.error(e)) + }} /> +
+ ) +}) \ No newline at end of file diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_form_http_repository.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_form_http_repository.ts new file mode 100644 index 0000000..4395a60 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_form_http_repository.ts @@ -0,0 +1,6 @@ +import { Cameras } from "../../../../../../core/model/cameras"; +import { HttpMethod, HttpRepository } from "../../../../../../core/repository/http_repository"; + +export class CameraDeviceHttpRepository extends HttpRepository { + getAllCameras = () => this._jsonRequest(HttpMethod.GET, '/behavior/trees/cameras'); +} \ No newline at end of file diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_store.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_store.ts new file mode 100644 index 0000000..598cc89 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/camera_device_form/camera_device_store.ts @@ -0,0 +1,21 @@ +import makeAutoObservable from "mobx-store-inheritance"; +import { NavigateFunction } from "react-router-dom"; +import { Cameras } from "../../../../../../core/model/cameras"; +import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model"; +import { FormState, CoreError } from "../../../../../../core/store/base_store"; +import { CameraDeviceHttpRepository } from "./camera_device_form_http_repository"; + +export class CameraDeviceStore extends FormState { + constructor() { + super(); + makeAutoObservable(this); + } + viewModel: SidViewModel = SidViewModel.empty(); + cameras?: Cameras; + cameraDeviceHttpRepository: CameraDeviceHttpRepository = new CameraDeviceHttpRepository(); + errorHandingStrategy = (error: CoreError) => { } + init = async (navigate?: NavigateFunction | undefined) => { + this.mapOk('cameras', this.cameraDeviceHttpRepository.getAllCameras()) + } + +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/forms.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/forms.tsx index 3f756e8..012aad0 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/forms/forms.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/forms.tsx @@ -1,6 +1,8 @@ -import { SimpleForm } from "./simple_form"; -import { TestForm } from "./test"; -import WeightsForm from "./weights_form"; + +import { CameraDeviceForm } from "./camera_device_form/camera_device_form_form"; +import { RobotDeviceForm } from "./robot_device_form/robot_device_form_form"; +import { TopicsForm } from "./topics_form/topics_form"; +import { WeightsForm } from "./weights_form/weights_form"; export interface IPropsForm { dependency: T; @@ -11,9 +13,15 @@ export interface IForms { name: string; component: JSX.Element; } - +export enum Form { + weights = 'weights', + robotName = 'robot_name', + cameraDeviceForm = 'camera', + topic = 'topic' +} export const forms = (props: any, onChange: (dependency: Object) => void): IForms[] => [ - { name: "weights", component: }, - { name: "simple", component: }, - { name: "test", component: }, + { name: Form.weights, component: }, + { name: Form.robotName, component: }, + { name: Form.cameraDeviceForm, component: }, + { name: Form.topic, component: } ]; diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_form.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_form.tsx new file mode 100644 index 0000000..483256b --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_form.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { observer } from "mobx-react-lite"; +import { RobotDeviceFormStore } from "./robot_device_form_store"; +import { IPropsForm } from "../forms"; +import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model"; + +export const RobotDeviceForm = observer((props: IPropsForm>) => { + const [store] = React.useState(() => new RobotDeviceFormStore()); + React.useEffect(() => { + store.init(); + }, [store]); + + return
; +}); diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_http_repository.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_http_repository.ts new file mode 100644 index 0000000..3560b6a --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_http_repository.ts @@ -0,0 +1,5 @@ +import { HttpRepository } from "../../../../../../core/repository/http_repository"; + +export class RobotDeviceFormHttpRepository extends HttpRepository { + +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_store.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_store.ts new file mode 100644 index 0000000..a1ea481 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/robot_device_form/robot_device_form_store.ts @@ -0,0 +1,19 @@ +import { makeAutoObservable } from "mobx"; +import { NavigateFunction } from "react-router-dom"; +import { FormState, CoreError } from "../../../../../../core/store/base_store"; +import { RobotDeviceFormHttpRepository } from "./robot_device_form_http_repository"; +import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model"; + +export class RobotDeviceFormStore extends FormState { + constructor() { + super(); + makeAutoObservable(this); + } + viewModel: SidViewModel = SidViewModel.empty(); + cameraDeviceHttpRepository: RobotDeviceFormHttpRepository = new RobotDeviceFormHttpRepository(); + errorHandingStrategy = (error: CoreError) => { } + init = async (navigate?: NavigateFunction | undefined) => { + + } + +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/simple_form.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/simple_form.tsx deleted file mode 100644 index adfbc68..0000000 --- a/ui/src/features/behavior_tree_builder/presentation/ui/forms/simple_form.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { CoreButton } from "../../../../../core/ui/button/button"; -import { IPropsForm } from "./forms"; - -export interface ISimpleFormDependency { - simple?: string; -} - -export const SimpleForm = (props: IPropsForm) => { - return ( -
- props.onChange({ simple: "132" })} text="OK" /> -
- ); -}; diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/test.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/test.tsx deleted file mode 100644 index 63fac7f..0000000 --- a/ui/src/features/behavior_tree_builder/presentation/ui/forms/test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { CoreButton } from "../../../../../core/ui/button/button"; -import { CoreText, CoreTextType } from "../../../../../core/ui/text/text"; -import { IPropsForm } from "./forms"; -import { ISimpleFormDependency } from "./simple_form"; - -export const TestForm = (props: IPropsForm) => { - return ( -
- - props.onChange({ test: "132" })} text="OK" /> -
- ); -}; diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model.ts new file mode 100644 index 0000000..187ce8a --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model.ts @@ -0,0 +1,18 @@ +import makeAutoObservable from "mobx-store-inheritance"; +import { Result } from "../../../../../../core/helper/result"; +import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model"; + +export class TopicDependencyViewModel extends SidViewModel { + axis: boolean; + constructor(sid: string, axis: boolean) { + super(sid); + this.axis = axis; + makeAutoObservable(this) + } + isValid = (): Result => { + return Result.ok(); + } + static empty() { + return new TopicDependencyViewModel('', false); + } +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form.tsx new file mode 100644 index 0000000..27b87ff --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { observer } from "mobx-react-lite"; +import { TopicsFormStore } from "./topics_form_store"; +import { IPropsForm } from "../forms"; +import { TopicDependencyViewModel } from "./topic_dependency_view_model"; +import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text"; +import { CoreSwitch } from "../../../../../../core/ui/switch/switch"; +import { CoreSelect } from "../../../../../../core/ui/select/select"; +import { CoreButton } from "../../../../../../core/ui/button/button"; +import { message } from "antd"; + +export const TopicsForm = observer((props: IPropsForm>) => { + const [store] = React.useState(() => new TopicsFormStore()); + + React.useEffect(() => { + store.init(); + }, [store, props]); + + return
+ + el.name) ?? []} + value={props.dependency?.sid ?? ""} + label={'Выберите топик'} + onChange={(value: string) => + store.updateForm({ sid: value }) + } /> + +
is input: { + console.log(200) + store.updateForm({ axis: !status }) + + }} />
+ { + store.viewModel.valid().fold((s) => { + props.onChange(s); + }, (e) => message.error(e)) + }} /> +
; +}); diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form_http_repository.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form_http_repository.ts new file mode 100644 index 0000000..158eee4 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form_http_repository.ts @@ -0,0 +1,6 @@ +import { Topics } from "../../../../../../core/model/topics"; +import { HttpMethod, HttpRepository } from "../../../../../../core/repository/http_repository"; + +export class TopicsFormHttpRepository extends HttpRepository { + getAllTopics = () => this._jsonRequest(HttpMethod.GET, '/behavior/trees/topics'); +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form_store.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form_store.ts new file mode 100644 index 0000000..66f953a --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form_store.ts @@ -0,0 +1,21 @@ +import makeAutoObservable from "mobx-store-inheritance"; +import { NavigateFunction } from "react-router-dom"; +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"; + +export class TopicsFormStore extends FormState { + constructor() { + super(); + makeAutoObservable(this); + } + topics?: Topics; + viewModel: TopicDependencyViewModel = TopicDependencyViewModel.empty(); + cameraDeviceHttpRepository: TopicsFormHttpRepository = new TopicsFormHttpRepository(); + errorHandingStrategy = (error: CoreError) => { } + init = async (navigate?: NavigateFunction | undefined) => { + await this.mapOk('topics', this.cameraDeviceHttpRepository.getAllTopics()) + } + +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form.tsx deleted file mode 100644 index c261917..0000000 --- a/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React from "react"; -import makeAutoObservable from "mobx-store-inheritance"; -import { IWeightsDependency } from "../../../../../core/model/skill_model"; -import { CoreError, FormState } from "../../../../../core/store/base_store"; -import { ISkils, SkillsHttpRepository } from "../../../../skils/skills_http_repository"; -import { observer } from "mobx-react-lite"; -import { CoreButton } from "../../../../../core/ui/button/button"; -import { Result } from "../../../../../core/helper/result"; -import { Select, message } from "antd"; -import { CoreInput } from "../../../../../core/ui/input/input"; -import { DataSetHttpRepository } from "../../../../dataset/dataset_http_repository"; -import { Assets } from "../../../../dataset/dataset_model"; - -export class WeightsViewModel implements IWeightsDependency { - constructor( - public object_name: string = "", - public weights_file: string = "", - public dimensions: number[] = [], - public weights_name = "" - ) {} - static empty() { - return new WeightsViewModel(); - } - isEmpty = (): Result => { - if (this.weights_name.isEmpty()) { - return Result.error("weights_name is empty"); - } - if (this.object_name.isEmpty()) { - return Result.error("object name is empty"); - } - if (this.weights_file.isEmpty()) { - return Result.error("weights_file is empty"); - } - if (this.dimensions.isEmpty()) { - return Result.error("dimensions is empty"); - } - return Result.ok(undefined); - }; -} -export class WeightsFormStore extends FormState { - weights?: ISkils[]; - assets?: Assets; - suitableWeights: string[] = []; - viewModel: WeightsViewModel = WeightsViewModel.empty(); - skillsHttpRepository: SkillsHttpRepository = new SkillsHttpRepository(); - datasetHttpRepository: DataSetHttpRepository = new DataSetHttpRepository(); - constructor() { - super(); - makeAutoObservable(this); - } - init = async () => { - await this.mapOk("weights", this.skillsHttpRepository.getAllSkills()); - await this.mapOk("assets", this.datasetHttpRepository.getAssetsActiveProject()); - }; - changeDimensions = (index: number, value: number) => { - this.viewModel.dimensions[index] = value; - }; - selectAsset = (): void => { - this.suitableWeights = - this.weights - ?.filter((el) => - el.datasetId.dataSetObjects.filter((datasetObject: string) => - this.viewModel.object_name.isEqual(datasetObject) - ) - ) - .map((el) => el.name) ?? []; - }; - updateWeights = (text: string) => { - const model = this.weights - ?.filter((el) => - el.datasetId.dataSetObjects.filter((datasetObject: string) => this.viewModel.object_name.isEqual(datasetObject)) - ) - .at(0); - - this.updateForm({ weights_file: `${model?.datasetId.local_path}/weights/${model?.name}/${model?.name}.pt` }); - }; - errorHandingStrategy = (_: CoreError) => {}; -} - -interface IWeightsFormProps { - dependency?: IWeightsDependency; - onChange: (dependency: IWeightsDependency) => void; -} - -const WeightsForm = observer((props: IWeightsFormProps) => { - const [store] = React.useState(() => new WeightsFormStore()); - React.useEffect(() => { - store.init(); - }, [store]); - - return ( -
- { - store.updateForm({ weights_name: e }); - store.updateWeights(e); - }} - filterOption={(input: string, option?: { label: string; value: string }) => - (option?.label ?? "").toLowerCase().includes(input.toLowerCase()) - } - style={{ width: "100%" }} - options={ - store.suitableWeights?.map((el) => { - return { label: el, value: el }; - }) ?? [] - } - /> -
- store.changeDimensions(0, Number(text))} - validation={(text) => Number().isValid(text)} - value={props.dependency?.dimensions?.at(0)?.toString()} - /> -
- - store.changeDimensions(1, Number(text))} - validation={(text) => Number().isValid(text)} - value={props.dependency?.dimensions?.at(1)?.toString()} - /> -
- - store.changeDimensions(2, Number(text))} - validation={(text) => Number().isValid(text)} - value={props.dependency?.dimensions?.at(2)?.toString()} - /> -
- { - store.viewModel.isEmpty().fold( - () => { - props.onChange(store.viewModel); - }, - (e) => { - message.error(e); - } - ); - }} - text="OK" - style={{ width: 100 }} - /> -
- ); -}); - -export default WeightsForm; diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_form.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_form.tsx new file mode 100644 index 0000000..ddec935 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_form.tsx @@ -0,0 +1,105 @@ +import { observer } from "mobx-react-lite"; +import React from "react"; + +import { Select, message } from "antd"; +import { WeightsFormStore } from "./weights_store"; +import { IWeightsDependency } from "../../../../../../core/model/skill_model"; +import { CoreButton } from "../../../../../../core/ui/button/button"; +import { CoreInput } from "../../../../../../core/ui/input/input"; +import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text"; + + +interface IWeightsFormProps { + dependency?: IWeightsDependency; + onChange: (dependency: IWeightsDependency) => void; +} + +export const WeightsForm = observer((props: IWeightsFormProps) => { + const [store] = React.useState(() => new WeightsFormStore()); + React.useEffect(() => { + store.init(); + }, [store]); + + return ( +
+ + { + store.updateForm({ weights_name: e }); + store.updateWeights(e); + }} + filterOption={(input: string, option?: { label: string; value: string }) => + (option?.label ?? "").toLowerCase().includes(input.toLowerCase()) + } + style={{ width: "100%" }} + options={ + store.suitableWeights?.map((el) => { + return { label: el, value: el }; + }) ?? [] + } + /> +
+ store.changeDimensions(0, Number(text))} + validation={(text) => Number().isValid(text)} + value={props.dependency?.dimensions?.at(0)?.toString()} + /> +
+ + store.changeDimensions(1, Number(text))} + validation={(text) => Number().isValid(text)} + value={props.dependency?.dimensions?.at(1)?.toString()} + /> +
+ + store.changeDimensions(2, Number(text))} + validation={(text) => Number().isValid(text)} + value={props.dependency?.dimensions?.at(2)?.toString()} + /> +
+ { + store.viewModel.isEmpty().fold( + () => { + props.onChange(store.viewModel); + }, + (e) => { + message.error(e); + } + ); + }} + text="OK" + style={{ width: 100 }} + /> +
+ ); +}); + diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_store.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_store.ts new file mode 100644 index 0000000..9319020 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_store.ts @@ -0,0 +1,48 @@ +import makeAutoObservable from "mobx-store-inheritance"; +import { FormState, CoreError } from "../../../../../../core/store/base_store"; +import { DataSetHttpRepository } from "../../../../../dataset/dataset_http_repository"; +import { Assets } from "../../../../../dataset/dataset_model"; +import { ISkils, SkillsHttpRepository } from "../../../../../skils/skills_http_repository"; +import { WeightsViewModel } from "./weights_view_model"; + +export class WeightsFormStore extends FormState { + weights?: ISkils[]; + assets?: Assets; + suitableWeights: string[] = []; + viewModel: WeightsViewModel = WeightsViewModel.empty(); + skillsHttpRepository: SkillsHttpRepository = new SkillsHttpRepository(); + datasetHttpRepository: DataSetHttpRepository = new DataSetHttpRepository(); + constructor() { + super(); + makeAutoObservable(this); + } + init = async () => { + await this.mapOk("weights", this.skillsHttpRepository.getAllSkills()); + await this.mapOk("assets", this.datasetHttpRepository.getAssetsActiveProject()); + }; + changeDimensions = (index: number, value: number) => { + this.viewModel.dimensions[index] = value; + }; + selectAsset = (): void => { + this.suitableWeights = + this.weights + ?.filter((el) => + el.datasetId.dataSetObjects.filter((datasetObject: string) => + this.viewModel.object_name.isEqual(datasetObject) + ) + ) + .map((el) => el.name) ?? []; + }; + updateWeights = (text: string) => { + const model = this.weights + ?.filter((el) => + el.datasetId.dataSetObjects.filter((datasetObject: string) => this.viewModel.object_name.isEqual(datasetObject)) + ) + .at(0); + + this.updateForm({ weights_file: `${model?.datasetId.local_path}/weights/${model?.name}/${model?.name}.pt` }); + }; + errorHandingStrategy = (_: CoreError) => { }; + } + + \ No newline at end of file diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_view_model.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_view_model.ts new file mode 100644 index 0000000..b03f170 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/weights_form/weights_view_model.ts @@ -0,0 +1,30 @@ +import { Result } from "../../../../../../core/helper/result"; +import { IWeightsDependency } from "../../../../../../core/model/skill_model"; + +export class WeightsViewModel implements IWeightsDependency { + constructor( + public object_name: string = "", + public weights_file: string = "", + public dimensions: number[] = [], + public weights_name = "" + ) { } + static empty() { + return new WeightsViewModel(); + } + isEmpty = (): Result => { + if (this.weights_name.isEmpty()) { + return Result.error("weights_name is empty"); + } + if (this.object_name.isEmpty()) { + return Result.error("object name is empty"); + } + if (this.weights_file.isEmpty()) { + return Result.error("weights_file is empty"); + } + if (this.dimensions.isEmpty()) { + return Result.error("dimensions is empty"); + } + return Result.ok(undefined); + }; + } + \ No newline at end of file