bt builder

This commit is contained in:
IDONTSUDO 2024-05-29 15:38:36 +03:00
parent 5162612a77
commit c5ae89d18a
52 changed files with 1013 additions and 441 deletions

View file

@ -15,7 +15,9 @@
"число",
"эпох",
"эпоха",
"Contolls",
"skils",
"typedataset"
"typedataset",
"usecases"
]
}

6
p.json
View file

@ -1,6 +0,0 @@
{
"dependency":{
"weights_path":"",
"object_name":""
}
}

View file

@ -6,7 +6,6 @@ import { Server } from "socket.io";
import { createServer } from "http";
import { SocketSubscriber } from "./socket_controller";
import { dirname } from "path";
import { SetLastActivePipelineToRealTimeServiceScenario } from "../scenarios/set_active_pipeline_to_realtime_service_scenario";
import { CheckAndCreateStaticFilesFolderUseCase } from "../usecases/check_and_create_static_files_folder_usecase";
import { DataBaseConnectUseCase } from "../usecases/database_connect_usecase";
import { TypedEvent } from "../helpers/typed_event";
@ -58,8 +57,6 @@ export class App extends TypedEvent<ServerStatus> {
io.on("connection", (socket) => {
this.socketSubscribers.map((el) => {
el.emitter.on((e) => {
console.log(el.event)
console.log(e)
socket.emit(el.event, e);
});
});
@ -125,3 +122,4 @@ export class App extends TypedEvent<ServerStatus> {
};
}

View file

@ -3,6 +3,7 @@ import { Result } from "../helpers/result";
import { Router, Request, Response } from "express";
import { IRouteModel, Routes } from "../interfaces/router";
import { CoreValidation } from "../validations/core_validation";
import { plainToInstance } from "class-transformer";
export type HttpMethodType = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "PATCH" | "HEAD";
@ -78,11 +79,11 @@ export class CoreHttpController<V> implements ICoreHttpController {
}
call(): Routes {
if (this.subRoutes.isNotEmpty()) {
this.subRoutes.map((el) => {
this.subRoutes.map(async (el) => {
this.router[el.method.toLowerCase()](this.mainURL + "/" + el.subUrl, async (req, res) => {
if (el.fn instanceof CallbackStrategyWithValidationModel) {
// TODO(IDONTSUDO):
throw Error("needs to be implimed");
this.responseHelper(res, el.fn.call(req.body));
return;
}
if (el.fn instanceof CallbackStrategyWithIdQuery) {
if (req.query.id === undefined) {

View file

@ -10,11 +10,13 @@ export const validationModelMiddleware = (
forbidNonWhitelisted = true
): RequestHandler => {
return (req, res, next) => {
if (type === null && type == undefined) {
next();
return;
}
const model = plainToInstance(type, req[value]);
validate(model, { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => {
if (errors.length > 0) {
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(", ");

View file

@ -1,8 +1,11 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { GetBehaviorTreeSkillsTemplatesUseCase } from "./get_bt_skills_templates_usecase";
import { GetBehaviorTreeSkillsTemplatesUseCase } from "./domain/get_bt_skills_templates_usecase";
import { BehaviorTreeValidationModel } from "./models/behavior_tree_validation_model";
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";
export class BehaviorTreesPresentation extends CrudController<BehaviorTreeValidationModel, typeof BehaviorTreeDBModel> {
constructor() {
super({
@ -10,6 +13,12 @@ export class BehaviorTreesPresentation extends CrudController<BehaviorTreeValida
validationModel: BehaviorTreeValidationModel,
databaseModel: BehaviorTreeDBModel,
});
super.get(new GetBehaviorTreeActiveProjectScenario().call);
this.subRoutes.push({
method: "POST",
subUrl: "fill/tree",
fn: new FillBtScenario(),
});
this.subRoutes.push({ method: "GET", subUrl: "templates", fn: new GetBehaviorTreeSkillsTemplatesUseCase() });
this.subRoutes.push({
method: "GET",

View file

@ -0,0 +1,15 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { SearchOneDataBaseModelUseCase } from "../../../core/usecases/search_database_model_usecase";
import { IProjectModel, ProjectDBModel } from "../../_projects/models/project_database_model";
import { BehaviorTreeDBModel } from "../models/behavior_tree_database_model";
export class GetBehaviorTreeActiveProjectScenario extends CallbackStrategyWithEmpty {
call = async (): ResponseBase =>
(
await new SearchOneDataBaseModelUseCase<IProjectModel>(ProjectDBModel).call(
{ isActive: true },
"no active projects"
)
).map(async (project) => Result.ok(await BehaviorTreeDBModel.find({ project: project._id })));
}

View file

@ -0,0 +1,44 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => {
return Result.ok({
skills: [
{
SkillPackage: { name: "Robossembler", version: "1.0", format: "1" },
Module: { name: "PoseEstimation", description: "Pose Estimation skill with DOPE" },
Launch: { package: "rbs_perception", executable: "pe_dope_lc.py", name: "lc_dope" },
BTAction: [
{
name: "peConfigure",
type: "run",
param: [
{
type: "weights",
dependency: {},
},
],
result: ["POSE"],
},
{ name: "peStop", type: "stop", param: [], result: [] },
],
Interface: {
Input: [
{ name: "cameraLink", type: "CAMERA" },
{ name: "object_name", type: "MODEL" },
],
Output: [{ name: "pose_estimation_topic", type: "POSE" }],
},
Settings: [
{ name: "cameraLink", value: "inner_rgbd_camera" },
{ name: "pose", value: "" },
{ name: "publishDelay", value: 0.5 },
{ name: "tf2_send_pose", value: 1 },
{ name: "mesh_scale", value: 0.001 },
],
},
],
});
};
}

View file

@ -0,0 +1,32 @@
import { CallbackStrategyWithValidationModel, ResponseBase } from "../../../core/controllers/http_controller";
import { CreateFileUseCase } from "../../../core/usecases/create_file_usecase";
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
import { SearchOneDataBaseModelUseCase } from "../../../core/usecases/search_database_model_usecase";
import { IProjectModel, ProjectDBModel } from "../../_projects/models/project_database_model";
import { FolderStructure } from "../../projects/domain/upload_file_to_to_project_scenario";
import { BehaviorTreeValidationModel } from "../models/behavior_tree_validation_model";
export class SaveBtScenario extends CallbackStrategyWithValidationModel<BehaviorTreeValidationModel> {
validationModel: BehaviorTreeValidationModel = new BehaviorTreeValidationModel();
call = async (model: BehaviorTreeValidationModel): ResponseBase =>
(
await new SearchOneDataBaseModelUseCase<IProjectModel>(ProjectDBModel).call(
{ isActive: true },
"no active projects"
)
).map(async (project) => {
const folder = `${project.rootDir}/${FolderStructure.behaviorTrees}/`;
(await new CreateFolderUseCase().call(folder)).map(async () =>
(await new CreateFolderUseCase().call(`${folder}${model.name}/`)).map(async () =>
(await new CreateFileUseCase().call(`${folder}${model.name}/bt.xml`, Buffer.from(model.xml))).map(
async () =>
await new CreateFileUseCase().call(
`${folder}${model.name}/skills.json`,
Buffer.from(JSON.stringify(model.skills))
)
)
)
);
});
}

View file

@ -1,98 +0,0 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../core/controllers/http_controller";
import { Result } from "../../core/helpers/result";
export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => {
return Result.ok({
skills: [
{
SkillPackage: {
name: "Robossembler",
version: "1.0",
format: "1",
},
Module: {
name: "PoseEstimation",
description: "Pose Estimation skill with DOPE",
},
Launch: {
executable: "pe_dope_lc.py",
},
ROS2: {
node_name: "lc_dope",
},
BTAction: [
{
name: "peConfigure",
format: "yaml",
type: "run",
param: [
{
type: "weights",
dependency: {},
},
{
type: "form",
dependency: {},
},
],
result: ["POSE"],
},
{
name: "peStop",
format: "yaml",
type: "stop",
param: [],
result: [],
},
],
Interface: {
Input: [
{
name: "cameraLink",
type: "CAMERA",
},
{
name: "object_name",
type: "MODEL",
},
],
Output: [
{
name: "pose_estimation_topic",
type: "POSE",
},
],
},
Settings: [
{
name: "cameraLink",
value: "inner_rgbd_camera",
},
{
name: "pose",
value: "",
},
{
name: "publishDelay",
value: 0.5,
},
{
name: "tf2_send_pose",
value: 1,
},
{
name: "mesh_scale",
value: 0.001,
},
],
xxx: {
cameraLink: "inner_rgbd_camera",
topicImage: "/inner_rgbd_camera/image",
topicCameraInfo: "/inner_rgbd_camera/camera_info",
},
},
],
});
};
}

View file

@ -3,7 +3,7 @@ import { IProjectModel, projectSchema } from "../../_projects/models/project_dat
export interface IBehaviorTreeModel {
name: string;
project?: IProjectModel;
project?: IProjectModel | string;
unixTime?: number;
local_path?: string;
}
@ -26,6 +26,13 @@ export const BehaviorTreeSchema = new Schema({
dependency: {
type: Schema.Types.Mixed,
},
skills: {
type: Schema.Types.Mixed,
},
scene: {
type: Array,
default: null,
},
project: {
type: Schema.Types.ObjectId,
ref: projectSchema,

View file

@ -1,7 +1,11 @@
import { IsString } from "class-validator";
import { IsMongoId, IsString } from "class-validator";
import { IBehaviorTreeModel } from "./behavior_tree_database_model";
export class BehaviorTreeValidationModel implements IBehaviorTreeModel {
@IsString()
public name: string;
@IsMongoId()
public project:string;
public skills:any;
public xml:string;
}

View file

@ -16,7 +16,6 @@ export class ExecDatasetProcessScenario extends CallbackStrategyWithIdQuery {
return (await new IsHaveActiveProcessUseCase().call()).map(async () => {
await DatasetDBModel.findById(id).updateOne({ processStatus: "RUN" });
model.local_path = `${model.local_path}/${FolderStructure.datasets}/`;
console.log(`blenderproc run $PYTHON_BLENDER_PROC --cfg '${JSON.stringify(model)}'`);
return new ExecProcessUseCase().call(
`${model.project.rootDir}/`,
`blenderproc run $PYTHON_BLENDER_PROC --cfg '${JSON.stringify(model)}'`,

View file

@ -13,6 +13,7 @@ export enum FolderStructure {
assets = "assets",
weights = "weights",
datasets = "datasets",
behaviorTrees = "behavior_trees",
}
export class UploadCadFileToProjectScenario extends CallbackStrategyWithFileUpload {
@ -30,9 +31,12 @@ export class UploadCadFileToProjectScenario extends CallbackStrategyWithFileUplo
""
)
).map(async () =>
(await new CreateManyFolderScenario().call(databaseModel.rootDir, Object.keys(FolderStructure).map((el) => `/${el}`))).map(() =>
Result.ok("file upload and save")
)
(
await new CreateManyFolderScenario().call(
databaseModel.rootDir,
Object.keys(FolderStructure).map((el) => `/${el}`)
)
).map(() => Result.ok("file upload and save"))
)
)
);

View file

@ -9,5 +9,4 @@ extensions();
const socketSubscribers = [new SocketSubscriber(executorProgramService, "realtime")];
new App(httpRoutes, socketSubscribers).listen();
new App(httpRoutes, socketSubscribers).listen();

6
ui/package-lock.json generated
View file

@ -22,6 +22,7 @@
"class-validator": "^0.14.0",
"formik-antd": "^2.0.4",
"i18next": "^23.6.0",
"just-clone": "^6.2.0",
"mobx": "^6.10.0",
"mobx-react-lite": "^4.0.4",
"mobx-store-inheritance": "^1.0.6",
@ -11116,6 +11117,11 @@
"node": ">=4.0"
}
},
"node_modules/just-clone": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz",
"integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA=="
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View file

@ -17,6 +17,7 @@
"class-validator": "^0.14.0",
"formik-antd": "^2.0.4",
"i18next": "^23.6.0",
"just-clone": "^6.2.0",
"mobx": "^6.10.0",
"mobx-react-lite": "^4.0.4",
"mobx-store-inheritance": "^1.0.6",
@ -50,7 +51,7 @@
"xml-formatter": "^3.6.2"
},
"scripts": {
"dev": "react-scripts start",
"dev": "GENERATE_SOURCEMAP=false && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",

View file

@ -63,11 +63,20 @@ export const ArrayExtensions = () => {
// eslint-disable-next-line no-extend-native
Array.prototype.rFind = function (predicate) {
const result = this.find(predicate as any);
console.log(result)
if (result === undefined) {
return Result.error(undefined);
}
return Result.ok(result);
};
}
if ([].maxLength === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.maxLength = function (length) {
if (this.length > length) {
return this;
} else {
return this.slice(0, length);
}
};
}
};

View file

@ -12,7 +12,6 @@ export type OptionalProperties<T> = {
[P in keyof T]?: T[P];
};
declare global {
interface Array<T> {
// @strict: The parameter is determined whether the arrays must be exactly the same in content and order of this relationship or simply follow the same requirements.
@ -22,10 +21,8 @@ declare global {
isNotEmpty(): boolean;
hasIncludeElement(element: T): boolean;
repeat(quantity: number): Array<T>;
rFind<T>(
predicate: (value: T, index: number, obj: never[]) => boolean,
thisArg?: any
): Result<void, T>;
rFind<T>(predicate: (value: T, index: number, obj: never[]) => boolean, thisArg?: any): Result<void, T>;
maxLength(length: number): Array<T>;
}
interface Number {
fromArray(): number[];
@ -43,7 +40,10 @@ declare global {
replaceMany(searchValues: string[], replaceValue: string): string;
isEqual(str: string): boolean;
isEqualMany(str: string[]): boolean;
hasPattern(pattern: string): boolean;
hasNoPattern(pattern: string): boolean;
}
interface Map<K, V> {
addValueOrMakeCallback(key: K, value: V, callBack: CallBackVoidFunction): void;
getKeyFromValueIsExists(value: V): K | undefined;
@ -51,6 +51,7 @@ declare global {
keysToJson(): string;
toArray(): V[];
getPredicateValue(callBack: (value: V) => boolean): K[];
incrementValue(key: K): void;
}
interface Vector3 {}
}

View file

@ -58,4 +58,14 @@ export const MapExtensions = () => {
return result;
};
}
if (Map.prototype.incrementValue === undefined) {
// eslint-disable-next-line no-extend-native
Map.prototype.incrementValue = function (key) {
if (this.get(key)) {
this.set(key, this.get(key) + 1);
} else {
this.set(key, 1);
}
};
}
};

View file

@ -36,4 +36,14 @@ export const StringExtensions = () => {
return false;
};
}
if ("".hasPattern === undefined) {
String.prototype.hasPattern = function (pattern) {
return new RegExp(pattern).test(this as string);
};
}
if ("".hasNoPattern === undefined) {
String.prototype.hasNoPattern = function (pattern) {
return !this.hasPattern(pattern);
};
}
};

View file

@ -1,9 +1,21 @@
import { IsArray, IsString, ValidateNested } from "class-validator";
import { IsArray, IsOptional, IsString, ValidateNested } 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 clone from "just-clone";
export interface ISkillPoseEstimation {
export interface IDependency {
skills: ISkillDependency[];
}
export interface ISkillDependency {
sid: string;
dependency: Object;
}
export interface ISkill {
sid?: string;
SkillPackage: ISkillPackage;
Module: IModule;
Launch: ILaunch;
@ -14,17 +26,21 @@ export interface ISkillPoseEstimation {
xxx: IXxx;
}
export interface IWeightsDependency {
objectName: string;
weightsPath: string;
weights_name:string;
object_name: string;
weights_file: string;
dimensions: number[];
}
export interface IParam {
type: string;
dependency: IWeightsDependency;
dependency: Object;
}
export interface IBTAction {
name: string;
format: string;
// TODO: Нужно выпилить его отсюда
// sid?: string;
type: string;
param: IParam[];
result: string[];
@ -91,6 +107,7 @@ export class BTAction implements IBTAction {
format: string;
@IsString()
type: string;
sid?: string;
@IsArray()
param: IParam[];
@IsArray()
@ -127,7 +144,10 @@ export class Xxx implements IXxx {
topicImage: string;
topicCameraInfo: string;
}
export class SkillModelPoseEstimation implements ISkillPoseEstimation {
export class SkillModel implements ISkill {
@IsOptional()
@IsString()
sid?: string;
@ValidateNested()
@Type(() => SkillPackage)
SkillPackage: ISkillPackage;
@ -154,12 +174,71 @@ export class SkillModelPoseEstimation implements ISkillPoseEstimation {
@ValidateNested()
@Type(() => Xxx)
xxx: IXxx;
static empty() {
const skillModel = new SkillModel();
skillModel.BTAction = [];
return skillModel;
}
public static isEmpty(skill: SkillModel): Result<void, SkillModel> {
if (skill.BTAction.isEmpty()) {
return Result.error(undefined);
}
return Result.ok(Object.assign(skill, {}));
}
public getSid = () => this.sid;
public setSid = (sid: string) => {
const result = clone(this);
result.sid = sid;
return result;
};
}
export class SkillDependency implements IDependency {
constructor(public skills: ISkillDependency[]) {}
static empty() {
return new SkillDependency([]);
}
static isEmpty = (skill: SkillDependency) => {
if (skill.skills.isEmpty()) {
return Result.error(undefined);
}
return Result.ok(skill);
};
}
export class Skills {
@IsArray()
@Type(() => SkillModelPoseEstimation)
skills: SkillModelPoseEstimation[];
@Type(() => SkillModel)
skills: SkillModel[];
validation = (): Result<string[], void> => {
const errors: string[] = [];
this.skills.forEach((skill) => {
skill.BTAction.forEach((action) => {
if (action.param.isNotEmpty()) {
action.param.forEach((param) => {
if (Object.keys(param.dependency).isEmpty()) {
errors.push(param.type);
}
});
}
});
});
if (errors.isNotEmpty()) {
return Result.error(errors);
}
return Result.ok(undefined);
};
skillBySid = (sid: string) =>
SkillModel.isEmpty(
this.skills.reduce<SkillModel>((acc, el) => {
if (el.sid?.isEqual(sid)) {
acc = el;
}
return acc;
}, SkillModel.empty())
);
toSkillView = (): ISkillView[] =>
this.skills.map((el) => {
return {
@ -169,6 +248,20 @@ export class Skills {
}),
};
});
getSkill = (name: string) =>
SkillModel.isEmpty(
this.skills.reduce<SkillModel>((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(name))) {
el.BTAction.map((action) => {
action.param.map((param) => {
return param;
});
});
acc = el;
}
return acc;
}, SkillModel.empty())
);
getSkilsOut = (name: string) =>
this.skills
@ -207,7 +300,7 @@ export class Skills {
getForms = (skillLabel: string) =>
this.skills
.reduce<SkillModelPoseEstimation[]>((acc, el) => {
.reduce<SkillModel[]>((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(skillLabel))) {
acc.push(el);
}
@ -217,17 +310,83 @@ export class Skills {
.flat(1)
.flat(1)
.filter((el) => el !== "");
getDependencyBySkillLabelAndType = <T>(skillLabel: string, skillType: string) =>
getDependencyBySkillLabelAndType = <T>(skillType: string, sid: string) =>
this.skills
.reduce<SkillModelPoseEstimation[]>((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(skillLabel))) {
acc.push(el);
.reduce<Object[]>((acc, skill) => {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.map((action) => {
action.param.map((param) => {
if (param.type.isEqual(skillType)) {
acc.push(param.dependency);
}
return param;
});
return action;
});
}
return acc;
}, [])
.map((el) => el.BTAction.map((act) => act.param.filter((el) => el.type.isEqual(skillType))))
.flat(1)
.flat(1)
.map((el) => el.dependency)
.at(0) as T;
static isEmpty(model: Skills): Result<void, void> {
if (model.skills.isEmpty()) {
return Result.error(undefined);
}
return Result.ok(undefined);
}
static empty() {
const skills = new Skills();
skills.skills = [];
return skills;
}
public dependencyIsFilled = (skillType: string, sid: string) =>
this.skills.reduce((acc, skill) => {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.forEach((action) => {
action.param.forEach((param) => {
if (param.type.isEqual(skillType)) {
// console.log('SKILL TYPE')
// console.log(skillType);
// console.log("SID")
// console.log(sid)
// console.log("DEPENDENCY")
// console.log(param.dependency)
acc = Object.keys(param.dependency).isNotEmpty();
}
});
});
}
return acc;
}, false);
getAllSids = () =>
this.skills.reduce((acc, skill) => {
skill.BTAction.forEach((action) =>
action.param.forEach((param) => {
// acc.incrementValue(param.sid ?? "empty");
return param;
})
);
return acc;
}, new Map<string, number>());
deleteSid(sid: string): SkillModel[] {
return this.skills.filter((skill) => !skill.sid?.isEqual(sid));
}
updateSkill = (skill: SkillModel) => {
console.log(skill);
this.skills = this.skills.map((el) => {
if (el.sid?.isEqual(skill.sid ?? "")) {
el = skill;
}
return el;
});
};
skillHasForm = (label: string): boolean => {
// TODO:NEED IMPLEMENTS
return true;
};
}

View file

@ -96,4 +96,5 @@ export class HttpRepository {
return Result.error(new HttpError(error, 0));
}
}
}

View file

@ -3,7 +3,7 @@ import { FormViewModel, InputBuilderViewModel, InputType } from "./form_view_mod
import { observer } from "mobx-react-lite";
import { FormBuilderStore } from "./form_builder_store";
import { FormBuilderValidationModel } from "../../../features/dataset/dataset_model";
import { SelectCore } from "../select/select";
import { CoreSelect } from "../select/select";
import { CoreInput } from "../input/input";
import { Icon } from "../icons/icons";
import { CoreText, CoreTextType } from "../text/text";
@ -41,7 +41,7 @@ export const FormBuilder = observer((props: IFormBuilder) => {
if (element.type.isEqual(InputType.ENUM)) {
const values = element.values as string[];
return (
<SelectCore
<CoreSelect
items={values}
value={element.totalValue ?? element.defaultValue}
onChange={(value) => store.changeTotalValue(element.id, value)}
@ -88,7 +88,7 @@ export const FormBuilder = observer((props: IFormBuilder) => {
if (subSubArrayItem.type.isEqual(InputType.ENUM)) {
return (
<>
<SelectCore
<CoreSelect
items={subSubArrayItem.values?.map((el) => String(el)) ?? []}
value={subSubArrayItem.totalValue ?? subSubArrayItem.defaultValue}
onChange={(value) => store.changeTotalSubValue(element.id, subIndex, value)}

View file

@ -5,6 +5,7 @@ import { IStyle } from "../../model/style";
interface IInputProps extends IStyle {
label: string;
value?: string;
subLabel?: React.ReactNode;
onChange?: (value: string) => void;
validation?: (value: string) => boolean;
error?: string;
@ -19,8 +20,7 @@ export const CoreInput = (props: IInputProps) => {
ref.current.innerText = value;
setAppendInnerText(false);
}
}, [ref, value, isAppendInnerText, setAppendInnerText]);
}, [ref, value, isAppendInnerText, setAppendInnerText, props]);
return (
<div
@ -47,12 +47,13 @@ export const CoreInput = (props: IInputProps) => {
color: "#1D1B20",
height: 24,
width: "100%",
userSelect: 'none',
outline:'none'
userSelect: "none",
outline: "none",
}}
onChange={(e) => {
const val = e.target.value;
setValue(val)
setValue(val);
if (val) {
if (props.validation !== undefined && props.validation(val) && props.onChange) {
props.onChange(val);

View file

@ -50,6 +50,7 @@ export interface IMainPageProps {
panelChildren?: JSX.Element;
panelStyle?: React.CSSProperties;
isLoading?: boolean;
maskLoader?: boolean;
error?: UiBaseError[];
}
export const MainPage = (props: IMainPageProps) => {
@ -132,6 +133,22 @@ export const MainPage = (props: IMainPageProps) => {
</>
) : (
<>
{props.maskLoader ? (
<div
style={{
width: "100vw",
left: 241,
height: "100vh",
backgroundColor: "#000000b0",
position: "absolute",
zIndex: 100,
alignContent: "center",
textAlignLast: "center",
}}
>
<Spin />
</div>
) : null}
<div
style={Object.assign(
{ width: 241, height: window.innerHeight, backgroundColor: "#F7F2FA", borderRadius: 16 },

View file

@ -2,13 +2,13 @@ import React from "react";
import { CoreText, CoreTextType } from "../text/text";
import { IStyle } from "../../model/style";
interface ISelectCoreProps extends IStyle {
interface ICoreSelectProps extends IStyle {
items: string[];
value: string;
label: string;
onChange: (value: string) => void;
}
export const SelectCore = (props: ISelectCoreProps) => {
export const CoreSelect = (props: ICoreSelectProps) => {
const ref = React.useRef<HTMLDivElement>(null);
const [cursorIsCorses, setCursorIsCorses] = React.useState(false);
const [value, setValue] = React.useState(props.value);

View file

@ -1,11 +1,12 @@
import { Result } from "../../../core/helper/result";
import { Skills } from "../../../core/model/skill_model";
import { HttpError, HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
import { UUID } from "../../all_projects/data/project_repository";
import { BehaviorTreeModel } from "../model/behavior_tree_model";
import { BehaviorTreeViewModel } from "../model/behavior_tree_view_model";
import { BtTreeModel } from "../model/bt_tree_model";
export class BehaviorTreeBuilderHttpRepository extends HttpRepository {
getAllBtInstances = async () => this._jsonRequest<BtTreeModel[]>(HttpMethod.GET, "/behavior/trees");
getAllBtInstances = async () => this._jsonRequest<BehaviorTreeModel[]>(HttpMethod.GET, "/behavior/trees");
getBtSkills = async (): Promise<Result<HttpError, Skills>> => {
return (await this._jsonToClassInstanceRequest<Skills>(
HttpMethod.GET,
@ -14,5 +15,17 @@ export class BehaviorTreeBuilderHttpRepository extends HttpRepository {
)) as unknown as Promise<Result<HttpError, Skills>>;
};
saveNewBt = async (model: BehaviorTreeViewModel) => this._jsonRequest(HttpMethod.POST, "/behavior/trees", model);
getBtById = async (id: string) => this._jsonRequest<BtTreeModel>(HttpMethod.GET, `/behavior/trees/by_id?id=${id}`);
getBtById = async (id: string): Promise<Result<HttpError, BehaviorTreeModel>> =>
this._jsonToClassInstanceRequest<BehaviorTreeModel>(
HttpMethod.GET,
`/behavior/trees/by_id?id=${id}`,
BehaviorTreeModel
) as unknown as Promise<Result<HttpError, BehaviorTreeModel>>;
getActiveProjectId(): Promise<Result<HttpError, UUID>> {
return this._jsonRequest<UUID>(HttpMethod.GET, "/projects/get/active/project/id");
}
editBt = async (model: BehaviorTreeModel) => {
await this._jsonRequest(HttpMethod.POST, "/behavior/trees/fill/tree", model);
return await this._jsonRequest(HttpMethod.PUT, "/behavior/trees", model);
};
}

View file

@ -0,0 +1,52 @@
import { Type } from "class-transformer";
import { Result } from "../../../core/helper/result";
import { Skills } from "../../../core/model/skill_model";
import { NodeBehaviorTree } from "./node_behavior_tree";
import { IsOptional } from "class-validator";
export class BehaviorTreeModel {
@IsOptional()
@Type(() => Skills)
public skills?: Skills;
public scene: NodeBehaviorTree[];
public xml: string;
public name: string;
public project: string;
public _id: string;
constructor(skills: Skills, scene: NodeBehaviorTree[], xml: string, name: string, project: string, _id: string) {
this.skills = skills;
this.scene = scene;
this.xml = xml;
this.name = name;
this.project = project;
this._id = _id;
}
updateDependency(skills: Skills, scene: NodeBehaviorTree[], xml: string) {
this.skills = skills;
this.scene = scene;
this.xml = xml;
}
static empty() {
return new BehaviorTreeModel(Skills.empty(), [], "", "", "", "");
}
static isEmpty(model: BehaviorTreeModel) {
return Skills.isEmpty(model.skills ?? Skills.empty()).map(() => {
if (model.scene.isEmpty()) {
return Result.error(undefined);
}
if (model.xml.isEmpty()) {
return Result.error(undefined);
}
if (model.project.isEmpty()) {
return Result.error(undefined);
}
if (model.name.isEmpty()) {
return Result.error(undefined);
}
if (model._id.isEmpty()) {
return Result.error(undefined);
}
return Result.ok();
});
}
}

View file

@ -1,11 +1,15 @@
import { Result } from "../../../core/helper/result";
export class BehaviorTreeViewModel {
constructor(public name: string) {}
constructor(public name: string, public project: string) {}
static empty() {
return new BehaviorTreeViewModel("");
return new BehaviorTreeViewModel("", "");
}
valid(): Result<string, BehaviorTreeViewModel> {
if (this.project.isEmpty()) {
return Result.error("project is empty");
}
if (this.name.isEmpty()) {
return Result.error("name is empty");
}

View file

@ -1,6 +0,0 @@
export interface BtTreeModel {
_id: string;
name: string;
unixTime: number;
__v: number;
}

View file

@ -4,17 +4,27 @@ import { AreaExtra, Schemes } from "../presentation/ui/editor/editor";
import { Result } from "../../../core/helper/result";
import { AreaPlugin } from "rete-area-plugin";
import { Skills } from "../../../core/model/skill_model";
import xmlFormat from "xml-formatter";
export interface BtDrawDragAndDropView {
x: number;
y: number;
name: string;
id: string;
}
export class BtNodeView extends TypedEvent<BtDrawDragAndDropView> {}
export class ReteObserver extends TypedEvent<any> {}
export class BtBuilderModel {
export enum UpdateEvent {
DELETE = "DELETE",
UPDATE = "UPDATE",
}
export interface IUpdateEvent {
type: UpdateEvent;
id: string;
}
export class NodeRerenderObserver extends TypedEvent<IUpdateEvent> {}
export class ReteForceUpdateObserver extends TypedEvent<any> {}
export class BehaviorTreeBuilderModel {
public static result = "";
static fromReteScene(
editor: NodeEditor<Schemes>,
@ -23,6 +33,8 @@ export class BtBuilderModel {
): Result<string, string> {
try {
this.result = "";
// eslint-disable-next-line array-callback-return
this.getFirstSequence(editor).map((sortedSequence) => {
const firstNodeId = sortedSequence.getKeyFromValueIsExists(1) as string;
this.findSequence(firstNodeId, editor, sortedSequence, 2);
@ -30,16 +42,35 @@ export class BtBuilderModel {
this.toXML(sortedSequence as Map<string, number>, editor, area, firstNodeId, skills);
this.result += `</${this.getNodeLabelAtId(editor, firstNodeId, skills)}>`;
});
return Result.ok(this.result);
return Result.ok(
xmlFormat(`<?xml version="1.0" encoding="UTF-8"?>
<root main_tree_to_execute="Main">
<BehaviorTree ID="Main">
${this.result}
</BehaviorTree>
<TreeNodesModel>
<Action ID="RbsBtAction">
<input_port name="do" />
<input_port name="command" />
<input_port name="server_name" />
<input_port name="server_timeout" />
</Action>
</TreeNodesModel>
</root>`)
);
} catch (error) {
return Result.error("BtBuilderModel fromReteScene error");
}
}
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="RbsBtAction" do="${skills.getSkillDo(editor.getNode(id).label)}" command="${
editor.getNode(id).label
}" server_name="rbs_interface" server_timeout="1000"`;
}" sid=${id} server_name="rbs_interface" server_timeout="1000"`;
}
return editor.getNode(id).label;
}
@ -60,7 +91,6 @@ export class BtBuilderModel {
});
}
public static getBtPriorities(ids: string[]) {}
public static findSequence(
nodeId: string,
editor: NodeEditor<Schemes>,

View file

@ -40,7 +40,6 @@ export class NodeBehaviorTree {
)
)
);
editor.getConnections().forEach((el) => console.log(el));
editor.getConnections().forEach((el) => nodes.overrideValue(el.target, { connectTo: el.source }));
return nodes.toArray();

View file

@ -12,7 +12,6 @@ import { Drawer } 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 { IWeightsDependency } from "../../../core/model/skill_model";
import { IForms, forms } from "./ui/forms/forms";
export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path";
@ -34,31 +33,31 @@ export const BehaviorTreeBuilderPath = "/behavior/tree/";
export const BehaviorTreeBuilderScreen = observer(() => {
const navigate = useNavigate();
const { id } = useParams();
const store = behaviorTreeBuilderStore;
// @ts-expect-error
const [ref] = useRete<HTMLDivElement>(createEditor);
const store = behaviorTreeBuilderStore;
if (ref.current) {
// @ts-expect-error
const domReact: DOMReact = ref.current.getBoundingClientRect();
store.dragZoneSetOffset(0, domReact.y, domReact.width, domReact.height);
}
React.useEffect(() => {
store.init(navigate);
store.initParams(id);
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);
}
});
});
return () => {
store.dispose();
};
}, [store, ref, id, navigate]);
}, [id, navigate, ref, store]);
return (
<MainPage
page={"Поведение"}
error={store.errors}
panelStyle={{ padding: 20 }}
maskLoader={store.isLoading}
panelChildren={
<>
{match(store.type)
@ -73,8 +72,11 @@ export const BehaviorTreeBuilderScreen = observer(() => {
textAlign: "center",
}}
>
{store.btTreeModels?.map((el) => (
<div style={{ display: "flex", justifyContent: "center", height: 30, alignItems: "center" }}>
{store.btTreeModels?.map((el, index) => (
<div
style={{ display: "flex", justifyContent: "center", height: 30, alignItems: "center" }}
key={index}
>
<CoreText text={el.name} type={CoreTextType.medium} />
<div style={{ width: 10 }} />
<Icon
@ -99,7 +101,7 @@ export const BehaviorTreeBuilderScreen = observer(() => {
{store.skillTemplates ? <SkillTree skills={store.skillTree} dragEnd={store.dragEnd} /> : null}
</div>
<div style={{ width: 100, height: 40 }}>
<CoreButton onClick={() => store.saveBt()} text="SAVE BT" />
<CoreButton onClick={() => store.onClickSaveBehaviorTree()} text="Сохранить" />
</div>
</div>
))
@ -110,23 +112,16 @@ export const BehaviorTreeBuilderScreen = observer(() => {
}
bodyChildren={
<>
{match(store.type)
.with(StoreUIType.SelectBehaviorTree, () => <></>)
.with(StoreUIType.ViewBehaviorTree, () => (
<div style={{ display: "flex", width: "100%" }}>
<div
ref={ref}
style={{
width: "100%",
height: window.innerHeight,
background: "white",
}}
></div>
</div>
))
.otherwise(() => (
<></>
))}
<div style={{ display: "flex", width: "100%" }}>
<div
ref={ref}
style={{
width: "100%",
height: window.innerHeight,
background: "white",
}}
/>
</div>
<Drawer
title={store.titleDrawer}
destroyOnClose={true}
@ -138,7 +133,7 @@ export const BehaviorTreeBuilderScreen = observer(() => {
<CoreInput label="Имя дерева" onChange={(text) => store.updateForm({ name: text })} />
</div>
<div style={{ display: "flex" }}>
<CoreButton text="Сохранить" filled={true} onClick={() => store.createNewBt()} />
<CoreButton text="Сохранить" filled={true} onClick={() => store.createNewBehaviorTree()} />
<div style={{ width: 10 }} />
<CoreButton text="Отмена" onClick={() => store.edtDrawer(DrawerState.newBehaviorTree, false)} />
</div>
@ -152,26 +147,21 @@ export const BehaviorTreeBuilderScreen = observer(() => {
>
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}>
<div>
{store.skillTemplates?.getForms(store.selected ?? "").map((formType) =>
forms
{store.skillTemplates?.getForms(store.selected ?? "").map((formType, index) =>
forms(
store.filledOutTemplates?.getDependencyBySkillLabelAndType(
forms(null, () => {}).find((form) => form.name.isEqual(formType))?.name ?? "",
store.selectedSid ?? ""
),
(dependency) => store.formUpdateDependency(dependency, formType)
)
.rFind<IForms>((form) => form.name.isEqual(formType))
.fold(
(s) => (
<div>
{/* {s.component(store.skillTemplates?.getDependencyBySkillLabelAndType(store.selected ?? "", s.name))} */}
forms.at(0)?.component({})
</div>
),
() => <div>Error: Unknown form type {formType}</div>
(s) => <div key={index}>{s.component}</div>,
() => <div key={index + "error"}>Error: Unknown form type {formType}</div>
)
)}
</div>
<div style={{ display: "flex" }}>
<CoreButton text="Сохранить" filled={true} onClick={() => store.createNewBt()} />
<div style={{ width: 10 }} />
<CoreButton text="Отмена" onClick={() => store.edtDrawer(DrawerState.newBehaviorTree, false)} />
</div>
</div>
</Drawer>
</>

View file

@ -1,20 +1,28 @@
import makeAutoObservable from "mobx-store-inheritance";
import xmlFormat from "xml-formatter";
import { CoreError, UiFormState } from "../../../core/store/base_store";
import { BtBuilderModel, BtNodeView, ReteObserver } from "../model/editor_view";
import {
BehaviorTreeBuilderModel,
BtNodeView as BtNodeObserver,
NodeRerenderObserver,
ReteForceUpdateObserver,
UpdateEvent,
} from "../model/editor_view";
import { NodeEditor } from "rete";
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_repository";
import { Skills } from "../../../core/model/skill_model";
import { IParam, Skills } from "../../../core/model/skill_model";
import { ISkillView } from "./ui/skill_tree/skill_tree";
import { message } from "antd";
import { BtTreeModel } from "../model/bt_tree_model";
import { NavigateFunction } from "react-router-dom";
import { BehaviorTreeViewModel } from "../model/behavior_tree_view_model";
import { UiBaseError } from "../../../core/model/ui_base_error";
import React from "react";
import { v4 } from "uuid";
import { behaviorTreeBuilderStore } from "./behavior_tree_builder_screen";
import clone from "just-clone";
import { BehaviorTreeModel } from "../model/behavior_tree_model";
interface I2DArea {
x: number;
@ -22,43 +30,52 @@ interface I2DArea {
w: number;
h: number;
}
export enum DrawerState {
newBehaviorTree = "Новое дерево поведения",
editThreadBehaviorTree = "Редактирование",
}
export enum StoreUIType {
SelectBehaviorTree,
ViewBehaviorTree,
}
export enum SystemPrimitive {
Sequence = "Sequence",
Fallback = "Fallback",
}
export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel, CoreError> {
type: StoreUIType;
type: StoreUIType = StoreUIType.ViewBehaviorTree;
viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty();
skillTemplates?: Skills;
behaviorTreeModel: BehaviorTreeModel = BehaviorTreeModel.empty();
skillTemplates: Skills = Skills.empty();
filledOutTemplates: Skills = Skills.empty();
area?: I2DArea;
btNodeView: BtNodeView = new BtNodeView();
reteNode?: ReteObserver;
btTreeModels?: BtTreeModel[];
behaviorTreeBuilderRepository = new BehaviorTreeBuilderHttpRepository();
btNodeObserver: BtNodeObserver = new BtNodeObserver();
reteForceUpdateObserver?: ReteForceUpdateObserver;
btTreeModels: BehaviorTreeModel[] = [];
activeProject: string = "";
behaviorTreeBuilderHttpRepository = new BehaviorTreeBuilderHttpRepository();
canRun = true;
shiftIsPressed = false;
selected?:string;
selected: string = "";
selectedSid: string = "";
deleteIsPressed = false;
nodes: NodeBehaviorTree[] = [];
nodeBehaviorTree: NodeBehaviorTree[] = [];
navigate?: NavigateFunction;
editor?: NodeEditor<Schemes>;
areaPlugin?: AreaPlugin<Schemes, AreaExtra>;
skillTree: ISkillView = {
name: "",
children: [
{
name: "Actions",
name: "Действия",
children: [],
},
{
name: "Control",
name: "Примитивы BT",
children: [
{ name: "Fallback", interface: "Vector3", out: "vo" },
{ name: "Sequence", interface: "Vector3", out: "vo" },
@ -66,42 +83,19 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
},
],
};
nodeUpdateObserver?: NodeRerenderObserver;
constructor() {
super(DrawerState);
makeAutoObservable(this);
this.type = StoreUIType.SelectBehaviorTree;
}
setBt(value: string): void {
JSON.parse(value) as BtNodeView[];
}
bt = async (editor: NodeEditor<Schemes>, area: AreaPlugin<Schemes, AreaExtra>) => {
(await BtBuilderModel.fromReteScene(editor, area, this.skillTemplates)).fold(
(s) => {
console.log(
xmlFormat(`<?xml version="1.0" encoding="UTF-8"?>
<root main_tree_to_execute="Main">
<BehaviorTree ID="Main">
${s}
</BehaviorTree>
<TreeNodesModel>
<Action ID="RbsBtAction">
<input_port name="do" />
<input_port name="command" />
<input_port name="server_name" />
<input_port name="server_timeout" />
</Action>
</TreeNodesModel>
</root>`)
);
},
(_) => _
);
syncScene = async (editor: NodeEditor<Schemes>, area: AreaPlugin<Schemes, AreaExtra>) => {
this.editor = editor;
this.areaPlugin = area;
};
errorHandingStrategy: (error: CoreError) => void;
errorHandingStrategy = (_: CoreError) => {};
dragEnd = (e: EventTarget) => {
if (this.canRun) {
@ -114,7 +108,7 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
}, 100);
}
};
drawSkillCheck(x: number, y: number, name: string) {
drawSkillCheck = (x: number, y: number, name: string) => {
const drawPoint = { x: x, y: y, w: 1, h: 1 };
if (
drawPoint.x < this.area!.x + this.area!.w &&
@ -122,20 +116,39 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
drawPoint.y < this.area!.y + this.area!.h &&
drawPoint.y + drawPoint.h > this.area!.y
) {
this.btNodeView.emit({
const sid = v4();
this.btNodeObserver.emit({
x: x,
y: y - (this.area!.y + this.area!.h / 2),
name: name,
id: sid,
});
if (!name.isEqualMany(Object.keys(SystemPrimitive))) {
this.skillTemplates?.getSkill(name).fold(
(m) => {
const model = clone(m);
const modelSetId = model.setSid(sid);
modelSetId.BTAction = m.BTAction.filter((el) => el.name.isEqual(name));
this.filledOutTemplates?.skills.push(modelSetId);
},
() => console.log("error")
);
}
}
}
};
async init(navigate: NavigateFunction): Promise<any> {
(await this.behaviorTreeBuilderRepository.getBtSkills()).fold(
(await this.behaviorTreeBuilderHttpRepository.getActiveProjectId())
// eslint-disable-next-line array-callback-return
.map((el) => {
this.activeProject = el.id;
});
(await this.behaviorTreeBuilderHttpRepository.getBtSkills()).fold(
(model) => {
this.skillTemplates = model;
this.skillTree.children = this.skillTree.children?.map((el) => {
if (el.name === "Actions") {
if (el.name === "Действия") {
el.children = model.toSkillView();
}
return el;
@ -143,7 +156,7 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
},
(e) => console.log(e)
);
await this.mapOk("btTreeModels", this.behaviorTreeBuilderRepository.getAllBtInstances());
await this.mapOk("btTreeModels", this.behaviorTreeBuilderHttpRepository.getAllBtInstances());
this.navigate = navigate;
document.addEventListener("keydown", (event) => {
@ -165,11 +178,18 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
}
});
}
initParams = async (id?: string) => {
initParam = async (id?: string) => {
this.isLoading = true;
this.type = StoreUIType.ViewBehaviorTree;
if (id) {
this.type = StoreUIType.ViewBehaviorTree;
(await this.behaviorTreeBuilderRepository.getBtById(id ?? "")).fold(
() => {},
(await this.behaviorTreeBuilderHttpRepository.getBtById(id)).fold(
(model) => {
this.nodeBehaviorTree = model.scene;
this.behaviorTreeModel = model;
if (model.skills) this.filledOutTemplates = model.skills;
this.isLoading = false;
this.reteForceUpdateObserver?.emit("");
},
() => this.errors.push(new UiBaseError(`не найдено дерево с id:${id}`))
);
} else {
@ -184,53 +204,124 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
h: height,
};
}
dispose() {
// TODO(IDONTSUDO): DELETE DOCUMENT LISTENERS
}
saveBt(): void {
this.reteNode?.emit("");
dispose(): void {
document.removeEventListener("keyup", () => {});
document.removeEventListener("keydown", () => {});
}
onClickSaveBehaviorTree = async (): Promise<void> => {
this.filledOutTemplates.validation().fold(
async () => {
(
await BehaviorTreeBuilderModel.fromReteScene(
this.editor as NodeEditor<Schemes>,
this.areaPlugin as AreaPlugin<Schemes, AreaExtra>,
this.filledOutTemplates
)
).fold(
(xml) => {
this.behaviorTreeModel.skills = this.filledOutTemplates;
this.behaviorTreeModel.scene = NodeBehaviorTree.fromReteScene(
this.editor as NodeEditor<Schemes>,
this.areaPlugin as AreaPlugin<Schemes, AreaExtra>
);
this.behaviorTreeModel.xml = xml;
this.behaviorTreeModel.project = this.activeProject;
this.messageHttp(this.behaviorTreeBuilderHttpRepository.editBt(this.behaviorTreeModel), {
successMessage: "Дерево поведения сохранено",
});
},
(_) => message.error(`Дерево поведения ошибка: ${_}`)
);
},
async () => message.error(`Дерево поведения не заполнено`)
);
};
validateBt() {}
createNewBt = async () => {
createNewBehaviorTree = async () => {
this.viewModel.project = this.activeProject;
this.viewModel.valid().fold(
async (model) => {
await this.messageHttp(this.behaviorTreeBuilderRepository.saveNewBt(model), {
await this.messageHttp(this.behaviorTreeBuilderHttpRepository.saveNewBt(model), {
successMessage: "Новое дерево создано",
});
this.mapOk("btTreeModels", this.behaviorTreeBuilderRepository.getAllBtInstances());
this.mapOk("btTreeModels", this.behaviorTreeBuilderHttpRepository.getAllBtInstances());
},
async (error) => message.error(error)
);
};
setSelected = (label: string, selected: boolean) => {
if (this.shiftIsPressed) {
setSelected = (label: string, selected: boolean, sid: string) => {
this.selectedSid = sid;
this.selected = label;
if (
this.shiftIsPressed &&
!Object.keys(SystemPrimitive).includes(label) &&
this.skillTemplates.skillHasForm(label)
) {
this.edtDrawer(DrawerState.editThreadBehaviorTree, selected);
this.selected = label
this.selected = label;
}
if (this.deleteIsPressed) {
this.nodeBehaviorTree = this.nodeBehaviorTree.filter((el) => !el.id.isEqual(sid));
this.filledOutTemplates.skills = this.filledOutTemplates.deleteSid(sid);
this.nodeUpdateObserver?.emit({ type: UpdateEvent.DELETE, id: sid });
}
};
getBodyNode(label: string): React.ReactNode | null {
formUpdateDependency = (dependency: Object, formType: string) => {
this.edtDrawer(DrawerState.editThreadBehaviorTree, false);
this.filledOutTemplates?.skillBySid(this.selectedSid ?? "").fold(
(m) => {
const model = clone(m);
model.BTAction.forEach((action) => {
const result: IParam[] = [];
action.param.forEach((param) => {
const paramClone = clone(param);
if (param.type.isEqual(formType)) {
paramClone.dependency = dependency;
}
result.push(paramClone);
});
action.param = result;
return action;
});
this.filledOutTemplates.updateSkill(model);
},
() => console.log("UNKNOWN SID: " + this.selectedSid)
);
this.nodeUpdateObserver?.emit({ id: this.selectedSid, type: UpdateEvent.UPDATE });
};
getBodyNode(label: string, sid: string): React.ReactNode | null {
if (Object.keys(SystemPrimitive).includes(label)) {
return null;
}
return (
<div style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "flex-start" }}>
{this.skillTemplates?.getSkillParams(label).map((el) => (
<div style={{ display: "flex", flexDirection: "row", width: "100%", marginBottom: 5 }}>
<div style={{ marginRight: 8 }}>IN</div>
<div style={{ display: "flex", width: "100%", backgroundColor: "rgba(255, 255, 255, 0.33)" }}>
{this.skillTemplates?.getSkillParams(label).map((el, index) => (
<div style={{ display: "flex", flexDirection: "row", width: "100%", marginBottom: 5 }} key={index}>
<div style={{ marginRight: 8, padding: 5 }}>IN</div>
<div style={{ display: "flex", width: "100%", backgroundColor: "rgba(255, 255, 255, 0.33)", padding: 5 }}>
{el.type}
{behaviorTreeBuilderStore.isFilledInput(el.type, sid) ? "" : <span style={{ color: "red" }}>*</span>}
</div>
</div>
))}
{this.skillTemplates?.getSkilsOut(label).map((el) => (
<div style={{ display: "flex", flexDirection: "row", width: "100%", marginBottom: 5 }}>
<div style={{ marginRight: 8 }}>OUT</div>
<div style={{ display: "flex", width: "100%", backgroundColor: "rgba(255, 255, 255, 0.33)" }}>{el}</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}
</div>
</div>
))}
</div>
);
}
isFilledInput = (type: string, sid: string): boolean => this.filledOutTemplates?.dependencyIsFilled(type, sid);
getInputs(name: string) {
if (Object.keys(SystemPrimitive).includes(name)) {
return {

View file

@ -1,10 +0,0 @@
import { BaseSchemes } from "rete";
import { AreaPlugin } from "rete-area-plugin";
import './background.css'
export function addCustomBackground<S extends BaseSchemes, K>(area: AreaPlugin<S, K>) {
const background = document.createElement("div");
background.classList.add("background");
background.classList.add("fill-area");
area.area.content.add(background);
}

View file

@ -4,25 +4,36 @@ import { AreaPlugin, AreaExtensions } from "rete-area-plugin";
import { ConnectionPlugin, Presets as ConnectionPresets } from "rete-connection-plugin";
import { ReactPlugin, Presets, ReactArea2D } from "rete-react-plugin";
import { CustomConnection } from "./custom_connection";
import { addCustomBackground } from "./custom_background";
import { behaviorTreeBuilderStore } from "../../behavior_tree_builder_screen";
import { SequenceNode } from "./nodes/controls_node";
import { ReteObserver } from "../../../model/editor_view";
import { CustomSocket } from "./custom_socket";
import { BaseSchemes } from "rete";
import {
NodeRerenderObserver as NodeUpdateObserver,
ReteForceUpdateObserver,
UpdateEvent,
} from "../../../model/editor_view";
import { v4 } from "uuid";
export type Schemes = GetSchemes<ClassicPreset.Node, ClassicPreset.Connection<ClassicPreset.Node, ClassicPreset.Node>>;
export type AreaExtra = ReactArea2D<Schemes>;
export function addCustomBackground<S extends BaseSchemes, K>(area: AreaPlugin<S, K>) {
const background = document.createElement("div");
background.classList.add("background");
background.classList.add("fill-area");
area.area.content.add(background);
}
export async function createEditor(container: HTMLElement) {
const socket = new ClassicPreset.Socket("socket");
const observer = new ReteObserver();
behaviorTreeBuilderStore.reteNode = observer;
behaviorTreeBuilderStore.btNodeView.on(async (event) => {
behaviorTreeBuilderStore.btNodeObserver.on(async (event) => {
setTimeout(async () => {
const node = new ClassicPreset.Node(event.name);
const { x, y } = areaContainer.area.pointer;
node.id = event.id;
const { output, input } = behaviorTreeBuilderStore.getInputs(event.name);
if (output) node.addOutput(output, new ClassicPreset.Output(socket));
if (input) node.addInput("b", new ClassicPreset.Input(socket));
@ -30,24 +41,46 @@ export async function createEditor(container: HTMLElement) {
await areaContainer.translate(node.id, { x, y });
}, 100);
});
behaviorTreeBuilderStore.reteForceUpdateObserver = new ReteForceUpdateObserver();
behaviorTreeBuilderStore.reteForceUpdateObserver!.on(async () => {
for await (const el of behaviorTreeBuilderStore.nodeBehaviorTree) {
const node = new ClassicPreset.Node(el.label);
node.id = el.id;
observer.on(() => {
behaviorTreeBuilderStore.bt(editor, areaContainer);
el.outputs.forEach((outputName) => {
node.addOutput(outputName, new ClassicPreset.Output(socket));
});
el.inputs.forEach((inputName) => {
node.addInput(inputName, new ClassicPreset.Input(socket));
});
await editor.addNode(node);
await areaContainer.translate(node.id, el.position);
}
for await (const el of behaviorTreeBuilderStore.nodeBehaviorTree) {
if (el.connectTo)
editor.addConnection({
id: v4(),
sourceOutput: "a",
targetInput: "b",
source: el.connectTo as string,
target: el.id,
});
}
});
const editor = new NodeEditor<Schemes>();
const areaContainer = new AreaPlugin<Schemes, AreaExtra>(container);
const connection = new ConnectionPlugin<Schemes, AreaExtra>();
const render = new ReactPlugin<Schemes, AreaExtra>({ createRoot });
AreaExtensions.selectableNodes(areaContainer, AreaExtensions.selector(), {
accumulating: AreaExtensions.accumulateOnCtrl(),
render.addPipe((el) => {
behaviorTreeBuilderStore.syncScene(editor, areaContainer);
return el;
});
render.addPreset(
Presets.classic.setup({
customize: {
node(context) {
node(_) {
return SequenceNode;
},
socket(_context) {
@ -59,28 +92,18 @@ export async function createEditor(container: HTMLElement) {
},
})
);
connection.addPreset(ConnectionPresets.classic.setup());
addCustomBackground(areaContainer);
render.addPipe((context) => {
if (context.type === "rendered") {
if (context.data.type === "node") {
context.data.element.addEventListener("dblclick", (event) => {
console.log(event);
});
}
}
return context;
});
editor.use(areaContainer);
areaContainer.use(connection);
areaContainer.use(render);
AreaExtensions.simpleNodesOrder(areaContainer);
for await (const el of behaviorTreeBuilderStore.nodes) {
const nodeUpdateObserver = new NodeUpdateObserver();
behaviorTreeBuilderStore.nodeUpdateObserver = nodeUpdateObserver;
for await (const el of behaviorTreeBuilderStore.nodeBehaviorTree) {
const node = new ClassicPreset.Node(el.label);
node.id = el.id;
el.outputs.forEach((outputName) => {
@ -89,19 +112,37 @@ export async function createEditor(container: HTMLElement) {
el.inputs.forEach((inputName) => {
node.addInput(inputName, new ClassicPreset.Input(socket));
});
await editor.addNode(node);
await areaContainer.translate(node.id, el.position);
}
behaviorTreeBuilderStore.nodes.forEach(async (el) => {
if (el.connectTo) {
const nodeConnectTo = editor.getNode(el.connectTo!);
const nodeConnect = editor.getNode(el.id);
await editor.addConnection(new ClassicPreset.Connection(nodeConnectTo, "a", nodeConnect, "a"));
for await (const el of behaviorTreeBuilderStore.nodeBehaviorTree) {
if (el.connectTo)
editor.addConnection({
id: v4(),
sourceOutput: "a",
targetInput: "b",
source: el.connectTo as string,
target: el.id,
});
}
nodeUpdateObserver.on(async (event) => {
if (event.type.isEqual(UpdateEvent.UPDATE)) {
areaContainer.update("node", event.id);
}
if (event.type.isEqual(UpdateEvent.DELETE)) {
editor
.getConnections()
.forEach((el) =>
el.source.isEqual(event.id) || el.target.isEqual(event.id) ? editor.removeConnection(el.id) : null
);
areaContainer.removeNodeView(event.id);
}
});
AreaExtensions.selectableNodes(areaContainer, AreaExtensions.selector(), {
accumulating: AreaExtensions.accumulateOnCtrl(),
});
setTimeout(() => {
AreaExtensions.zoomAt(areaContainer, editor.getNodes());
}, 100);

View file

@ -102,18 +102,17 @@ export function SequenceNode<Scheme extends ClassicScheme>(props: Props<Scheme>)
sortByIndex(inputs);
sortByIndex(outputs);
sortByIndex(controls);
behaviorTreeBuilderStore.setSelected(label, selected);
behaviorTreeBuilderStore.setSelected(label, selected, id);
return (
<>
<NodeStyles
selected={selected}
width={width}
onDoubleClick={() => {
console.log("double");
}}
style={behaviorTreeBuilderStore.getStylesByLabelNode(label)}
data-testid="node"
className="node"
id="node"
>
<div style={{ display: "inline-flex", width: "100%", justifyContent: "space-between" }}>
@ -155,7 +154,7 @@ export function SequenceNode<Scheme extends ClassicScheme>(props: Props<Scheme>)
data-testid="title"
>
<div>{label}</div>
<div>{behaviorTreeBuilderStore.getBodyNode(label)}</div>
<div>{behaviorTreeBuilderStore.getBodyNode(label,id)}</div>
</div>
{outputs.map(
([key, output]) =>

View file

@ -1,30 +0,0 @@
import { Presets } from "rete-react-plugin";
import { css } from "styled-components";
import "./background.css";
const styles = css<{ selected?: boolean }>`
background: #ebebeb;
border-color: #646464;
.title {
color: #646464;
}
&:hover {
background: #f2f2f2;
}
.output-socket {
margin-right: -1px;
}
.input-socket {
margin-left: -1px;
}
${(props) =>
props.selected &&
css`
border-color: red;
`}
`;
export function StyledNode(props: any) {
// eslint-disable-next-line react/jsx-pascal-case
return <Presets.classic.Node styles={() => styles} {...props} />;
}

View file

@ -1,30 +0,0 @@
import { Presets } from "rete-react-plugin";
import { css } from "styled-components";
import "./background.css";
const styles = css<{ selected?: boolean }>`
background: #ebebeb;
border-color: #646464;
.title {
color: #646464;
}
&:hover {
background: #f2f2f2;
}
.output-socket {
margin-right: -1px;
}
.input-socket {
margin-left: -1px;
}
${(props) =>
props.selected &&
css`
border-color: red;
`}
`;
export function StyledNode(props: any) {
// eslint-disable-next-line react/jsx-pascal-case
return <Presets.classic.Node styles={() => styles} {...props} />;
}

View file

@ -1,8 +1,19 @@
import { SimpleForm } from "./simple_form";
import { TestForm } from "./test";
import WeightsForm from "./weights_form";
export interface IPropsForm<T> {
dependency: T;
onChange: (dependency: Object) => void;
}
export interface IForms {
name: string;
component: any;
component: JSX.Element;
}
export const forms = [{ name: "weights", component: WeightsForm }];
export const forms = (props: any, onChange: (dependency: Object) => void): IForms[] => [
{ name: "weights", component: <WeightsForm dependency={props} onChange={onChange} /> },
{ name: "simple", component: <SimpleForm dependency={props} onChange={onChange} /> },
{ name: "test", component: <TestForm dependency={props} onChange={onChange} /> },
];

View file

@ -0,0 +1,14 @@
import { CoreButton } from "../../../../../core/ui/button/button";
import { IPropsForm } from "./forms";
export interface ISimpleFormDependency {
simple?: string;
}
export const SimpleForm = (props: IPropsForm<ISimpleFormDependency>) => {
return (
<div>
<CoreButton onClick={() => props.onChange({ simple: "132" })} text="OK" />
</div>
);
};

View file

@ -0,0 +1,13 @@
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<ISimpleFormDependency>) => {
return (
<div>
<CoreText text="Test Form" type={CoreTextType.header} />
<CoreButton onClick={() => props.onChange({ test: "132" })} text="OK" />
</div>
);
};

View file

@ -1,39 +1,173 @@
import { IWeightsDependency } from "../../../../../core/model/skill_model";
import makeAutoObservable from "mobx-store-inheritance";
import { CoreError, FormState } from "../../../../../core/store/base_store";
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 { Spin } from "antd";
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";
interface IWeightsFormProps {
dependency?: IWeightsDependency;
}
export class WeightsViewModel {
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<string, undefined> => {
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<WeightsViewModel, CoreError> {
errorHandingStrategy = (error: CoreError) => {};
weights: ISkils[];
weights?: ISkils[];
assets?: Assets;
suitableWeights: string[] = [];
viewModel: WeightsViewModel = WeightsViewModel.empty();
skillsHttpRepository: SkillsHttpRepository = new SkillsHttpRepository();
datasetHttpRepository: DataSetHttpRepository = new DataSetHttpRepository();
constructor() {
super();
makeAutoObservable(this);
}
init = async () => {
this.mapOk("weights", this.skillsHttpRepository.getAllSkills());
await this.mapOk("weights", this.skillsHttpRepository.getAllSkills());
await this.mapOk("assets", this.datasetHttpRepository.getAssetsActiveProject());
};
}
const WeightsForm = observer((props: IWeightsFormProps) => {
// const [store] = React.useState(() => new WeightsFormStore());
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);
// React.useEffect(() => {
// store.init();
// }, []);
return <div>123</div>;
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 (
<div>
<Select
showSearch
placeholder="Выберите деталь"
optionFilterProp="children"
defaultValue={props.dependency?.object_name}
onChange={(e) => {
store.updateForm({ object_name: e });
store.selectAsset();
}}
filterOption={(input: string, option?: { label: string; value: string }) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
style={{ width: "100%" }}
options={
store.assets?.assets.map((el) => {
return { label: el.name, value: el.name };
}) ?? []
}
/>
<Select
showSearch
placeholder="Выберите деталь"
optionFilterProp="children"
defaultValue={props.dependency?.weights_name}
onChange={(e) => {
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 };
}) ?? []
}
/>
<div style={{ height: 5 }} />
<CoreInput
label={"dimensions 1"}
onChange={(text) => store.changeDimensions(0, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(0)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreInput
label={"dimensions 2"}
onChange={(text) => store.changeDimensions(1, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(1)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreInput
label={"dimensions 3"}
onChange={(text) => store.changeDimensions(2, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(2)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreButton
onClick={() => {
store.viewModel.isEmpty().fold(
() => {
props.onChange(store.viewModel);
},
(e) => {
message.error(e);
}
);
}}
text="OK"
style={{ width: 100 }}
/>
</div>
);
});
export default WeightsForm;

View file

@ -3,6 +3,7 @@ import TreeView, { EventCallback, IBranchProps, INode, LeafProps, flattenTree }
import { IFlatMetadata } from "react-accessible-treeview/dist/TreeView/utils";
import { CallBackEventTarget } from "../../../../../core/extensions/extensions";
import "./styles.css";
import { CoreText, CoreTextType } from "../../../../../core/ui/text/text";
export interface ISkillView {
name: string;
@ -48,9 +49,31 @@ export const RefListener = (props: IRefListerProps) => {
}}
/>
<div ref={ref} style={{ color: "black" }} draggable={props.isBranch ? undefined : "true"}>
{props.isBranch ? "* ":""}
{props.element.name}
<div
ref={ref}
style={{ color: "black", alignItems: "center", height: "max-content", display: "flex" }}
draggable={props.isBranch ? undefined : "true"}
>
{props.isBranch ? (
<svg
style={{ marginRight: 5, width: 8 }}
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6 12C9.31371 12 12 9.31371 12 6C12 2.68629 9.31371 0 6 0C2.68629 0 0 2.68629 0 6C0 9.31371 2.68629 12 6 12Z"
fill="#49454F"
/>
</svg>
) : (
""
)}
<CoreText text={props.element.name} type={CoreTextType.large} />
</div>
</div>
);

View file

@ -3,7 +3,7 @@ import { HttpError, HttpMethod, HttpRepository } from "../../core/repository/htt
import { UUID } from "../all_projects/data/project_repository";
import { Assets, DataSetModel, Dataset, IDatasetModel, ProcessStatus } from "./dataset_model";
export class DataSetRepository extends HttpRepository {
export class DataSetHttpRepository extends HttpRepository {
editDataset(dataSetModel: DataSetModel) {
dataSetModel.processStatus = ProcessStatus.NEW;
return this._jsonRequest<void>(HttpMethod.PUT, `/datasets`, dataSetModel);

View file

@ -1,5 +1,5 @@
import makeAutoObservable from "mobx-store-inheritance";
import { DataSetRepository } from "./dataset_repository";
import { DataSetHttpRepository } from "./dataset_http_repository";
import { Drawer, UiErrorState } from "../../core/store/base_store";
import { HttpError } from "../../core/repository/http_repository";
import { Asset, Assets, DataSetModel, IDatasetModel, ProcessStatus } from "./dataset_model";
@ -13,7 +13,7 @@ export enum DrawersDataset {
}
export class DataSetStore extends UiErrorState<HttpError> {
dataSetRepository: DataSetRepository;
dataSetRepository: DataSetHttpRepository;
assets?: Assets;
datasets?: IDatasetModel[];
activeProject: UUID;
@ -25,7 +25,7 @@ export class DataSetStore extends UiErrorState<HttpError> {
constructor() {
super();
this.socketRepository = socketRepository;
this.dataSetRepository = new DataSetRepository();
this.dataSetRepository = new DataSetHttpRepository();
this.drawers = Object.entries(DrawersDataset).map((k, v) => {
return {
name: k.at(1) ?? "",

View file

@ -1,8 +1,6 @@
import * as React from "react";
import { PipelineInstanceStore } from "./pipeline_instance_store";
import { useNavigate } from "react-router-dom";
import { Icon } from "../../core/ui/icons/icons";
import { CardDataSet, CardDataSetType } from "../dataset/card_dataset";
import { MainPage } from "../../core/ui/pages/main_page";
export const PipelineInstanceScreenPath = "/pipeline_instance/";

View file

@ -2,7 +2,6 @@ import * as React from "react";
import { SceneMangerStore } from "./scene_manager_store";
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";
import { SceneManagerView, SceneMode } from "../model/scene_view";
import { MainPage } from "../../../core/ui/pages/main_page";
export const SceneManagerPath = "/scene/manager/";
@ -22,12 +21,6 @@ export const SceneManger = observer(() => {
};
}, [id, store]);
const sceneIcons: SceneManagerView[] = Object.values(SceneMode)
.filter((el) => el !== SceneMode.EMPTY)
.map((el) => {
return { name: el, clickHandel: () => store.setSceneMode(el as SceneMode) };
});
return (
<MainPage
page={"Сцена"}

View file

@ -1,5 +1,4 @@
import * as React from "react";
import { ReactComponent as ReloadIcon } from "../../core/assets/icons/reload.svg";
import { socketListerStore } from "./socket_lister_store";
import { observer } from "mobx-react-lite";

View file

@ -1,6 +1,5 @@
import { makeAutoObservable } from "mobx";
import { SocketRepository, socketRepository } from "../../core/repository/socket_repository";
import { setTimeout } from "timers/promises";
class SocketListerStore {
repository: SocketRepository;

18
ui/tslint.json Normal file
View file

@ -0,0 +1,18 @@
{
"eslintConfig": {
"extends": ["react-app", "shared-config"],
"rules": {
"additional-rule": "warn"
},
"overrides": [
{
"files": ["**/*.ts?(x)"],
"rules": {
"additional-typescript-only-rule": "warn",
"array-callback-return": "off",
"react-hooks/exhaustive-deps": "off"
}
}
]
}
}