import makeAutoObservable from "mobx-store-inheritance"; import clone from "just-clone"; import { IsArray, IsEnum, IsNotEmpty, 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 { ValidationModel } from "./validation_model"; import { ITopicModel, TopicViewModel } from "../../features/topics/topic_view_model"; import { BehaviorTreeBuilderStore } from "../../features/behavior_tree_builder/presentation/behavior_tree_builder_store"; import { TopicDependencyViewModel } from "../../features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model"; import { FormBuilderValidationModel } from "./form_builder_validation_model"; import { FormType } from "./form"; export interface IDependency { skills: ISkillDependency[]; } export interface ISkillDependency { sid: string; dependency: Object; } export interface ISkill { sid?: string; SkillPackage: ISkillPackage; Module: IModule; BTAction: IBTAction[]; } export interface IWeightsDependency { weights_name: string; object_name: string; weights_file: string; dimensions: number[]; } export interface IDeviceDependency { sid: string; } export interface IDependency { } export interface IParam { isFilled: boolean; type: string; dependency?: DependencyViewModel; } abstract class IDependencyViewModel { abstract type: FormType; } export class DependencyViewModel implements IDependencyViewModel { type: FormType; static empty = () => new DependencyViewModel(); toView = (store: BehaviorTreeBuilderStore | undefined): React.ReactNode => "Dependency view model error"; } export class ParamViewModel implements IParam { type: string; @Type(() => DependencyViewModel, { discriminator: { property: "type", subTypes: [ { value: TopicDependencyViewModel, name: FormType.topic }, { value: FormBuilderValidationModel, name: FormType.formBuilder }, ], }, keepDiscriminatorProperty: true, }) dependency: DependencyViewModel; isFilled: boolean = false; constructor(type: string, dependency: DependencyViewModel) { this.type = type; this.dependency = dependency; } static empty = () => new ParamViewModel("", DependencyViewModel.empty()); } export interface IBTAction { name: string; type: string; param: IParam[]; result: string[]; } export enum BtAction { ACTION = "ACTION", CONDITION = "CONDITION", } export class BtActionViewModel extends ValidationModel implements IBTAction { @IsNotEmpty() @IsString() name: string; @IsNotEmpty() @IsString() type: string; @Type(() => ParamViewModel) param: IParam[]; @IsNotEmpty() @IsString() result: string[]; @IsNotEmpty() @IsEnum(BtAction) typeAction: BtAction; constructor(name: string, type: string, param: IParam[], result: string[], typeAction: BtAction) { super(); this.name = name; this.type = type; this.param = param; this.result = result; this.typeAction = typeAction; } static empty = () => new BtActionViewModel("", "", [], [], BtAction.ACTION); public validParam = (type: string) => this.param.someR((param) => param.type === type); } export interface IInterface { Input: IPut[]; Output: IPut[]; } export interface IPut { name: string; type: string; } export interface ILaunch { executable: string; } export interface IModule { name: string; node_name: string; description: string; } export interface IRos2 { node_name: string; } export interface ISetting { name: string; value: number | string; } export interface ISkillPackage { name: string; version: string; format: string; } export class SkillPackage implements ISkillPackage { @IsNotEmpty() @IsString() name: string; @IsNotEmpty() @IsString() version: string; @IsNotEmpty() @IsString() format: string; constructor(name: string, version: string, format: string) { this.name = name; this.format = format; this.version = version; } static empty = () => new SkillPackage("", "", ""); } export class Module implements IModule { @IsString() node_name: string; @IsString() name: string; @IsString() description: string; constructor(name: string, description: string) { this.name = name; this.description = description; } static empty = () => new Module("", ""); } export class BTAction implements IBTAction { @IsString() name: string; @IsString() type: string; sid?: string; @IsArray() param: IParam[]; @IsArray() result: string[]; } export class Launch implements ILaunch { @IsNotEmpty() @IsString() executable: string; @IsNotEmpty() @IsString() package: string; constructor(executable: string, plage: string) { this.executable = executable; this.package = plage; } static empty = () => new Launch("", ""); } export class Ros2 implements IRos2 { @IsString() node_name: string; } export class Put implements IPut { @IsString() name: string; @IsString() type: string; } export class Interface implements IInterface { @ValidateNested() @Type(() => Put) Input: IPut[]; @ValidateNested() @Type(() => Put) Output: IPut[]; } export class Setting implements ISetting { name: string; value: string | number; } export class SkillModel extends ValidationModel implements ISkill { @IsOptional() @IsString() _id?: string; constructor() { super(); makeAutoObservable(this); } bgColor: string = `rgba(5, 26, 39, 1)`; borderColor: string = "rgba(25, 130, 196, 1)"; @IsOptional() @IsString() sid?: string; @ValidateNested() @Type(() => SkillPackage) SkillPackage: ISkillPackage; @ValidateNested() @Type(() => Module) Module: IModule; @IsNotEmpty() @IsArray() @Type(() => BtActionViewModel) BTAction: BtActionViewModel[]; topicsOut: TopicViewModel[] = []; @Type(() => Launch) Launch: Launch; @Type(() => FormBuilderValidationModel) Settings: FormBuilderValidationModel; static empty() { const skillModel = new SkillModel(); skillModel.BTAction = []; skillModel.SkillPackage = SkillPackage.empty(); skillModel.Module = Module.empty(); skillModel.Launch = Launch.empty(); skillModel.Settings = FormBuilderValidationModel.empty(); return skillModel; } public static isEmpty(skill: SkillModel): Result { 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; }; emptyParam = () => this.BTAction.at(0)?.param.at(0)?.dependency === undefined; } 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 { constructor() { makeAutoObservable(this); } @IsOptional() @IsArray() @Type(() => TopicViewModel) topicsStack: ITopicModel[] = []; @IsArray() @Type(() => SkillModel) skills: SkillModel[]; static fromSkills = (skilsModel: SkillModel[]) => { const skills = this.empty(); skills.skills = skilsModel; return skills; }; deleteTopic = (sid: string) => (this.topicsStack = this.topicsStack.filter((el) => !el.sid?.isEqual(sid))); getSkillAtSid = (sid: string): Result => { const result = this.skills.filter((skill) => skill.sid?.isEqual(sid)).at(0); if (result === undefined) { return Result.error(undefined); } return Result.ok(result); }; validation = (): Result => { 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); }; getCssAtLabel = (label: string): React.CSSProperties => this.skills.reduce((acc, el) => { el.BTAction.rFind((el) => el.name.isEqual(label)).map(() => { acc = { backgroundColor: el.bgColor, border: `1px solid ${el.borderColor}`, }; }); return acc; }, {}); skillBySid = (sid: string) => SkillModel.isEmpty( this.skills.reduce((acc, el) => { if (el.sid?.isEqual(sid)) { acc = el; } return acc; }, SkillModel.empty()) ); toSkillView = (): ISkillView[] => this.skills.map((el) => { return { name: el.Module.name, children: el.BTAction.map((act) => { return { name: act.name, uuid: v4() }; }), }; }); getSkill = (name: string) => SkillModel.isEmpty( this.skills.reduce((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 .reduce((acc, el) => { if (el.BTAction.find((el) => el.name.isEqual(name))) { acc = el.BTAction.filter((f) => f.name.isEqual(name)).map((z) => z.result); } return acc; }, []) .flat(1); getSkillParams = (name: string) => this.skills .reduce((acc, el) => { if (el.BTAction.find((el) => el.name.isEqual(name))) { acc = el.BTAction.filter((f) => f.name.isEqual(name)).map((z) => z.param); } return acc; }, []) .flat(1); getSkillDo = (name: string) => this.skills.reduce((acc, el) => { if (el.BTAction.find((el) => el.name.isEqual(name))) { acc = el.Module.name; } return acc; }, "error"); getSkillsNames = () => this.skills .map((el) => { return el.BTAction.map((act) => { return { name: act.name }; }); }) .flat(1); getForms = (skillLabel: string) => this.skills .reduce((acc, el) => { if (el.BTAction.find((el) => el.name.isEqual(skillLabel))) { acc.push(el); } return acc; }, []) .map((el) => el.BTAction.map((act) => act.param.map((el) => el.type).flat(1))) .flat(1) .flat(1) .filter((el) => el !== ""); getDependencyBySkillLabelAndType = (skillType: string, sid: string): DependencyViewModel => this.skills .reduce((acc, skill) => { if (skill.sid?.isEqual(sid)) { skill.BTAction.map((action) => { action.param.map((param) => { if (param.type.isEqual(skillType)) { acc.push(param?.dependency ?? DependencyViewModel.empty()); } return param; }); return action; }); } return acc; }, []) .at(0) ?? DependencyViewModel.empty() static isEmpty(model: Skills): Result { 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) => { acc = param.isFilled; }); }); } return acc; }, false); getAllSids = () => this.skills.reduce((acc, skill) => { skill.BTAction.forEach((action) => action.param.forEach((param) => { return param; }) ); return acc; }, new Map()); deleteSid(sid: string): SkillModel[] { return this.skills.filter((skill) => !skill.sid?.isEqual(sid)); } updateSkill = (skill: SkillModel) => { this.skills = this.skills.map((el) => { if (el.sid?.isEqual(skill.sid ?? "")) { el = skill; } return el; }); }; skillHasForm = (label: string): boolean => { return true; }; getSkillWidthAtLabel = (label: string): number => this.getSkillParams(label).reduce((acc, element) => { if (element.type.length > acc) { acc = element.type.length; } return acc; }, 0); skillIsDependencyFilled = (label: string): Result => this.getSkill(label).fold( (skill) => { return Result.ok(skill.BTAction.find((el) => el.name.isEqual(label))?.param.isEmpty()); }, () => Result.error(false) ); skillIsCondition = (label: string) => this.getSkill(label).fold( (s) => s.BTAction.atR(0).map((el) => el.typeAction.isEqualR(BtAction.CONDITION)), (e) => Result.error(undefined) ); }