skills screen
This commit is contained in:
parent
cac5fad8ce
commit
a920f5fb45
49 changed files with 681 additions and 168 deletions
|
@ -9,6 +9,7 @@ import { CalculationsInstancesPresentation } from "../../features/calculations_i
|
||||||
import { DigitalTwinsInstancePresentation } from "../../features/digital_twins_instance/digital_twins_instance_presentation";
|
import { DigitalTwinsInstancePresentation } from "../../features/digital_twins_instance/digital_twins_instance_presentation";
|
||||||
import { DigitalTwinsTemplatePresentation } from "../../features/digital_twins_template/digital_twins_template_presentation";
|
import { DigitalTwinsTemplatePresentation } from "../../features/digital_twins_template/digital_twins_template_presentation";
|
||||||
import { TopicsPresentation } from "../../features/topics/topics_presentation";
|
import { TopicsPresentation } from "../../features/topics/topics_presentation";
|
||||||
|
import { SkillsPresentation } from "../../features/skills/skill_presentation";
|
||||||
|
|
||||||
extensions();
|
extensions();
|
||||||
|
|
||||||
|
@ -22,4 +23,5 @@ export const httpRoutes: Routes[] = [
|
||||||
new DigitalTwinsTemplatePresentation(),
|
new DigitalTwinsTemplatePresentation(),
|
||||||
new DigitalTwinsInstancePresentation(),
|
new DigitalTwinsInstancePresentation(),
|
||||||
new TopicsPresentation(),
|
new TopicsPresentation(),
|
||||||
|
new SkillsPresentation(),
|
||||||
].map((el) => el.call());
|
].map((el) => el.call());
|
||||||
|
|
|
@ -1,7 +1,26 @@
|
||||||
import { IsArray, IsString, ValidateNested } from "class-validator";
|
import { IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested } from "class-validator";
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
|
export class TopicViewModel {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
type: string;
|
||||||
|
constructor(name: string, type: string) {
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
static empty() {
|
||||||
|
return new TopicViewModel("", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface SkillPoseEstimation {
|
export interface IParam {
|
||||||
|
type: string;
|
||||||
|
dependency: Object;
|
||||||
|
}
|
||||||
|
export interface Skill {
|
||||||
SkillPackage: ISkillPackage;
|
SkillPackage: ISkillPackage;
|
||||||
Module: IModule;
|
Module: IModule;
|
||||||
Launch: ILaunch;
|
Launch: ILaunch;
|
||||||
|
@ -16,7 +35,7 @@ export interface IBTAction {
|
||||||
name: string;
|
name: string;
|
||||||
format: string;
|
format: string;
|
||||||
type: string;
|
type: string;
|
||||||
param: string[];
|
param: IParam[];
|
||||||
result: string[];
|
result: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +101,7 @@ export class BTAction implements IBTAction {
|
||||||
@IsString()
|
@IsString()
|
||||||
type: string;
|
type: string;
|
||||||
@IsArray()
|
@IsArray()
|
||||||
param: string[];
|
param: IParam[];
|
||||||
@IsArray()
|
@IsArray()
|
||||||
result: string[];
|
result: string[];
|
||||||
}
|
}
|
||||||
|
@ -117,7 +136,7 @@ export class Xxx implements IXxx {
|
||||||
topicImage: string;
|
topicImage: string;
|
||||||
topicCameraInfo: string;
|
topicCameraInfo: string;
|
||||||
}
|
}
|
||||||
export class SkillModelPoseEstimation implements SkillPoseEstimation {
|
export class SkillModel implements Skill {
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => SkillPackage)
|
@Type(() => SkillPackage)
|
||||||
SkillPackage: ISkillPackage;
|
SkillPackage: ISkillPackage;
|
||||||
|
@ -145,3 +164,26 @@ export class SkillModelPoseEstimation implements SkillPoseEstimation {
|
||||||
@Type(() => Xxx)
|
@Type(() => Xxx)
|
||||||
xxx: IXxx;
|
xxx: IXxx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum BtAction {
|
||||||
|
ACTION = "ACTION",
|
||||||
|
CONDITION = "CONDITION",
|
||||||
|
}
|
||||||
|
export class BtActionViewModel implements IBTAction {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
format: string;
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
type: string;
|
||||||
|
param: IParam[];
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
result: string[];
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsEnum(BtAction)
|
||||||
|
typeAction: BtAction;
|
||||||
|
}
|
||||||
|
|
|
@ -13,3 +13,4 @@ export class CreateManyFolderScenario {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,27 @@ export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithE
|
||||||
{ name: "end_effector_acceleration", value: 1.0 },
|
{ name: "end_effector_acceleration", value: 1.0 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Module: { name: "MMM", description: "Move to Pose skill with cartesian controllers" },
|
||||||
|
topicOut: [
|
||||||
|
{
|
||||||
|
topicName: "",
|
||||||
|
topicType: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
BTAction: [
|
||||||
|
{
|
||||||
|
name: "move",
|
||||||
|
type: "action",
|
||||||
|
param: [
|
||||||
|
{
|
||||||
|
type: "move_to_pose",
|
||||||
|
dependency: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
SkillPackage: { name: "Robossembler", version: "1.0", format: "1", type: "Action" },
|
SkillPackage: { name: "Robossembler", version: "1.0", format: "1", type: "Action" },
|
||||||
Module: { name: "PoseEstimation", description: "Pose Estimation skill with DOPE" },
|
Module: { name: "PoseEstimation", description: "Pose Estimation skill with DOPE" },
|
||||||
|
|
25
server/src/features/skills/model/skills_database_model.ts
Normal file
25
server/src/features/skills/model/skills_database_model.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface ISkillsModel {}
|
||||||
|
|
||||||
|
export const SkillsSchema = new Schema({
|
||||||
|
SkillPackage: {
|
||||||
|
type: Schema.Types.Mixed,
|
||||||
|
},
|
||||||
|
Module: {
|
||||||
|
type: Schema.Types.Mixed,
|
||||||
|
},
|
||||||
|
BTAction: {
|
||||||
|
type: Schema.Types.Mixed,
|
||||||
|
},
|
||||||
|
topicsOut: {
|
||||||
|
type: Schema.Types.Mixed,
|
||||||
|
},
|
||||||
|
param: {
|
||||||
|
type: Schema.Types.Mixed,
|
||||||
|
},
|
||||||
|
}).plugin(require("mongoose-autopopulate"));
|
||||||
|
|
||||||
|
export const skillsSchema = "skills";
|
||||||
|
|
||||||
|
export const SkillsDBModel = model<ISkillsModel>(skillsSchema, SkillsSchema);
|
17
server/src/features/skills/model/skills_validation_model.ts
Normal file
17
server/src/features/skills/model/skills_validation_model.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { IsOptional, IsString, ValidateNested } from "class-validator";
|
||||||
|
import { SkillPackage, ISkillPackage, Module, IModule, BtActionViewModel, TopicViewModel } from "../../../core/models/skill_model";
|
||||||
|
|
||||||
|
export class SkillInstanceValidationModel {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
sid?: string;
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => SkillPackage)
|
||||||
|
SkillPackage: ISkillPackage;
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => Module)
|
||||||
|
Module: IModule;
|
||||||
|
BTAction: BtActionViewModel[];
|
||||||
|
topicsOut: TopicViewModel[] = [];
|
||||||
|
}
|
12
server/src/features/skills/skill_presentation.ts
Normal file
12
server/src/features/skills/skill_presentation.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { CrudController } from "../../core/controllers/crud_controller";
|
||||||
|
import { SkillInstanceValidationModel } from "./model/skills_validation_model";
|
||||||
|
import { SkillsDBModel } from "./model/skills_database_model";
|
||||||
|
export class SkillsPresentation extends CrudController<SkillInstanceValidationModel, typeof SkillsDBModel> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
url: "skills",
|
||||||
|
validationModel: SkillInstanceValidationModel,
|
||||||
|
databaseModel: SkillsDBModel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
import { Schema, model } from "mongoose";
|
|
||||||
|
|
||||||
export interface ISkillsInstanceModel {}
|
|
||||||
|
|
||||||
export const SkillsInstanceSchema = new Schema({}).plugin(require("mongoose-autopopulate"));
|
|
||||||
|
|
||||||
export const skillsInstanceSchema = "skills_instances";
|
|
||||||
|
|
||||||
export const SkillsInstanceDBModel = model<ISkillsInstanceModel>(skillsInstanceSchema, SkillsInstanceSchema);
|
|
|
@ -1 +0,0 @@
|
||||||
export class SkillInstanceValidationModel {}
|
|
|
@ -1 +0,0 @@
|
||||||
export class SkillsInstancePresentation {}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { Schema, model } from "mongoose";
|
|
||||||
|
|
||||||
export interface ISkillsTemplateModel {}
|
|
||||||
|
|
||||||
export const SkillsTemplateSchema = new Schema({}).plugin(require("mongoose-autopopulate"));
|
|
||||||
|
|
||||||
export const skillsTemplateSchema = "skills_templates";
|
|
||||||
|
|
||||||
export const SkillsTemplateDBModel = model<ISkillsTemplateModel>(skillsTemplateSchema, SkillsTemplateSchema);
|
|
|
@ -1 +0,0 @@
|
||||||
export class SkillTemplateValidationModel {}
|
|
|
@ -1 +0,0 @@
|
||||||
export class SkillsTemplatePresentation {}
|
|
|
@ -31,6 +31,24 @@ export const ArrayExtensions = () => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if ([].replacePropIndex === undefined) {
|
||||||
|
Array.prototype.replacePropIndex = function (property, index) {
|
||||||
|
return this.map((element, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
element = Object.assign(element, property);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ([].someR === undefined) {
|
||||||
|
Array.prototype.someR = function (predicate) {
|
||||||
|
if (this.some(predicate)) {
|
||||||
|
return Result.error(undefined);
|
||||||
|
}
|
||||||
|
return Result.ok(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
if ([].lastElement === undefined) {
|
if ([].lastElement === undefined) {
|
||||||
Array.prototype.lastElement = function () {
|
Array.prototype.lastElement = function () {
|
||||||
const instanceCheck = this;
|
const instanceCheck = this;
|
||||||
|
|
|
@ -26,6 +26,8 @@ declare global {
|
||||||
maxLength(length: number): Array<T>;
|
maxLength(length: number): Array<T>;
|
||||||
add(element: T): Array<T>;
|
add(element: T): Array<T>;
|
||||||
indexOfR(element: T): Result<void, Array<T>>;
|
indexOfR(element: T): Result<void, Array<T>>;
|
||||||
|
replacePropIndex(property: Partial<T>, index: number): T[];
|
||||||
|
someR(predicate: (value: T) => boolean): Result<void, Array<T>>;
|
||||||
}
|
}
|
||||||
interface Date {
|
interface Date {
|
||||||
formatDate(): string;
|
formatDate(): string;
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { IsArray, IsOptional, IsString, ValidateNested } from "class-validator";
|
import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import { ISkillView } from "../../features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree";
|
import { ISkillView } from "../../features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helper/result";
|
||||||
|
import { ValidationModel } from "./validation_model";
|
||||||
|
import { TopicViewModel } from "../../features/topics/topic_view_model";
|
||||||
|
import makeAutoObservable from "mobx-store-inheritance";
|
||||||
import clone from "just-clone";
|
import clone from "just-clone";
|
||||||
|
|
||||||
export interface IDependency {
|
export interface IDependency {
|
||||||
|
@ -18,12 +21,9 @@ export interface ISkill {
|
||||||
sid?: string;
|
sid?: string;
|
||||||
SkillPackage: ISkillPackage;
|
SkillPackage: ISkillPackage;
|
||||||
Module: IModule;
|
Module: IModule;
|
||||||
Launch: ILaunch;
|
|
||||||
ROS2: IRos2;
|
|
||||||
BTAction: IBTAction[];
|
BTAction: IBTAction[];
|
||||||
Interface: IInterface;
|
// Interface: IInterface;
|
||||||
Settings: ISetting[];
|
// Settings: ISetting[];
|
||||||
xxx: IXxx;
|
|
||||||
}
|
}
|
||||||
export interface IWeightsDependency {
|
export interface IWeightsDependency {
|
||||||
weights_name: string;
|
weights_name: string;
|
||||||
|
@ -39,16 +39,55 @@ export interface IParam {
|
||||||
type: string;
|
type: string;
|
||||||
dependency: Object;
|
dependency: Object;
|
||||||
}
|
}
|
||||||
|
export class ParamViewModel implements IParam {
|
||||||
|
type: string;
|
||||||
|
dependency: Object;
|
||||||
|
constructor(type: string, dependency: Object) {
|
||||||
|
this.type = type;
|
||||||
|
this.dependency = dependency;
|
||||||
|
}
|
||||||
|
static empty = () => new ParamViewModel("", {});
|
||||||
|
}
|
||||||
export interface IBTAction {
|
export interface IBTAction {
|
||||||
name: string;
|
name: string;
|
||||||
format: string;
|
format: string;
|
||||||
// TODO: Нужно выпилить его отсюда
|
|
||||||
// sid?: string;
|
|
||||||
type: string;
|
type: string;
|
||||||
param: IParam[];
|
param: IParam[];
|
||||||
result: string[];
|
result: string[];
|
||||||
}
|
}
|
||||||
|
export enum BtAction {
|
||||||
|
ACTION = "ACTION",
|
||||||
|
CONDITION = "CONDITION",
|
||||||
|
}
|
||||||
|
export class BtActionViewModel extends ValidationModel implements IBTAction {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
format: string;
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
type: string;
|
||||||
|
param: IParam[];
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
result: string[];
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsEnum(BtAction)
|
||||||
|
typeAction: BtAction;
|
||||||
|
constructor(name: string, format: string, type: string, param: IParam[], result: string[], typeAction: BtAction) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.format = format;
|
||||||
|
this.type = type;
|
||||||
|
this.param = param;
|
||||||
|
this.result = result;
|
||||||
|
this.typeAction = typeAction;
|
||||||
|
}
|
||||||
|
static empty = () => new BtActionViewModel("", "", "", [], [], BtAction.ACTION);
|
||||||
|
public validParam = (type: string) => this.param.someR((param) => param.type === type);
|
||||||
|
}
|
||||||
export interface IInterface {
|
export interface IInterface {
|
||||||
Input: IPut[];
|
Input: IPut[];
|
||||||
Output: IPut[];
|
Output: IPut[];
|
||||||
|
@ -83,19 +122,22 @@ export interface ISkillPackage {
|
||||||
format: string;
|
format: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IXxx {
|
|
||||||
cameraLink: string;
|
|
||||||
topicImage: string;
|
|
||||||
topicCameraInfo: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SkillPackage implements ISkillPackage {
|
export class SkillPackage implements ISkillPackage {
|
||||||
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
version: string;
|
version: string;
|
||||||
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
format: string;
|
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 {
|
export class Module implements IModule {
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -142,12 +184,12 @@ export class Setting implements ISetting {
|
||||||
name: string;
|
name: string;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
}
|
}
|
||||||
export class Xxx implements IXxx {
|
|
||||||
cameraLink: string;
|
export class SkillModel extends ValidationModel implements ISkill {
|
||||||
topicImage: string;
|
constructor() {
|
||||||
topicCameraInfo: string;
|
super();
|
||||||
}
|
makeAutoObservable(this);
|
||||||
export class SkillModel implements ISkill {
|
}
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
sid?: string;
|
sid?: string;
|
||||||
|
@ -157,29 +199,12 @@ export class SkillModel implements ISkill {
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => Module)
|
@Type(() => Module)
|
||||||
Module: IModule;
|
Module: IModule;
|
||||||
@ValidateNested()
|
BTAction: BtActionViewModel[];
|
||||||
@Type(() => Launch)
|
topicsOut: TopicViewModel[] = [];
|
||||||
Launch: ILaunch;
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => Ros2)
|
|
||||||
ROS2: IRos2;
|
|
||||||
@ValidateNested()
|
|
||||||
@IsArray()
|
|
||||||
@Type(() => BTAction)
|
|
||||||
BTAction: IBTAction[];
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => Interface)
|
|
||||||
Interface: IInterface;
|
|
||||||
@ValidateNested()
|
|
||||||
@IsArray()
|
|
||||||
@Type(() => Setting)
|
|
||||||
Settings: ISetting[];
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => Xxx)
|
|
||||||
xxx: IXxx;
|
|
||||||
static empty() {
|
static empty() {
|
||||||
const skillModel = new SkillModel();
|
const skillModel = new SkillModel();
|
||||||
skillModel.BTAction = [];
|
skillModel.BTAction = [];
|
||||||
|
skillModel.SkillPackage = SkillPackage.empty();
|
||||||
return skillModel;
|
return skillModel;
|
||||||
}
|
}
|
||||||
public static isEmpty(skill: SkillModel): Result<void, SkillModel> {
|
public static isEmpty(skill: SkillModel): Result<void, SkillModel> {
|
||||||
|
@ -350,12 +375,6 @@ export class Skills {
|
||||||
skill.BTAction.forEach((action) => {
|
skill.BTAction.forEach((action) => {
|
||||||
action.param.forEach((param) => {
|
action.param.forEach((param) => {
|
||||||
if (param.type.isEqual(skillType)) {
|
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();
|
acc = Object.keys(param.dependency).isNotEmpty();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,8 @@ export class ValidationModel {
|
||||||
if (error.constraints) return Object.values(error.constraints).join(", ");
|
if (error.constraints) return Object.values(error.constraints).join(", ");
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
|
console.log(errors)
|
||||||
|
console.log(this)
|
||||||
return Result.error(message.join(","));
|
return Result.error(message.join(","));
|
||||||
} else {
|
} else {
|
||||||
return Result.ok(this as unknown as T);
|
return Result.ok(this as unknown as T);
|
||||||
|
|
|
@ -71,6 +71,36 @@ export class HttpRepository {
|
||||||
}
|
}
|
||||||
return Result.ok(response.text as T);
|
return Result.ok(response.text as T);
|
||||||
}
|
}
|
||||||
|
public async _arrayJsonToClassInstanceRequest<T>(
|
||||||
|
method: HttpMethod,
|
||||||
|
url: string,
|
||||||
|
instance: ClassConstructor<T>,
|
||||||
|
data?: any
|
||||||
|
): Promise<Result<HttpError, T[]>> {
|
||||||
|
try {
|
||||||
|
const reqInit = {
|
||||||
|
body: data,
|
||||||
|
method: method,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
};
|
||||||
|
if (data !== undefined) {
|
||||||
|
reqInit["body"] = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
const response = await fetch(this.server + url, reqInit);
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return Result.error(new HttpError(this.server + url, response.status));
|
||||||
|
}
|
||||||
|
const array: any[] = await response.json();
|
||||||
|
return Result.ok(
|
||||||
|
array.map((el) => {
|
||||||
|
return plainToInstance(instance, el) as T;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return Result.error(new HttpError(error, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
public async _jsonToClassInstanceRequest<T>(
|
public async _jsonToClassInstanceRequest<T>(
|
||||||
method: HttpMethod,
|
method: HttpMethod,
|
||||||
url: string,
|
url: string,
|
||||||
|
|
|
@ -9,10 +9,12 @@ import { DataSetScreen, DatasetsScreenPath } from "../../features/dataset/datase
|
||||||
import { AssemblesScreen, AssemblesScreenPath } from "../../features/assembles/assembles_screen";
|
import { AssemblesScreen, AssemblesScreenPath } from "../../features/assembles/assembles_screen";
|
||||||
import { SimulationScreen, SimulationScreenPath } from "../../features/simulations/simulations_screen";
|
import { SimulationScreen, SimulationScreenPath } from "../../features/simulations/simulations_screen";
|
||||||
import { EstimateScreen, EstimateScreenPath } from "../../features/estimate/estimate_screen";
|
import { EstimateScreen, EstimateScreenPath } from "../../features/estimate/estimate_screen";
|
||||||
import { CalculationScreenPath, CalculationScreen } from "../../features/calculation/presentation/calculation_screen";
|
import { CalculationInstanceScreenPath, CalculationInstanceScreen } from "../../features/calculation_instance/presentation/calculation_instance_screen";
|
||||||
import { DetailsScreenPath, DetailsScreen } from "../../features/details/details_screen";
|
import { DetailsScreenPath, DetailsScreen } from "../../features/details/details_screen";
|
||||||
import { DigitalTwinsScreen, DigitalTwinsScreenPath } from "../../features/digital_twins/digital_twins_screen";
|
import { DigitalTwinsScreen, DigitalTwinsScreenPath } from "../../features/digital_twins/digital_twins_screen";
|
||||||
import { TopicsScreen, TopicsScreenPath } from "../../features/topics/topics_screen";
|
import { TopicsScreen, TopicsScreenPath } from "../../features/topics/topics_screen";
|
||||||
|
import { SkillsScreen, SkillsScreenPath } from "../../features/skills/skills_screen";
|
||||||
|
import { CalculationsTemplateScreenPath } from "../../features/calculations_template/calculations_template_screen";
|
||||||
|
|
||||||
const idURL = ":id";
|
const idURL = ":id";
|
||||||
|
|
||||||
|
@ -58,8 +60,8 @@ export const router = createBrowserRouter([
|
||||||
element: <EstimateScreen />,
|
element: <EstimateScreen />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: CalculationScreenPath,
|
path: CalculationInstanceScreenPath,
|
||||||
element: <CalculationScreen />,
|
element: <CalculationInstanceScreen />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: DigitalTwinsScreenPath,
|
path: DigitalTwinsScreenPath,
|
||||||
|
@ -69,4 +71,12 @@ export const router = createBrowserRouter([
|
||||||
path: TopicsScreenPath,
|
path: TopicsScreenPath,
|
||||||
element: <TopicsScreen />,
|
element: <TopicsScreen />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: SkillsScreenPath,
|
||||||
|
element: <SkillsScreen />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: CalculationsTemplateScreenPath,
|
||||||
|
element: <CalculationInstanceScreen />,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { AssemblesScreenPath } from "../../../features/assembles/assembles_scree
|
||||||
import { SimulationScreenPath } from "../../../features/simulations/simulations_screen";
|
import { SimulationScreenPath } from "../../../features/simulations/simulations_screen";
|
||||||
import { EstimateScreenPath } from "../../../features/estimate/estimate_screen";
|
import { EstimateScreenPath } from "../../../features/estimate/estimate_screen";
|
||||||
import { BehaviorTreeBuilderPath } from "../../../features/behavior_tree_builder/presentation/behavior_tree_builder_screen";
|
import { BehaviorTreeBuilderPath } from "../../../features/behavior_tree_builder/presentation/behavior_tree_builder_screen";
|
||||||
import { CalculationScreenPath as SkillScreenPath } from "../../../features/calculation/presentation/calculation_screen";
|
import { CalculationInstanceScreenPath as SkillScreenPath } from "../../../features/calculation_instance/presentation/calculation_instance_screen";
|
||||||
import { UiBaseError } from "../../model/ui_base_error";
|
import { UiBaseError } from "../../model/ui_base_error";
|
||||||
import { DetailsScreenPath } from "../../../features/details/details_screen";
|
import { DetailsScreenPath } from "../../../features/details/details_screen";
|
||||||
export interface IBlockProps {
|
export interface IBlockProps {
|
||||||
|
|
|
@ -9,6 +9,9 @@ import { CoreButton } from "../../../core/ui/button/button";
|
||||||
import { CoreInput } from "../../../core/ui/input/input";
|
import { CoreInput } from "../../../core/ui/input/input";
|
||||||
import { DetailsScreenPath } from "../../details/details_screen";
|
import { DetailsScreenPath } from "../../details/details_screen";
|
||||||
import { DigitalTwinsScreenPath } from "../../digital_twins/digital_twins_screen";
|
import { DigitalTwinsScreenPath } from "../../digital_twins/digital_twins_screen";
|
||||||
|
import { TopicsScreenPath } from "../../topics/topics_screen";
|
||||||
|
import { SkillsScreenPath } from "../../skills/skills_screen";
|
||||||
|
import { CalculationsTemplateScreenPath } from "../../calculations_template/calculations_template_screen";
|
||||||
|
|
||||||
export const AllProjectScreenPath = "/";
|
export const AllProjectScreenPath = "/";
|
||||||
export const AllProjectScreen: React.FunctionComponent = observer(() => {
|
export const AllProjectScreen: React.FunctionComponent = observer(() => {
|
||||||
|
@ -30,6 +33,10 @@ export const AllProjectScreen: React.FunctionComponent = observer(() => {
|
||||||
children={
|
children={
|
||||||
<>
|
<>
|
||||||
<div onClick={() => navigate(DigitalTwinsScreenPath)}>Digital twins</div>
|
<div onClick={() => navigate(DigitalTwinsScreenPath)}>Digital twins</div>
|
||||||
|
<div onClick={() => navigate(TopicsScreenPath)}>Topics</div>
|
||||||
|
<div onClick={() => navigate(SkillsScreenPath)}>Skills</div>
|
||||||
|
<div onClick={() => navigate(CalculationsTemplateScreenPath)}>Calculation template</div>
|
||||||
|
|
||||||
{store.projectsModels?.map((el) => {
|
{store.projectsModels?.map((el) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -143,6 +143,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
|
||||||
});
|
});
|
||||||
(await this.behaviorTreeBuilderHttpRepository.getBtSkills()).fold(
|
(await this.behaviorTreeBuilderHttpRepository.getBtSkills()).fold(
|
||||||
(model) => {
|
(model) => {
|
||||||
|
|
||||||
this.skillTemplates = model;
|
this.skillTemplates = model;
|
||||||
this.skillTree.children = this.skillTree.children?.map((el) => {
|
this.skillTree.children = this.skillTree.children?.map((el) => {
|
||||||
if (el.name === "Действия") {
|
if (el.name === "Действия") {
|
||||||
|
|
|
@ -2,12 +2,13 @@ import { BehaviorTreeBuilderStore } from "../../behavior_tree_builder_store";
|
||||||
import { CameraDeviceForm } from "./camera_device_form/camera_device_form_form";
|
import { CameraDeviceForm } from "./camera_device_form/camera_device_form_form";
|
||||||
import { MoveToPose } from "./move_to_pose/move_to_pose_form";
|
import { MoveToPose } from "./move_to_pose/move_to_pose_form";
|
||||||
import { RobotDeviceForm } from "./robot_device_form/robot_device_form_form";
|
import { RobotDeviceForm } from "./robot_device_form/robot_device_form_form";
|
||||||
|
import { TopicDependencyViewModel } from "./topics_form/topic_dependency_view_model";
|
||||||
import { TopicsForm } from "./topics_form/topics_form";
|
import { TopicsForm } from "./topics_form/topics_form";
|
||||||
import { WeightsForm } from "./weights_form/weights_form";
|
import { WeightsForm } from "./weights_form/weights_form";
|
||||||
|
|
||||||
export interface IPropsForm<T> {
|
export interface IPropsForm<T> {
|
||||||
dependency: T;
|
dependency: T;
|
||||||
store: BehaviorTreeBuilderStore;
|
store?: BehaviorTreeBuilderStore;
|
||||||
onChange: (dependency: Object) => void;
|
onChange: (dependency: Object) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +23,18 @@ export enum Form {
|
||||||
topic = "topic",
|
topic = "topic",
|
||||||
moveToPose = "move_to_pose",
|
moveToPose = "move_to_pose",
|
||||||
}
|
}
|
||||||
|
export interface BtDependencyFormBuilder {
|
||||||
|
form: Form;
|
||||||
|
component?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const btDependencyFormBuilder = (onChange: (dependency: Object) => void) => [
|
||||||
|
{ form: Form.weights, component: null },
|
||||||
|
{
|
||||||
|
form: Form.topic,
|
||||||
|
component: <TopicsForm store={undefined} dependency={TopicDependencyViewModel.empty()} onChange={onChange} />,
|
||||||
|
},
|
||||||
|
];
|
||||||
export const forms = (
|
export const forms = (
|
||||||
props: any,
|
props: any,
|
||||||
onChange: (dependency: Object) => void,
|
onChange: (dependency: Object) => void,
|
||||||
|
|
|
@ -8,9 +8,11 @@ import { PointModel } from "../../../../../../core/model/point_model";
|
||||||
import { ObjectIsNotEmpty } from "../../../../../../core/extensions/object";
|
import { ObjectIsNotEmpty } from "../../../../../../core/extensions/object";
|
||||||
import { CoreButton } from "../../../../../../core/ui/button/button";
|
import { CoreButton } from "../../../../../../core/ui/button/button";
|
||||||
import { message } from "antd";
|
import { message } from "antd";
|
||||||
|
import { BehaviorTreeBuilderStore } from "../../../behavior_tree_builder_store";
|
||||||
|
|
||||||
export const MoveToPose = observer((props: IPropsForm<MoveToPoseRobotModel | undefined>) => {
|
export const MoveToPose = observer((props: IPropsForm<MoveToPoseRobotModel | undefined>) => {
|
||||||
const [store] = React.useState(() => new MoveToPoseStore(props.store));
|
const propStore = props.store as BehaviorTreeBuilderStore;
|
||||||
|
const [store] = React.useState(() => new MoveToPoseStore(propStore));
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (ObjectIsNotEmpty(props.dependency)) store.loadDependency(props.dependency);
|
if (ObjectIsNotEmpty(props.dependency)) store.loadDependency(props.dependency);
|
||||||
store.init();
|
store.init();
|
||||||
|
@ -18,16 +20,16 @@ export const MoveToPose = observer((props: IPropsForm<MoveToPoseRobotModel | und
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<CoreSelect
|
<CoreSelect
|
||||||
items={props.store.sceneAsset?.getAllRobotsTopics() ?? ["ERROR"]}
|
items={propStore.sceneAsset?.getAllRobotsTopics() ?? ["ERROR"]}
|
||||||
value={store.viewModel.robot_name ?? ""}
|
value={store.viewModel.robot_name ?? ""}
|
||||||
label={"Робот"}
|
label={"Робот"}
|
||||||
onChange={(text) => store.updateForm({ robot_name: text })}
|
onChange={(text) => store.updateForm({ robot_name: text })}
|
||||||
/>
|
/>
|
||||||
<CoreSelect
|
<CoreSelect
|
||||||
items={props.store.sceneAsset?.getAllPoints() ?? ["ERROR"]}
|
items={propStore.sceneAsset?.getAllPoints() ?? ["ERROR"]}
|
||||||
label={"Точка"}
|
label={"Точка"}
|
||||||
onChange={(text) =>
|
onChange={(text) =>
|
||||||
store.updateForm({ pose: props.store.sceneAsset?.getElementByName<PointModel>(text).toDependency() })
|
store.updateForm({ pose: propStore.sceneAsset?.getElementByName<PointModel>(text).toDependency() })
|
||||||
}
|
}
|
||||||
value={store.viewModel?.pose?.name ?? ""}
|
value={store.viewModel?.pose?.name ?? ""}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import makeAutoObservable from "mobx-store-inheritance";
|
import makeAutoObservable from "mobx-store-inheritance";
|
||||||
import { Result } from "../../../../../../core/helper/result";
|
import { ValidationModel } from "../../../../../../core/model/validation_model";
|
||||||
import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model";
|
import { IsNotEmpty, IsString } from "class-validator";
|
||||||
|
import { StoreTopicType } from "./topics_form_store";
|
||||||
|
|
||||||
export class TopicDependencyViewModel extends SidViewModel {
|
export class TopicDependencyViewModel extends ValidationModel {
|
||||||
axis: boolean;
|
@IsNotEmpty()
|
||||||
constructor(sid: string, axis: boolean) {
|
@IsString()
|
||||||
super(sid);
|
type: string;
|
||||||
this.axis = axis;
|
mode: StoreTopicType;
|
||||||
makeAutoObservable(this)
|
constructor(type: string, mode: StoreTopicType) {
|
||||||
}
|
super();
|
||||||
isValid = (): Result<string, void> => {
|
makeAutoObservable(this);
|
||||||
return Result.ok();
|
this.type = type;
|
||||||
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static empty() {
|
static empty() {
|
||||||
return new TopicDependencyViewModel('', false);
|
return new TopicDependencyViewModel("", StoreTopicType.specifiesDependencies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { TopicsFormStore } from "./topics_form_store";
|
import { StoreTopicType, TopicsFormStore } from "./topics_form_store";
|
||||||
import { IPropsForm } from "../forms";
|
import { IPropsForm } from "../forms";
|
||||||
import { TopicDependencyViewModel } from "./topic_dependency_view_model";
|
import { TopicDependencyViewModel } from "./topic_dependency_view_model";
|
||||||
import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text";
|
import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text";
|
||||||
import { CoreSwitch } from "../../../../../../core/ui/switch/switch";
|
import { match } from "ts-pattern";
|
||||||
import { CoreSelect } from "../../../../../../core/ui/select/select";
|
import { CoreInput } from "../../../../../../core/ui/input/input";
|
||||||
import { CoreButton } from "../../../../../../core/ui/button/button";
|
import { CoreButton } from "../../../../../../core/ui/button/button";
|
||||||
import { message } from "antd";
|
import { message } from "antd";
|
||||||
|
|
||||||
|
@ -14,40 +14,38 @@ export const TopicsForm = observer((props: IPropsForm<Partial<TopicDependencyVie
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
store.init();
|
store.init();
|
||||||
}, [store, props]);
|
store.loadClassInstance(TopicDependencyViewModel, props.dependency as TopicDependencyViewModel);
|
||||||
|
console.log(store.viewModel);
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ border: "1px solid", margin: 10, padding: 10 }}>
|
<div>
|
||||||
<CoreText text={"Topics"} type={CoreTextType.header} style={{ padding: 10 }} />
|
<CoreText text={"Topics"} type={CoreTextType.header} style={{ padding: 10 }} />
|
||||||
<CoreSelect
|
{match(store.viewModel.mode)
|
||||||
items={store.topics?.topics?.map((el) => el.name) ?? []}
|
.with(StoreTopicType.btExecute, () => (
|
||||||
value={props.dependency?.sid ?? ""}
|
<>
|
||||||
label={"Выберите топик"}
|
<CoreText text={"Выберите точку размещения"} type={CoreTextType.header} />
|
||||||
onChange={(value: string) => store.updateForm({ sid: value })}
|
</>
|
||||||
/>
|
))
|
||||||
|
.with(StoreTopicType.specifiesDependencies, () => (
|
||||||
<div>
|
<>
|
||||||
is input:{" "}
|
<CoreInput label={"Тип топика"} onChange={(text) => store.updateForm({ type: text })} />
|
||||||
<CoreSwitch
|
<div style={{ height: 10 }} />
|
||||||
isSelected={store.viewModel.axis}
|
<CoreButton
|
||||||
id={""}
|
text="OK"
|
||||||
onChange={(status: boolean, id: string) => {
|
style={{ width: 100 }}
|
||||||
store.updateForm({ axis: !status });
|
onClick={async () => {
|
||||||
}}
|
(await store.viewModel.valid<TopicDependencyViewModel>()).fold(
|
||||||
/>
|
(s) => props.onChange(s),
|
||||||
</div>
|
(e) => message.error(e)
|
||||||
<CoreButton
|
);
|
||||||
style={{ margin: 10, width: 100 }}
|
}}
|
||||||
text="OK"
|
/>
|
||||||
onClick={async () => {
|
</>
|
||||||
(await store.viewModel.valid<TopicDependencyViewModel>()).fold(
|
))
|
||||||
(s) => {
|
.otherwise(() => (
|
||||||
props.onChange(s);
|
<></>
|
||||||
},
|
))}
|
||||||
(e) => message.error(e)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,10 @@ import { TopicDependencyViewModel } from "./topic_dependency_view_model";
|
||||||
import { FormState, CoreError } from "../../../../../../core/store/base_store";
|
import { FormState, CoreError } from "../../../../../../core/store/base_store";
|
||||||
import { TopicsFormHttpRepository } from "./topics_form_http_repository";
|
import { TopicsFormHttpRepository } from "./topics_form_http_repository";
|
||||||
import { Topics } from "../../../../../../core/model/topics";
|
import { Topics } from "../../../../../../core/model/topics";
|
||||||
|
export enum StoreTopicType {
|
||||||
|
specifiesDependencies = "specifiesDependencies",
|
||||||
|
btExecute = "btExecute",
|
||||||
|
}
|
||||||
export class TopicsFormStore extends FormState<TopicDependencyViewModel, CoreError> {
|
export class TopicsFormStore extends FormState<TopicDependencyViewModel, CoreError> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -15,13 +18,6 @@ export class TopicsFormStore extends FormState<TopicDependencyViewModel, CoreErr
|
||||||
cameraDeviceHttpRepository: TopicsFormHttpRepository = new TopicsFormHttpRepository();
|
cameraDeviceHttpRepository: TopicsFormHttpRepository = new TopicsFormHttpRepository();
|
||||||
errorHandingStrategy = (error: CoreError) => {};
|
errorHandingStrategy = (error: CoreError) => {};
|
||||||
init = async (navigate?: NavigateFunction | undefined) => {
|
init = async (navigate?: NavigateFunction | undefined) => {
|
||||||
try {
|
|
||||||
throw new Error('213')
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// await this.mapOk('topics', this.cameraDeviceHttpRepository.getAllTopics())
|
// await this.mapOk('topics', this.cameraDeviceHttpRepository.getAllTopics())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import makeAutoObservable from "mobx-store-inheritance";
|
import makeAutoObservable from "mobx-store-inheritance";
|
||||||
import { FormState, CoreError } from "../../../../../../core/store/base_store";
|
import { FormState, CoreError } from "../../../../../../core/store/base_store";
|
||||||
import { DataSetHttpRepository } from "../../../../../dataset/dataset_http_repository";
|
import { DataSetHttpRepository } from "../../../../../dataset/dataset_http_repository";
|
||||||
import { ISkils, CalculationHttpRepository } from "../../../../../calculation/data/calculation_http_repository";
|
import { ISkils, CalculationHttpRepository } from "../../../../../calculation_instance/data/calculation_http_repository";
|
||||||
import { WeightsViewModel } from "./weights_view_model";
|
import { WeightsViewModel } from "./weights_view_model";
|
||||||
import { Parts } from "../../../../../details/details_http_repository";
|
import { Parts } from "../../../../../details/details_http_repository";
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Drawer, Modal } from "antd";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { MainPage } from "../../../core/ui/pages/main_page";
|
import { MainPage } from "../../../core/ui/pages/main_page";
|
||||||
import { CoreText, CoreTextType } from "../../../core/ui/text/text";
|
import { CoreText, CoreTextType } from "../../../core/ui/text/text";
|
||||||
import { DrawersSkill, CalculationStore, StoreTypes } from "./calculation_store";
|
import { DrawersSkill, CalculationInstanceStore, StoreTypes } from "./calculation_instance_store";
|
||||||
import { CoreInput } from "../../../core/ui/input/input";
|
import { CoreInput } from "../../../core/ui/input/input";
|
||||||
import { CoreButton } from "../../../core/ui/button/button";
|
import { CoreButton } from "../../../core/ui/button/button";
|
||||||
import { CoreSelect } from "../../../core/ui/select/select";
|
import { CoreSelect } from "../../../core/ui/select/select";
|
||||||
|
@ -22,10 +22,10 @@ interface IItem {
|
||||||
|
|
||||||
const skills: IItem[] = [{ name: "ML", isActive: true }];
|
const skills: IItem[] = [{ name: "ML", isActive: true }];
|
||||||
|
|
||||||
export const CalculationScreenPath = "/calculation";
|
export const CalculationInstanceScreenPath = "/calculation";
|
||||||
|
|
||||||
export const CalculationScreen = observer(() => {
|
export const CalculationInstanceScreen = observer(() => {
|
||||||
const [store] = React.useState(() => new CalculationStore());
|
const [store] = React.useState(() => new CalculationInstanceStore());
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
store.init();
|
store.init();
|
|
@ -20,11 +20,7 @@ export enum StoreTypes {
|
||||||
empty = "empty",
|
empty = "empty",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CalculationStore extends UiDrawerFormState<CalculationModel, HttpError> {
|
export class CalculationInstanceStore extends UiDrawerFormState<CalculationModel, HttpError> {
|
||||||
deleteInstance = async (id: string) => {
|
|
||||||
await this.calculationHttpRepository.deleteInstance(id);
|
|
||||||
await this.init();
|
|
||||||
};
|
|
||||||
calculationHttpRepository: CalculationHttpRepository = new CalculationHttpRepository();
|
calculationHttpRepository: CalculationHttpRepository = new CalculationHttpRepository();
|
||||||
calculationSocketRepository: CalculationSocketRepository = new CalculationSocketRepository();
|
calculationSocketRepository: CalculationSocketRepository = new CalculationSocketRepository();
|
||||||
activeProjectId?: UUID;
|
activeProjectId?: UUID;
|
||||||
|
@ -49,6 +45,10 @@ export class CalculationStore extends UiDrawerFormState<CalculationModel, HttpEr
|
||||||
this.calculationSocketRepository.on(this.socketUpdate);
|
this.calculationSocketRepository.on(this.socketUpdate);
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
deleteInstance = async (id: string) => {
|
||||||
|
await this.calculationHttpRepository.deleteInstance(id);
|
||||||
|
await this.init();
|
||||||
|
};
|
||||||
socketUpdate = (data: ProcessUpdate) => {
|
socketUpdate = (data: ProcessUpdate) => {
|
||||||
this.calculationInstances?.map((el) => {
|
this.calculationInstances?.map((el) => {
|
||||||
if (el?._id && el._id.isEqual(data.id)) {
|
if (el?._id && el._id.isEqual(data.id)) {
|
|
@ -1,6 +1,5 @@
|
||||||
import { match } from "ts-pattern";
|
import { match } from "ts-pattern";
|
||||||
import { PoseEstimateCard } from "./pose_estimate_card/model_card";
|
import { PoseEstimateCard } from "./pose_estimate_card/model_card";
|
||||||
import { FormBuilderValidationModel } from "../../../../dataset/dataset_model";
|
|
||||||
import { Dropdown, MenuProps, message } from "antd";
|
import { Dropdown, MenuProps, message } from "antd";
|
||||||
import { CoreText, CoreTextType } from "../../../../../core/ui/text/text";
|
import { CoreText, CoreTextType } from "../../../../../core/ui/text/text";
|
||||||
import { IMenuItem } from "../../../../dataset/card_dataset";
|
import { IMenuItem } from "../../../../dataset/card_dataset";
|
|
@ -1,11 +1,4 @@
|
||||||
import { CoreButton } from "../../../../../../core/ui/button/button";
|
|
||||||
import { Icon } from "../../../../../../core/ui/icons/icons";
|
|
||||||
import { Dropdown } from "antd";
|
|
||||||
import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text";
|
|
||||||
import { ProcessStatus } from "../../../../../dataset/dataset_model";
|
|
||||||
import { IMenuItem } from "../../../../../dataset/card_dataset";
|
|
||||||
import type { MenuProps } from "antd";
|
|
||||||
import { match } from "ts-pattern";
|
|
||||||
|
|
||||||
export interface IModelCardProps {
|
export interface IModelCardProps {
|
||||||
dependency?: Object;
|
dependency?: Object;
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { CoreHttpRepository } from "../../core/repository/core_http_repository";
|
||||||
|
|
||||||
|
export class CalculationsTemplateHttpRepository extends CoreHttpRepository {}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { CalculationsTemplateStore } from "./calculations_template_store";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const CalculationsTemplateScreenPath = "/calculations/template";
|
||||||
|
|
||||||
|
export const CalculationsTemplateScreen = observer(() => {
|
||||||
|
const [store] = React.useState(() => new CalculationsTemplateStore());
|
||||||
|
React.useEffect(() => {
|
||||||
|
store.init();
|
||||||
|
}, []);
|
||||||
|
return <></>;
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
import makeAutoObservable from "mobx-store-inheritance";
|
||||||
|
import { UiDrawerFormState } from "../../core/store/base_store";
|
||||||
|
import { HttpError } from "../../core/repository/core_http_repository";
|
||||||
|
import { CalculationsTemplateViewModel } from "./calculations_template_view_model";
|
||||||
|
import { NavigateFunction } from "react-router-dom";
|
||||||
|
export enum CalculationTemplateDrawer {
|
||||||
|
newSkill = "Новый навык",
|
||||||
|
}
|
||||||
|
export class CalculationsTemplateStore extends UiDrawerFormState<CalculationsTemplateViewModel, HttpError> {
|
||||||
|
viewModel: CalculationsTemplateViewModel = CalculationsTemplateViewModel.empty();
|
||||||
|
constructor() {
|
||||||
|
super(CalculationTemplateDrawer);
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
init = async (navigate?: NavigateFunction | undefined): Promise<any> => {};
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { ValidationModel } from "../../core/model/validation_model";
|
||||||
|
|
||||||
|
export class CalculationsTemplateViewModel extends ValidationModel {
|
||||||
|
static empty() {
|
||||||
|
return new CalculationsTemplateViewModel();
|
||||||
|
}
|
||||||
|
}
|
8
ui/src/features/skills/skills_http_repository.ts
Normal file
8
ui/src/features/skills/skills_http_repository.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { SkillModel } from "../../core/model/skill_model";
|
||||||
|
import { HttpMethod, HttpRepository } from "../../core/repository/core_http_repository";
|
||||||
|
|
||||||
|
export class SkillsHttpRepository extends HttpRepository {
|
||||||
|
featureApi = "/skills";
|
||||||
|
createNewSkill = (model: SkillModel) => this._jsonRequest(HttpMethod.POST, this.featureApi, model);
|
||||||
|
getAllSkills = () => this._arrayJsonToClassInstanceRequest(HttpMethod.GET, this.featureApi, SkillModel);
|
||||||
|
}
|
184
ui/src/features/skills/skills_screen.tsx
Normal file
184
ui/src/features/skills/skills_screen.tsx
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
import React from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { DrawersSkills, SkillsStore } from "./skills_store";
|
||||||
|
import { Drawer, Modal } from "antd";
|
||||||
|
import { CoreInput } from "../../core/ui/input/input";
|
||||||
|
import { CoreText, CoreTextType } from "../../core/ui/text/text";
|
||||||
|
import { TopicViewModel } from "../topics/topic_view_model";
|
||||||
|
import { BtAction, BtActionViewModel } from "../../core/model/skill_model";
|
||||||
|
import { btDependencyFormBuilder } from "../behavior_tree_builder/presentation/ui/forms/forms";
|
||||||
|
import { CoreButton } from "../../core/ui/button/button";
|
||||||
|
import { CoreSelect } from "../../core/ui/select/select";
|
||||||
|
|
||||||
|
export const SkillsScreenPath = "/skills";
|
||||||
|
|
||||||
|
export const SkillsScreen = observer(() => {
|
||||||
|
const [store] = React.useState(() => new SkillsStore());
|
||||||
|
React.useEffect(() => {
|
||||||
|
store.init();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<CoreButton
|
||||||
|
style={{ width: 100 }}
|
||||||
|
text="new skill"
|
||||||
|
onClick={() => store.editDrawer(DrawersSkills.newSkill, true)}
|
||||||
|
/>
|
||||||
|
{store.skills?.map((el) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: "coral",
|
||||||
|
width: "max-content",
|
||||||
|
margin: 10,
|
||||||
|
padding: 10,
|
||||||
|
borderRadius: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CoreText text={`skill server name:${el.SkillPackage.name}`} type={CoreTextType.header} />
|
||||||
|
<div>
|
||||||
|
<CoreText text={"actions"} type={CoreTextType.header} />
|
||||||
|
|
||||||
|
{el.BTAction.map((el) => (
|
||||||
|
<div>
|
||||||
|
<CoreText text={el.name} type={CoreTextType.medium} />
|
||||||
|
{el.param.map((element) => (
|
||||||
|
<div>
|
||||||
|
<div>{element.type}</div>
|
||||||
|
<div>{JSON.stringify(element.dependency)}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
width={(window.innerWidth / 100) * 50}
|
||||||
|
title={store.titleDrawer}
|
||||||
|
destroyOnClose={true}
|
||||||
|
onClose={() => store.editDrawer(DrawersSkills.newSkill, false)}
|
||||||
|
open={store.drawers.find((el) => el.name === DrawersSkills.newSkill)?.status}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}>
|
||||||
|
<div>
|
||||||
|
<CoreInput
|
||||||
|
label={"name"}
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ SkillPackage: Object.assign(store.viewModel.SkillPackage, { name: text }) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreInput
|
||||||
|
label={"format"}
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ SkillPackage: Object.assign(store.viewModel.SkillPackage, { format: text }) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreInput
|
||||||
|
label="version"
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ SkillPackage: Object.assign(store.viewModel.SkillPackage, { version: text }) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreText
|
||||||
|
text={`Topics ${store.viewModel.topicsOut.length}`}
|
||||||
|
type={CoreTextType.large}
|
||||||
|
onClick={() => store.updateForm({ topicsOut: store.viewModel.topicsOut.add(TopicViewModel.empty()) })}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
{store.viewModel.topicsOut.map((el, index) => (
|
||||||
|
<div key={index} style={{ marginTop: 10 }}>
|
||||||
|
<CoreInput
|
||||||
|
value={el.name}
|
||||||
|
label={"name"}
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ topicsOut: store.viewModel.topicsOut.replacePropIndex({ name: text }, index) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreInput
|
||||||
|
value={el.type}
|
||||||
|
label={"type"}
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ topicsOut: store.viewModel.topicsOut.replacePropIndex({ type: text }, index) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<CoreText
|
||||||
|
text={`BTAction ${store.viewModel.BTAction.length}`}
|
||||||
|
type={CoreTextType.large}
|
||||||
|
onClick={() => store.updateForm({ BTAction: store.viewModel.BTAction.add(BtActionViewModel.empty()) })}
|
||||||
|
/>
|
||||||
|
{store.viewModel.BTAction.map((el, index) => (
|
||||||
|
<div key={index} style={{ marginTop: 10 }}>
|
||||||
|
<CoreInput
|
||||||
|
value={el.name}
|
||||||
|
label={"name"}
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ BTAction: store.viewModel.BTAction.replacePropIndex({ name: text }, index) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreInput
|
||||||
|
value={el.type}
|
||||||
|
label={"type"}
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ BTAction: store.viewModel.BTAction.replacePropIndex({ type: text }, index) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreInput
|
||||||
|
value={el.format}
|
||||||
|
label={"format"}
|
||||||
|
onChange={(text) =>
|
||||||
|
store.updateForm({ BTAction: store.viewModel.BTAction.replacePropIndex({ format: text }, index) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreSelect
|
||||||
|
items={Object.keys(BtAction)}
|
||||||
|
value={el.typeAction}
|
||||||
|
label={"type action"}
|
||||||
|
onChange={(value: string) =>
|
||||||
|
store.updateForm({
|
||||||
|
BTAction: store.viewModel.BTAction.replacePropIndex({ typeAction: value as BtAction }, index),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CoreText
|
||||||
|
text={`Param ${el.param.length}`}
|
||||||
|
type={CoreTextType.large}
|
||||||
|
onClick={() => store.addNewParam(index)}
|
||||||
|
/>
|
||||||
|
{el.param.map((param) => (
|
||||||
|
<div style={{ border: "1px solid", padding: 10, margin: 1 }} onClick={() => store.showModal()}>
|
||||||
|
<div>{param.type}</div>
|
||||||
|
<div> {JSON.stringify(param.dependency)}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<CoreButton text="Save" style={{ width: 100 }} onClick={() => store.saveNewSkill()} />
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
<Modal
|
||||||
|
destroyOnClose={true}
|
||||||
|
afterClose={() => (store.selectParam = undefined)}
|
||||||
|
open={store.isModalOpen}
|
||||||
|
footer={null}
|
||||||
|
closable={false}
|
||||||
|
closeIcon={null}
|
||||||
|
onCancel={store.handleCancel}
|
||||||
|
>
|
||||||
|
<CoreText text={"Выберите параметр"} type={CoreTextType.large} />
|
||||||
|
{store.selectParam !== undefined
|
||||||
|
? store.selectParam.component
|
||||||
|
: btDependencyFormBuilder((dependency) => store.onChangeBtDependency(dependency)).map((el) => (
|
||||||
|
<div onClick={() => store.clickParam(el)}>{el.form}</div>
|
||||||
|
))}
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
78
ui/src/features/skills/skills_store.ts
Normal file
78
ui/src/features/skills/skills_store.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import makeAutoObservable from "mobx-store-inheritance";
|
||||||
|
import { UiDrawerFormState } from "../../core/store/base_store";
|
||||||
|
import { NavigateFunction } from "react-router-dom";
|
||||||
|
import { HttpError } from "../../core/repository/core_http_repository";
|
||||||
|
import { ParamViewModel, SkillModel } from "../../core/model/skill_model";
|
||||||
|
import { Form } from "../behavior_tree_builder/presentation/ui/forms/forms";
|
||||||
|
import { message } from "antd";
|
||||||
|
import { SkillsHttpRepository } from "./skills_http_repository";
|
||||||
|
export enum DrawersSkills {
|
||||||
|
newSkill = "Новый навык",
|
||||||
|
}
|
||||||
|
export class SkillsStore extends UiDrawerFormState<SkillModel, HttpError> {
|
||||||
|
isModalOpen: boolean = false;
|
||||||
|
activeIndex?: number;
|
||||||
|
viewModel: SkillModel = SkillModel.empty();
|
||||||
|
selectParam?: {
|
||||||
|
form: Form;
|
||||||
|
component?: React.ReactNode;
|
||||||
|
};
|
||||||
|
skills: SkillModel[];
|
||||||
|
skillsHttpRepository: SkillsHttpRepository = new SkillsHttpRepository();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(DrawersSkills);
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
init = async (navigate?: NavigateFunction | undefined) => {
|
||||||
|
this.mapOk("skills", this.skillsHttpRepository.getAllSkills());
|
||||||
|
};
|
||||||
|
showModal = () => {
|
||||||
|
this.isModalOpen = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOk = () => {
|
||||||
|
this.isModalOpen = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = () => {
|
||||||
|
this.isModalOpen = false;
|
||||||
|
};
|
||||||
|
addNewParam = (index: number) => {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.showModal();
|
||||||
|
};
|
||||||
|
onChangeBtDependency = (dependency: Object) =>
|
||||||
|
this.viewModel.BTAction.at(this.activeIndex ?? 0)
|
||||||
|
?.validParam(this.selectParam?.form ?? "")
|
||||||
|
.fold(
|
||||||
|
() => {
|
||||||
|
this.updateForm({
|
||||||
|
BTAction: this.viewModel.BTAction.replacePropIndex(
|
||||||
|
{
|
||||||
|
param: this.viewModel.BTAction.at(this.activeIndex ?? 0)?.param.add(
|
||||||
|
new ParamViewModel(this.selectParam?.form ?? "", dependency)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
this.activeIndex ?? 0
|
||||||
|
),
|
||||||
|
});
|
||||||
|
this.handleCancel();
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
message.error(`${this.selectParam?.form} is filled`);
|
||||||
|
this.handleCancel();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
clickParam(el: { form: Form; component: null } | { form: Form; component: React.JSX.Element }): void {
|
||||||
|
this.selectParam = el;
|
||||||
|
if (el.component === null) this.onChangeBtDependency({});
|
||||||
|
}
|
||||||
|
saveNewSkill = async () => {
|
||||||
|
(await this.viewModel.valid<SkillModel>()).fold(
|
||||||
|
async (model) => this.skillsHttpRepository.createNewSkill(model),
|
||||||
|
async (e) => message.error(e)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { socketListerStore } from "./socket_lister_store";
|
import { socketListenerStore } from "./socket_listener_store";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { CoreText, CoreTextType } from "../../core/ui/text/text";
|
import { CoreText, CoreTextType } from "../../core/ui/text/text";
|
||||||
|
|
||||||
export interface ISocketListerProps {
|
export interface ISocketListenerProps {
|
||||||
children?: JSX.Element;
|
children?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SocketLister = observer((props: ISocketListerProps) => {
|
export const SocketListener = observer((props: ISocketListenerProps) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
socketListerStore.init();
|
socketListenerStore.init();
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
|
@ -1,7 +1,7 @@
|
||||||
import { makeAutoObservable } from "mobx";
|
import { makeAutoObservable } from "mobx";
|
||||||
import { SocketRepository, socketRepository } from "../../core/repository/core_socket_repository";
|
import { SocketRepository, socketRepository } from "../../core/repository/core_socket_repository";
|
||||||
|
|
||||||
class SocketListerStore {
|
class SocketListenerStore {
|
||||||
socketRepository: SocketRepository;
|
socketRepository: SocketRepository;
|
||||||
socketHasDisconnect = false;
|
socketHasDisconnect = false;
|
||||||
|
|
||||||
|
@ -28,4 +28,4 @@ class SocketListerStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketListerStore = new SocketListerStore(socketRepository);
|
export const socketListenerStore = new SocketListenerStore(socketRepository);
|
|
@ -1,8 +1,20 @@
|
||||||
|
import { IsNotEmpty, IsString } from "class-validator";
|
||||||
import { ValidationModel } from "../../core/model/validation_model";
|
import { ValidationModel } from "../../core/model/validation_model";
|
||||||
|
|
||||||
export class TopicViewModel extends ValidationModel {
|
export class TopicViewModel extends ValidationModel {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
type: string;
|
||||||
|
constructor(name: string, type: string) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
static empty() {
|
static empty() {
|
||||||
return new TopicViewModel();
|
return new TopicViewModel("", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface ITopicModel {
|
export interface ITopicModel {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { HttpMethod, HttpRepository } from "../../core/repository/core_http_repository";
|
import { HttpMethod, HttpRepository } from "../../core/repository/core_http_repository";
|
||||||
|
|
||||||
export class TopicsHttpRepository extends HttpRepository {
|
export class TopicsHttpRepository extends HttpRepository {
|
||||||
getAllTopics = () => this._jsonRequest(HttpMethod.GET, "/topics");
|
featureApi = "/topics";
|
||||||
|
getAllTopics = () => this._jsonRequest(HttpMethod.GET, this.featureApi);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const TopicsScreen = observer(() => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{store.topics?.map((el) => (
|
{store.topics?.map((el) => (
|
||||||
<div>
|
<div style={{ margin: 10, border: "1px solid red" }}>
|
||||||
<div>{el.name}</div>
|
<div>{el.name}</div>
|
||||||
<div>{el.type}</div>
|
<div>{el.type}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import "reflect-metadata";
|
||||||
import "antd/dist/antd.min.css";
|
import "antd/dist/antd.min.css";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { extensions } from "./core/extensions/extensions";
|
import { extensions } from "./core/extensions/extensions";
|
||||||
import { SocketLister } from "./features/socket_lister/socket_lister";
|
import { SocketListener } from "./features/socket_listener/socket_listener";
|
||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom";
|
||||||
import { router } from "./core/routers/routers";
|
import { router } from "./core/routers/routers";
|
||||||
import { configure } from "mobx";
|
import { configure } from "mobx";
|
||||||
|
@ -17,8 +17,9 @@ const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<>
|
<>
|
||||||
<SocketLister>
|
<SocketListener>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</SocketLister>
|
</SocketListener>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue