Реализовать загрузку 3D-моделей в Scene manager и выгрузку файлов для запуска симуляции

This commit is contained in:
IDONTSUDO 2024-02-04 13:12:53 +00:00 committed by Igor Brylyov
parent 11ca9cdb5e
commit 3fefd60b72
109 changed files with 2726 additions and 1190 deletions

View file

@ -15,6 +15,6 @@
"*ui": false, "*ui": false,
"*ui.*": false "*ui.*": false
}, },
"cSpell.words": ["antd", "fileupload", "metadatas", "undici", "uuidv"], "cSpell.words": ["antd", "Collada", "Contolls", "fileupload", "metadatas", "undici", "uuidv"],
"editor.rulers": [100] "editor.rulers": [100]
} }

1
server/.gitignore vendored
View file

@ -9,3 +9,4 @@ package-lock.json
build/ build/
model_create.ts model_create.ts
public public
p.ts

3
server/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cSpell.words": ["fileupload", "Metadatas", "readir"]
}

View file

@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"pretest": "tsc", "test:dev": "NODE_ENV=test_dev tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
"test:unit": "NODE_ENV=unit tsc-watch --onSuccess 'ts-node ./build/test/test.js'", "test:unit": "NODE_ENV=unit tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
"test:e2e": "NODE_ENV=e2e tsc-watch --onSuccess 'ts-node ./build/test/test.js'", "test:e2e": "NODE_ENV=e2e tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
"dev": "NODE_ENV=dev tsc-watch --onSuccess 'ts-node ./build/src/main.js'" "dev": "NODE_ENV=dev tsc-watch --onSuccess 'ts-node ./build/src/main.js'"

View file

@ -1,11 +1,11 @@
import express from "express"; import express from "express";
import { Routes } from "../interfaces/router";
import cors from "cors"; import cors from "cors";
import fileUpload from "express-fileupload";
import { Routes } from "../interfaces/router";
import { Server } from "socket.io"; import { Server } from "socket.io";
import { createServer } from "http"; import { createServer } from "http";
import { SocketSubscriber } from "./socket_controller"; import { SocketSubscriber } from "./socket_controller";
import { dirname } from "path"; import { dirname } from "path";
import fileUpload from "express-fileupload";
import { SetLastActivePipelineToRealTimeServiceScenario } from "../scenarios/set_active_pipeline_to_realtime_service_scenario"; import { SetLastActivePipelineToRealTimeServiceScenario } from "../scenarios/set_active_pipeline_to_realtime_service_scenario";
import { CheckAndCreateStaticFilesFolderUseCase } from "../usecases/check_and_create_static_files_folder_usecase"; import { CheckAndCreateStaticFilesFolderUseCase } from "../usecases/check_and_create_static_files_folder_usecase";
import { DataBaseConnectUseCase } from "../usecases/database_connect_usecase"; import { DataBaseConnectUseCase } from "../usecases/database_connect_usecase";
@ -13,7 +13,7 @@ import { TypedEvent } from "../helpers/typed_event";
export enum ServerStatus { export enum ServerStatus {
init = "init", init = "init",
finished = "finshed", finished = "finished",
error = "error", error = "error",
} }
export enum Environment { export enum Environment {
@ -85,7 +85,7 @@ export class App extends TypedEvent<ServerStatus> {
this.app.use(cors()); this.app.use(cors());
this.app.use(express.json()); this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true })); this.app.use(express.urlencoded({ extended: true }));
this.app.use(express.static("public")); this.app.use(express.static(App.staticFilesStoreDir()));
this.app.use( this.app.use(
fileUpload({ fileUpload({
@ -118,6 +118,7 @@ export class App extends TypedEvent<ServerStatus> {
static staticFilesStoreDir = () => { static staticFilesStoreDir = () => {
const dir = dirname(__filename); const dir = dirname(__filename);
const rootDir = dir.slice(0, dir.length - 20); const rootDir = dir.slice(0, dir.length - 20);
return rootDir + "public/"; return rootDir + "public/";
}; };
} }

View file

@ -2,20 +2,20 @@ import { validationModelMiddleware } from "../middlewares/validation_model";
import { Result } from "../helpers/result"; import { Result } from "../helpers/result";
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { IRouteModel, Routes } from "../interfaces/router"; import { IRouteModel, Routes } from "../interfaces/router";
import { CoreValidation } from "../validations/core_validation";
export type HttpMethodType = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "PATCH" | "HEAD"; export type HttpMethodType = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "PATCH" | "HEAD";
export type ResponseBase = Promise<Result<any, any>>; export type ResponseBase = Promise<Result<any, any>>;
export abstract class CallbackStrategyWithEmpty { export abstract class CallbackStrategyWithEmpty {
abstract call(): ResponseBase; abstract call(): ResponseBase;
} }
export abstract class CallbackStrategyWithValidationModel<V> { export abstract class CallbackStrategyWithValidationModel<V> {
abstract validationModel: V; abstract validationModel: V;
abstract call(a: V): ResponseBase; abstract call(model: V): ResponseBase;
} }
export abstract class CallbackStrategyWithIdQuery { export abstract class CallbackStrategyWithIdQuery {
abstract idValidationExpression: RegExp | null; abstract idValidationExpression: CoreValidation;
abstract call(id: string): ResponseBase; abstract call(id: string): ResponseBase;
} }
export abstract class CallBackStrategyWithQueryPage { export abstract class CallBackStrategyWithQueryPage {
@ -25,7 +25,8 @@ export abstract class CallBackStrategyWithQueryPage {
export abstract class CallbackStrategyWithFileUpload { export abstract class CallbackStrategyWithFileUpload {
abstract checkingFileExpression: RegExp; abstract checkingFileExpression: RegExp;
abstract call(file: File): ResponseBase; abstract idValidationExpression: CoreValidation;
abstract call(file: File, id: string, description: string): ResponseBase;
} }
interface ISubSetFeatureRouter<T> { interface ISubSetFeatureRouter<T> {
@ -84,7 +85,24 @@ export class CoreHttpController<V> implements ICoreHttpController {
throw Error("needs to be implimed"); throw Error("needs to be implimed");
} }
if (el.fn instanceof CallbackStrategyWithIdQuery) { if (el.fn instanceof CallbackStrategyWithIdQuery) {
throw Error("needs to be implimed"); if (req.query.id === undefined) {
res.status(400).json("request query id is null, need query id ?id={id:String}");
return;
}
if (el.fn.idValidationExpression !== undefined) {
if (!el.fn.idValidationExpression.regExp.test(req.query.id)) {
res
.status(400)
.json(
`request query id must fall under the pattern: ${el.fn.idValidationExpression.regExp} message: ${el.fn.idValidationExpression.message} `
);
return;
} else {
await this.responseHelper(res, el.fn.call(req.query.id));
}
} else {
await this.responseHelper(res, el.fn.call(req["files"]["file"]));
}
} }
if (el.fn instanceof CallBackStrategyWithQueryPage) { if (el.fn instanceof CallBackStrategyWithQueryPage) {
throw Error("needs to be implimed"); throw Error("needs to be implimed");
@ -99,17 +117,32 @@ export class CoreHttpController<V> implements ICoreHttpController {
res.status(400).json("need files to form-data request"); res.status(400).json("need files to form-data request");
return; return;
} }
if (req["files"]["file"] === undefined) { if (req["files"]["file"] === undefined) {
res.status(400).json("need file to form data request"); res.status(400).json("need file to form data request");
return; return;
} }
if (req.query.description === undefined) {
res
.status(400)
.json("request query description is null, need query description &description={description:String}");
return;
}
if (req.query.id === undefined) {
res.status(400).json("request query id is null, need query id ?id={id:String}");
return;
}
if (!el.fn.idValidationExpression.regExp.test(req.query.id)) {
res.status(400).json(el.fn.idValidationExpression.message);
return;
}
if (el.fn instanceof CallbackStrategyWithFileUpload) { if (el.fn instanceof CallbackStrategyWithFileUpload) {
if (!el.fn.checkingFileExpression.test(req["files"]["file"]["name"])) { if (!el.fn.checkingFileExpression.test(req["files"]["file"]["name"])) {
res.status(400).json("a file with this extension is expected: " + String(el.fn.checkingFileExpression)); res.status(400).json("a file with this extension is expected: " + String(el.fn.checkingFileExpression));
return; return;
} }
} }
await this.responseHelper(res, el.fn.call(req["files"]["file"])); await this.responseHelper(res, el.fn.call(req["files"]["file"], req.query.id, req.query.description));
} }
}); });
}); });

View file

@ -1,7 +1,10 @@
import { NixStoreManagerPresentation } from "../../features/nix_store_manager/nix_store_manager"; import { NixStoreManagerPresentation } from "../../features/nix_store_manager/nix_store_manager";
import { PipelinePresentation } from "../../features/pipelines/pipeline_presentation"; import { PipelinePresentation } from "../../features/pipelines/pipeline_presentation";
import { ProcessPresentation } from "../../features/process/process_presentation"; import { ProcessPresentation } from "../../features/process/process_presentation";
import { ProjectInstancePresentation } from "../../features/project_instance/project_instance_presentation"; import {
ProjectInstancePresentation,
RobossemblerAssetsPresentation,
} from "../../features/project_instance/project_instance_presentation";
import { ProjectsPresentation } from "../../features/projects/projects_presentation"; import { ProjectsPresentation } from "../../features/projects/projects_presentation";
import { RealTimePresentation } from "../../features/realtime/realtime_presentation"; import { RealTimePresentation } from "../../features/realtime/realtime_presentation";
import { TriggerPresentation } from "../../features/triggers/triggers_presentation"; import { TriggerPresentation } from "../../features/triggers/triggers_presentation";
@ -21,6 +24,7 @@ export const httpRoutes: Routes[] = [
new RealTimePresentation(), new RealTimePresentation(),
new ProjectInstancePresentation(), new ProjectInstancePresentation(),
new NixStoreManagerPresentation(), new NixStoreManagerPresentation(),
new RobossemblerAssetsPresentation(),
] ]
.concat(routersImplementPureCrud) .concat(routersImplementPureCrud)
.map((el) => el.call()); .map((el) => el.call());

View file

@ -0,0 +1,8 @@
export const BufferExtensions = () => {
if (Buffer.joinBuffers === undefined) {
Buffer.prototype.joinBuffers = function (buffers: Array<Buffer>, delimiter = " ") {
const d = Buffer.from(delimiter);
return buffers.reduce((prev, b) => Buffer.concat([prev, d, b]));
};
}
};

View file

@ -1,4 +1,5 @@
import { ArrayExtensions } from "./array"; import { ArrayExtensions } from "./array";
import { BufferExtensions } from "./buffer";
import { StringExtensions } from "./string"; import { StringExtensions } from "./string";
declare global { declare global {
@ -10,15 +11,20 @@ declare global {
isEmpty(): boolean; isEmpty(): boolean;
isNotEmpty(): boolean; isNotEmpty(): boolean;
} }
interface BufferConstructor {
joinBuffers(buffers: Array<Buffer>, delimiter?: string);
}
interface String { interface String {
isEmpty(): boolean; isEmpty(): boolean;
isNotEmpty(): boolean; isNotEmpty(): boolean;
lastElement(): string; lastElement(): string;
hasPattern(pattern: string): boolean; hasPattern(pattern: string): boolean;
hasNoPattern(pattern: string): boolean; hasNoPattern(pattern: string): boolean;
pathNormalize(): string;
} }
} }
export const extensions = () => { export const extensions = () => {
ArrayExtensions(); ArrayExtensions();
StringExtensions(); StringExtensions();
BufferExtensions();
}; };

View file

@ -5,6 +5,11 @@ export const StringExtensions = () => {
return this.length === 0; return this.length === 0;
}; };
} }
if ("".pathNormalize === undefined) {
String.prototype.pathNormalize = function () {
return this.replace("//", "/");
};
}
if ("".isNotEmpty === undefined) { if ("".isNotEmpty === undefined) {
// eslint-disable-next-line no-extend-native // eslint-disable-next-line no-extend-native
String.prototype.isNotEmpty = function () { String.prototype.isNotEmpty = function () {
@ -27,3 +32,6 @@ export const StringExtensions = () => {
}; };
} }
}; };
// python3 /Users/idontsudo/framework/path.py --path /Users/idontsudo/webservice/server/build/public/0a3422cc-f2e3-4abc-87d8-ae13b8b6d26d/ --env /Users/idontsudo/framework/cad_generation/env.json
// python3 /Users/idontsudo/framework/path.py --path /Users/idontsudo/webservice/server/build/public/0a3422cc-f2e3-4abc-87d8-ae13b8b6d26d/ --env /Users/idontsudo/framework/cad_generation/env.json
// /Users/idontsudo/Desktop/FreeCAD.app/Contents/MacOS/FreeCAD /Users/idontsudo/framework/cad_generation/main.py

View file

@ -8,8 +8,19 @@ export interface Disposable {
export class TypedEvent<T> { export class TypedEvent<T> {
private listeners: Listener<T>[] = []; private listeners: Listener<T>[] = [];
public listenersOnces: Listener<T>[] = []; public listenersOnces: Listener<T>[] = [];
waitedEvent(predicate: (e: T) => boolean) {
return new Promise<T>((resolve, _reject) => {
this.on((e) => {
const isContinueWatching = predicate(e);
if (!isContinueWatching) {
resolve(e);
}
});
});
}
on = (listener: Listener<T>): Disposable => { on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener); this.listeners.push(listener);
return { return {

View file

@ -17,7 +17,9 @@ export interface WorkerDataExec {
process.on("message", async (message) => { process.on("message", async (message) => {
const workerData = message as WorkerDataExec; const workerData = message as WorkerDataExec;
if (workerData.type == WorkerType.SPAWN) { if (workerData.type == WorkerType.SPAWN) {
const subprocess = cp.spawn(workerData.command, workerData.cliArgs, { // Maybe error
// const subprocess = cp.spawn(workerData.command, workerData.cliArgs, {
const subprocess = cp.spawn(workerData.command, {
cwd: workerData.execPath, cwd: workerData.execPath,
}); });

View file

@ -1,13 +0,0 @@
// export class Payload<T>{
// model: T | undefined
// query:string | undefined
// setModel(model:T){
// this.model = model
// }
// setQuery(query:string){
// this.query = query
// }
// isEmpty(){
// return this.model != undefined || this.query != undefined
// }
// }

View file

@ -1,12 +1,6 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const validationMiddleware = ( export const validationMiddleware = (): RequestHandler => {
type: any,
value = "body",
skipMissingProperties = false,
whitelist = true,
forbidNonWhitelisted = true
): RequestHandler => {
// TODO:(IDONTSUDO) need TOKEN // TODO:(IDONTSUDO) need TOKEN
// return nextTick // return nextTick
return (req, res, next) => {}; return (req, res, next) => {};

View file

@ -6,7 +6,7 @@ export const validationModelMiddleware = (
type: any, type: any,
value = "body", value = "body",
skipMissingProperties = false, skipMissingProperties = false,
whitelist = true, whitelist = false,
forbidNonWhitelisted = true forbidNonWhitelisted = true
): RequestHandler => { ): RequestHandler => {
return (req, res, next) => { return (req, res, next) => {

View file

@ -1,24 +1,27 @@
export class ActivePipeline { export class ActivePipeline {
pipelineIsRunning: boolean; pipelineIsRunning: boolean;
projectUUID?: string | null; projectId?: string | null;
lastProcessCompleteCount: number | null; lastProcessCompleteCount: number | null;
error: any; error: any;
rootDir: string;
path: string; path: string;
constructor( constructor(
pipelineIsRunning: boolean, pipelineIsRunning: boolean,
lastProcessCompleteCount: number | null, lastProcessCompleteCount: number | null,
error: any, error: any,
path: string | null, path: string | null,
projectUUID?: string | null projectId: string | null,
rootDir: string | null
) { ) {
this.pipelineIsRunning = pipelineIsRunning; this.pipelineIsRunning = pipelineIsRunning;
this.projectUUID = projectUUID; this.projectId = projectId;
this.lastProcessCompleteCount = lastProcessCompleteCount; this.lastProcessCompleteCount = lastProcessCompleteCount;
this.error = error; this.error = error;
this.path = path; this.path = path;
this.rootDir = rootDir;
} }
static empty() { static empty() {
return new ActivePipeline(false, null, null, null, null); return new ActivePipeline(false, null, null, null, null, null);
} }
} }

View file

@ -4,10 +4,15 @@ extensions();
export class ExecError extends Error { export class ExecError extends Error {
static isExecError(e: any): ExecError | void { static isExecError(e: any): ExecError | void {
if ("type" in e && "script" in e && "unixTime" in e && "error" in e) { try {
return new ExecError(e.type, e.event, e.data); if (e) {
if ("type" in e && "script" in e && "unixTime" in e && "error" in e) {
return new ExecError(e.type, e.event, e.data);
}
}
} catch (error) {
console.log(error);
} }
return;
} }
script: string; script: string;
unixTime: number; unixTime: number;
@ -34,10 +39,15 @@ export class SpawnError extends Error {
this.unixTime = Date.now(); this.unixTime = Date.now();
} }
static isError(errorType: any): SpawnError | void { static isError(errorType: any): SpawnError | void {
if ("command" in errorType && "error" in errorType && "execPath" in errorType) { try {
return new SpawnError(errorType.command, errorType.execPath, errorType.error); if (errorType) {
if ("command" in errorType && "error" in errorType && "execPath" in errorType) {
return new SpawnError(errorType.command, errorType.execPath, errorType.error);
}
}
} catch (error) {
console.log(error);
} }
return;
} }
} }

View file

@ -10,9 +10,14 @@ export class ExecutorResult {
this.data = data; this.data = data;
} }
static isExecutorResult(value: any): void | ExecutorResult { static isExecutorResult(value: any): void | ExecutorResult {
if ("type" in value && "event" in value && "data" in value) { try {
return new ExecutorResult(value.type, value.event, value.data); if (value) {
if ("type" in value && "event" in value && "data" in value) {
return new ExecutorResult(value.type, value.event, value.data);
}
}
} catch (error) {
console.log(error);
} }
return;
} }
} }

View file

@ -0,0 +1,160 @@
import { IsArray, IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
export class Gravity {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export class Pose {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
@IsNumber()
roll: number;
@IsNumber()
pitch: number;
@IsNumber()
yaw: number;
}
export class Position {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export enum InstanceType {
RGB_CAMERA = "rgb_camera",
SCENE_SIMPLE_OBJECT = "scene_simple_object",
}
abstract class CoreInstances {}
export class Instance extends CoreInstances {
@IsEnum(InstanceType)
instanceType: InstanceType;
@Type(() => Position)
position: Position;
@IsArray()
quaternion: number[];
@IsOptional()
@IsString()
instanceAt: null | string = null;
}
export class SceneSimpleObject extends Instance {}
export class InstanceRgbCamera extends Instance {
@IsString()
cameraLink: string;
@IsString()
topicCameraInfo: string;
@IsOptional()
@IsString()
topicDepth: string | null;
@IsString()
topicImage: string;
}
export class Asset {
@IsString()
name: string;
@IsString()
ixx: string;
@IsString()
ixy: string;
@IsString()
ixz: string;
@IsString()
iyy: string;
@IsString()
izz: string;
@IsString()
mass: string;
@IsString()
posX: string;
@IsString()
posY: string;
@IsString()
posZ: string;
@IsString()
eulerX: string;
@IsString()
eulerY: string;
@IsString()
eulerZ: string;
@IsString()
iyz: string;
@IsString()
meshPath: string;
@IsString()
friction: string;
@IsString()
centerMassX: string;
@IsString()
centerMassY: string;
@IsString()
centerMassZ: string;
}
export class Physics {
@IsString()
engine_name: string;
@Type(() => Gravity)
gravity: Gravity;
}
export class RobossemblerAssets {
@ValidateNested()
@Type(() => Asset)
assets: Asset[];
@IsArray()
@Type(() => Instance, {
discriminator: {
property: "type",
subTypes: [
{ value: InstanceRgbCamera, name: InstanceType.RGB_CAMERA },
{ value: SceneSimpleObject, name: InstanceType.SCENE_SIMPLE_OBJECT },
],
},
keepDiscriminatorProperty: true,
})
instances: Instance[];
@IsOptional()
@ValidateNested()
@Type(() => Physics)
physics: Physics;
convertLocalPathsToServerPaths(server_address: string): RobossemblerAssets {
this.assets = this.assets.map((el) => {
el.meshPath = server_address + el.meshPath;
return el;
});
return this;
}
getAssetPath(assetName: string): string {
const findElement = this.assets.find((el) => el.name === assetName);
if (findElement === undefined) {
throw new Error("RobossemblerAssets.getAssetPath not found asset by name:" + assetName);
}
return findElement.meshPath;
}
getAssetAtInstance(instanceAt: string): Asset {
return this.assets.filter((el) => el.name === instanceAt)[0];
}
}

View file

@ -0,0 +1,3 @@
export enum StaticFiles {
robossembler_assets = "robossembler_assets.json",
}

View file

@ -0,0 +1,43 @@
import * as fs from "fs";
import { promisify } from "node:util";
export class FileSystemRepository {
public createDir = promisify(fs.mkdir);
public lsStat = promisify(fs.lstat);
public writeFileAsync = promisify(fs.writeFile);
public dirIsExists = promisify(fs.exists);
public stat = promisify(fs.stat);
public readFileAsync = promisify(fs.readFile);
public readdir = promisify(fs.readdir);
async readFileAtBuffer(path: string): Promise<Buffer> {
if ((await this.lsStat(path)).isDirectory()) {
return (
await this.readdir(path, {
encoding: "buffer",
})
).reduce((accumulator, currentValue) => Buffer.joinBuffers([accumulator, currentValue]), Buffer.from(""));
}
return await this.readFileAsync(path);
}
readDirRecursive(path: string, filesToDir: string[] = []): string[] {
const files = fs.readdirSync(path);
files.forEach((file) => {
let filePath = "";
if (path[path.length - 1] !== "/") {
filePath = `${path}/${file}`;
} else {
filePath = `${path}${file}`;
}
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
this.readDirRecursive(filePath, filesToDir);
} else {
filesToDir.push(file);
}
});
return filesToDir;
}
}

View file

@ -1,31 +0,0 @@
import * as fs from "fs";
import { promisify } from "node:util";
export const readFileAsync = promisify(fs.readFile);
export const readdir = promisify(fs.readdir);
export const stat = promisify(fs.stat);
export const lsStat = promisify(fs.lstat);
export const createDir = promisify(fs.mkdir);
export const dirIsExists = promisify(fs.exists);
export const writeFileAsync = promisify(fs.writeFile);
export function readDirRecursive(path: string, filesToDir: string[] = []) {
const files = fs.readdirSync(path);
files.forEach((file) => {
let filePath = "";
if (path[path.length - 1] !== "/") {
filePath = `${path}/${file}`;
} else {
filePath = `${path}${file}`;
}
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
readDirRecursive(filePath, filesToDir);
} else {
filesToDir.push(file);
}
});
return filesToDir;
}

View file

@ -0,0 +1,33 @@
import { ClassConstructor, plainToInstance } from "class-transformer";
import { ReadFileAndParseJsonUseCase } from "../usecases/read_file_and_parse_json";
import { Result } from "../helpers/result";
import { validate, ValidationError } from "class-validator";
const skipMissingProperties = false,
whitelist = false,
forbidNonWhitelisted = true;
export class ReadingJsonFileAndConvertingToInstanceClassScenario<T> {
model: ClassConstructor<T>;
constructor(cls: ClassConstructor<T>) {
this.model = cls;
}
call = async (path: string): Promise<Result<string, T>> => {
try {
const result = await new ReadFileAndParseJsonUseCase().call(path);
if (result.isFailure()) {
return result.forward();
}
const json = result.value;
const model = plainToInstance(this.model, json);
const errors = await validate(model as object, { skipMissingProperties, whitelist, forbidNonWhitelisted });
if (errors.length > 0) {
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(", ");
return Result.error("ReadingJsonFileAndConvertingToInstanceClassScenario:" + message);
} else {
return Result.ok(model as T);
}
} catch (error) {
return Result.error("ReadingJsonFileAndConvertingToInstanceClassScenario" + String(error));
}
};
}

View file

@ -4,20 +4,29 @@ import {
} from "../../features/project_instance/models/project_instance_database_model"; } from "../../features/project_instance/models/project_instance_database_model";
import { pipelineRealTimeService } from "../../features/realtime/realtime_presentation"; import { pipelineRealTimeService } from "../../features/realtime/realtime_presentation";
import { App } from "../controllers/app"; import { App } from "../controllers/app";
import { CreateFolderUseCase } from "../usecases/crete_folder_usecase"; import { CreateFolderUseCase } from "../usecases/create_folder_usecase";
import { SearchDataBaseModelUseCase } from "../usecases/search_database_model_usecase"; import { SearchDataBaseModelUseCase } from "../usecases/search_database_model_usecase";
export class SetLastActivePipelineToRealTimeServiceScenario { export class SetLastActivePipelineToRealTimeServiceScenario {
call = async (): Promise<void> => { call = async (): Promise<void> => {
const result = await new SearchDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call({ return (
isActive: true, await new SearchDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call({
}); isActive: true,
})
if (result.isSuccess()) { ).fold(
const projectModel = result.value; async (projectModel) => {
const projectPath = App.staticFilesStoreDir() + result.value.rootDir + "/"; const projectPath = App.staticFilesStoreDir() + projectModel.rootDir + "/";
await new CreateFolderUseCase().call(projectPath); await new CreateFolderUseCase().call(projectPath);
pipelineRealTimeService.setPipelineDependency(projectModel.project.pipelines, projectPath, projectModel._id); pipelineRealTimeService.setPipelineDependency(
} projectModel.project.pipelines,
projectPath,
projectModel._id,
projectModel.rootDir
);
},
async (_e) => {
console.log("not found active pipeline");
}
);
}; };
} }

View file

@ -21,53 +21,61 @@ export class ExecutorProgramService
constructor(execPath: string, maxTime: number | null = null) { constructor(execPath: string, maxTime: number | null = null) {
super(); super();
this.execPath = execPath; this.execPath = execPath.pathNormalize();
this.maxTime = maxTime; this.maxTime = maxTime;
} }
private async workerExecuted(command: string, workerType: WorkerType, args: Array<string> | undefined = undefined) { private async workerExecuted(command: string, workerType: WorkerType, args: Array<string> | undefined = undefined) {
cluster.setupPrimary({ try {
exec: "/Users/idontsudo/Desktop/testdeck-mocha-seed/server/build/src/core/helpers/worker_computed.js", cluster.setupPrimary({
}); exec: __dirname + "/../helpers/worker_computed.js",
});
const worker = cluster.fork(); const worker = cluster.fork();
await delay(300); await delay(300);
this.worker = worker; this.worker = worker;
const workerDataExec: WorkerDataExec = { const workerDataExec: WorkerDataExec = {
command: command, command: command,
execPath: this.execPath, execPath: this.execPath,
type: workerType, type: workerType,
cliArgs: args, cliArgs: args,
}; };
worker.send(workerDataExec); worker.send(workerDataExec);
worker.on("message", (e) => { worker.on("message", (e) => {
const spawnError = SpawnError.isError(e); const spawnError = SpawnError.isError(e);
if (spawnError instanceof SpawnError) {
this.emit(Result.error(spawnError)); if (spawnError instanceof SpawnError) {
return; this.emit(Result.error(spawnError));
return;
}
const execError = ExecError.isExecError(e);
if (execError instanceof ExecError) {
this.emit(Result.error(execError));
return;
}
const executorResult = ExecutorResult.isExecutorResult(e);
if (executorResult instanceof ExecutorResult) {
this.emit(Result.ok(executorResult));
return;
}
});
if (this.maxTime != null) {
setTimeout(() => {
this.worker.kill();
this.emit(
Result.error(
WorkerType.EXEC ? new ExecError(command, "timeout err") : new SpawnError(command, "timeout err")
)
);
}, this.maxTime!);
} }
const executorResult = ExecutorResult.isExecutorResult(e); } catch (error) {
if (executorResult instanceof ExecutorResult) { console.log(error);
this.emit(Result.ok(executorResult));
return;
}
const execError = ExecError.isExecError(e);
if (execError instanceof ExecError) {
this.emit(Result.error(execError));
return;
}
});
if (this.maxTime != null) {
setTimeout(() => {
this.worker.kill();
this.emit(
Result.error(WorkerType.EXEC ? new ExecError(command, "timeout err") : new SpawnError(command, "timeout err"))
);
}, this.maxTime!);
} }
} }
@ -78,6 +86,7 @@ export class ExecutorProgramService
return; return;
} }
this.workerExecuted(command, WorkerType.SPAWN, args); this.workerExecuted(command, WorkerType.SPAWN, args);
return; return;
} }
} }

View file

@ -5,23 +5,7 @@ import { BinaryLike } from "crypto";
import { EventsFileChanger, MetaDataFileManagerModel } from "../models/meta_data_file_manager_model"; import { EventsFileChanger, MetaDataFileManagerModel } from "../models/meta_data_file_manager_model";
import { Result } from "../helpers/result"; import { Result } from "../helpers/result";
import { TypedEvent } from "../helpers/typed_event"; import { TypedEvent } from "../helpers/typed_event";
import { lsStat, readFileAsync, readdir, stat } from "../repository/fs"; import { FileSystemRepository } from "../repository/file_system_repository";
function joinBuffers(buffers: Array<Buffer>, delimiter = " ") {
const d = Buffer.from(delimiter);
return buffers.reduce((prev, b) => Buffer.concat([prev, d, b]));
}
async function readFileAtBuffer(path: string): Promise<Buffer> {
if ((await lsStat(path)).isDirectory()) {
return (
await readdir(path, {
encoding: "buffer",
})
).reduce((accumulator, currentValue) => joinBuffers([accumulator, currentValue]), Buffer.from(""));
}
return await readFileAsync(path);
}
function md5(content: Buffer | BinaryLike): Promise<string> { function md5(content: Buffer | BinaryLike): Promise<string> {
return new Promise((resolve, _reject) => { return new Promise((resolve, _reject) => {
@ -41,12 +25,14 @@ export class FilesChangeNotifierService
extends TypedEvent<Result<Error, IHashesCache>> extends TypedEvent<Result<Error, IHashesCache>>
implements IFilesChangeNotifierService implements IFilesChangeNotifierService
{ {
fileSystemRepository: FileSystemRepository;
watcher!: fs.FSWatcher; watcher!: fs.FSWatcher;
directory: string; directory: string;
constructor(directory: string) { constructor(directory: string) {
super(); super();
this.directory = directory; this.directory = directory;
this.init(); this.init();
this.fileSystemRepository = new FileSystemRepository();
} }
hashes: IHashesCache = {}; hashes: IHashesCache = {};
async init() { async init() {
@ -56,18 +42,18 @@ export class FilesChangeNotifierService
} }
} }
async setHash(file: string) { async setHash(file: string) {
const data = await readFileAsync(file); const data = await this.fileSystemRepository.readFileAsync(file);
const md5Current = await md5(data); const md5Current = await md5(data);
this.hashes[file] = new MetaDataFileManagerModel(file, md5Current, EventsFileChanger.static); this.hashes[file] = new MetaDataFileManagerModel(file, md5Current, EventsFileChanger.static);
this.emit(Result.ok(this.hashes)); this.emit(Result.ok(this.hashes));
} }
async getFiles(dir: string): Promise<string | any[]> { async getFiles(dir: string): Promise<string | any[]> {
const subdirs = await readdir(dir); const subdirs = await new FileSystemRepository().readdir(dir);
const files = await Promise.all( const files = await Promise.all(
subdirs.map(async (subdir) => { subdirs.map(async (subdir) => {
const res = resolve(dir, subdir); const res = resolve(dir, subdir);
return (await stat(res)).isDirectory() ? this.getFiles(res) : res; return (await this.fileSystemRepository.stat(res)).isDirectory() ? this.getFiles(res) : res;
}) })
); );
return files.reduce((a: string | any[], f: any) => a.concat(f), []); return files.reduce((a: string | any[], f: any) => a.concat(f), []);
@ -85,7 +71,7 @@ export class FilesChangeNotifierService
fsWait = false; fsWait = false;
}, 100); }, 100);
try { try {
const file = await readFileAtBuffer(filePath); const file = await this.fileSystemRepository.readFileAtBuffer(filePath);
const md5Current = await md5(file); const md5Current = await md5(file);
if (md5Current === md5Previous) { if (md5Current === md5Previous) {
return; return;

View file

@ -3,7 +3,7 @@ import { ExecError } from "../models/exec_error_model";
import { ExecutorResult } from "../models/executor_result"; import { ExecutorResult } from "../models/executor_result";
import { ActivePipeline } from "../models/active_pipeline_model"; import { ActivePipeline } from "../models/active_pipeline_model";
import { IPipeline } from "../models/process_model"; import { IPipeline } from "../models/process_model";
import { Iteration } from "./stack_service"; import { Iteration, StackService } from "./stack_service";
export class PipelineRealTimeService extends TypedEvent<ActivePipeline> { export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
status: ActivePipeline; status: ActivePipeline;
@ -32,9 +32,8 @@ export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
this.iterationLogSaver(iteration); this.iterationLogSaver(iteration);
} }
iterationLogSaver(iteration: Iteration): void { iterationLogSaver(_iteration: Iteration): void {
// throw new Error("Method not implemented."); // throw new Error("Method not implemented.");
// citeration.result.data
} }
iterationErrorObserver(iteration: Iteration): void { iterationErrorObserver(iteration: Iteration): void {
@ -59,15 +58,17 @@ export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
this.status.pipelineIsRunning = false; this.status.pipelineIsRunning = false;
} }
} }
setPipelineDependency(pipelineModels: IPipeline[], path: string, projectUUID: string) { setPipelineDependency(pipelineModels: IPipeline[], path: string, projectId: string, rootDir: string) {
this.pipelineModels = pipelineModels; this.pipelineModels = pipelineModels;
this.status["projectUUID"] = projectUUID; this.status["projectId"] = projectId;
this.status["path"] = path; this.status["path"] = path;
this.status["rootDir"] = rootDir;
} }
runPipeline(): void { runPipeline(): void {
// const stack = new StackService(this.pipelineModels, this.path); const stack = new StackService(this.pipelineModels, this.status.path);
// this.status["pipelineIsRunning"] = true;
// stack.on(this.pipelineSubscriber); this.status["pipelineIsRunning"] = true;
// stack.call(); stack.on(this.pipelineSubscriber);
stack.call();
} }
} }

View file

@ -11,7 +11,7 @@ import { Trigger } from "../../features/triggers/models/trigger_database_model";
export interface Iteration { export interface Iteration {
hashes: IHashesCache | null; hashes: IHashesCache | null;
process: IPipeline; pipeline: IPipeline;
result?: ExecError | SpawnError | ExecutorResult; result?: ExecError | SpawnError | ExecutorResult;
} }
@ -36,80 +36,61 @@ export class StackService extends TypedEvent<Iteration[]> implements IStackServi
el = this.commandHandler(el); el = this.commandHandler(el);
this.callStack.push({ this.callStack.push({
hashes: null, hashes: null,
process: el, pipeline: el,
}); });
} }
} }
private commandHandler(processMetaData: IPipeline) { private commandHandler(processMetaData: IPipeline) {
processMetaData.process.command = processMetaData.process.command.replace("$PATH", this.path); processMetaData.process.command = processMetaData.process.command.replace("$PATH", this.path).pathNormalize();
return processMetaData; return processMetaData;
} }
public async call() { public async call() {
let inc = 0; this.callStack.map(async (el, index) => {
await this.execStack(index, el);
for await (const el of this.callStack!) {
await this.execStack(inc, el);
inc += 1;
this.emit(this.callStack); this.emit(this.callStack);
} });
} }
async execStack(stackNumber: number, stackLayer: Iteration): Promise<void | boolean> { async execStack(stackNumber: number, stackLayer: Iteration): Promise<void | boolean> {
const executorService = new ExecutorProgramService(this.path); const executorService = new ExecutorProgramService(this.path);
executorService.call(stackLayer.process.process.type, stackLayer.process.process.command); executorService.call(stackLayer.pipeline.process.type, stackLayer.pipeline.process.command);
const filesChangeNotifierService = new FilesChangeNotifierService(this.path); const filesChangeNotifierService = new FilesChangeNotifierService(this.path);
filesChangeNotifierService.call(); filesChangeNotifierService.call();
const result = await this.waitEvent<Result<ExecError | SpawnError, ExecutorResult>>(executorService);
await delay(100);
if (result.isSuccess()) {
this.callStack[stackNumber].result = result.value;
this.callStack[stackNumber].hashes = filesChangeNotifierService.hashes;
const triggerResult = await this.triggerExec(stackLayer.process.trigger, stackNumber); const result = await executorService.waitedEvent((event: Result<ExecError | SpawnError, ExecutorResult>) => {
triggerResult.fold( event.map((value) => {
(s) => { if (value.event === EXEC_EVENT.END) {
s; return true;
},
(e) => {
e;
} }
); });
} return false;
});
await delay(100);
result.map(async (el) => {
this.callStack.at(stackNumber).result = el;
this.callStack.at(stackNumber).hashes = filesChangeNotifierService.hashes;
(await this.triggerExec(stackLayer.pipeline.trigger, stackNumber)).map(() => {
filesChangeNotifierService.cancel();
});
});
filesChangeNotifierService.cancel();
return; return;
} }
public waitEvent<T>(stream: TypedEvent<T>): Promise<T> {
const promise = new Promise<T>((resolve, reject) => {
const addListener = () => {
stream.on((e) => {
const event = e as Result<ExecError | SpawnError, ExecutorResult>;
event.fold(
(s) => {
if (s.event === EXEC_EVENT.END) {
resolve(e);
}
},
(e) => {
reject(e);
}
);
});
};
addListener();
});
return promise;
}
private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<boolean, boolean>> {
if (trigger !== null) {
const hashes = this.callStack[stackNumber].hashes;
if (hashes != null) { private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<Error, void>> {
return await new TriggerService(trigger, hashes, this.path).call(); try {
if (trigger !== null) {
const hashes = this.callStack[stackNumber].hashes;
if (hashes != null) {
await new TriggerService(trigger, hashes, this.path).call();
}
throw new Error("Hashes is null");
} }
throw new Error("Hashes is null"); return Result.ok();
} catch (error) {
return Result.error(error);
} }
return Result.ok();
} }
} }

View file

@ -1,10 +1,15 @@
import { App } from "../controllers/app"; import { App } from "../controllers/app";
import { dirIsExists } from "../repository/fs"; import { FileSystemRepository } from "../repository/file_system_repository";
import { CreateFolderUseCase } from "./crete_folder_usecase"; import { CreateFolderUseCase } from "./create_folder_usecase";
export class CheckAndCreateStaticFilesFolderUseCase { export class CheckAndCreateStaticFilesFolderUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
call = async (): Promise<void> => { call = async (): Promise<void> => {
if (await dirIsExists(App.staticFilesStoreDir())) { if (await this.fileSystemRepository.dirIsExists(App.staticFilesStoreDir())) {
return; return;
} }
const createFolderUseCase = await new CreateFolderUseCase().call(App.staticFilesStoreDir()); const createFolderUseCase = await new CreateFolderUseCase().call(App.staticFilesStoreDir());

View file

@ -1,10 +1,15 @@
import { Result } from "../helpers/result"; import { Result } from "../helpers/result";
import { writeFileAsync } from "../repository/fs"; import { FileSystemRepository } from "../repository/file_system_repository";
export class CreateFileUseCase { export class CreateFileUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
async call(path: string, buffer: Buffer): Promise<Result<Error, void>> { async call(path: string, buffer: Buffer): Promise<Result<Error, void>> {
try { try {
await writeFileAsync(path, buffer); await this.fileSystemRepository.writeFileAsync(path.pathNormalize(), buffer);
return Result.ok(); return Result.ok();
} catch (err) { } catch (err) {
return Result.error(err as Error); return Result.error(err as Error);

View file

@ -0,0 +1,21 @@
import { Result } from "../helpers/result";
import { FileSystemRepository } from "../repository/file_system_repository";
export class CreateFolderUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
call = async (path: string): Promise<Result<Error, string>> => {
try {
if (await this.fileSystemRepository.dirIsExists(path)) {
return Result.ok("ok");
}
await this.fileSystemRepository.createDir(path);
return Result.ok("ok");
} catch (error) {
return Result.error(error as Error);
}
};
}

View file

@ -1,17 +0,0 @@
import { Result } from "../helpers/result";
import { dirIsExists, createDir } from "../repository/fs";
export class CreateFolderUseCase {
call = async (path: string): Promise<Result<Error, string>> => {
try {
if (await dirIsExists(path)) {
return Result.error(new Error("createFolderUseCase create dir "));
}
await createDir(path);
return Result.ok("ok");
} catch (error) {
return Result.error(error as Error);
}
};
}

View file

@ -0,0 +1,11 @@
export class FindPredicateModelAndUpdateDatabaseModelUseCase<D> {
databaseModel: D;
constructor(model) {
this.databaseModel = model;
}
async call(conditions: Partial<D>, update: Partial<D>) {
const dbModel = this.databaseModel as any;
dbModel.findOneAndUpdate(conditions, update);
}
}

View file

@ -0,0 +1,7 @@
import { Result } from "../helpers/result";
export class GetServerAddressUseCase {
call = (): Result<string, string> => {
return Result.ok("http://localhost:4001");
};
}

View file

@ -0,0 +1,25 @@
import { Result } from "../helpers/result";
import { FileSystemRepository } from "../repository/file_system_repository";
export class ReadFileAndParseJsonUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
async call<T>(path: string): Promise<Result<string, T>> {
try {
if (RegExp(path).test("^(.+)/([^/]+)$")) {
return Result.error(`ReadFileAndParseJsonUseCase got the bad way: ${path}`);
}
const file = await this.fileSystemRepository.readFileAsync(path);
try {
return Result.ok(JSON.parse(file.toString()));
} catch {
return Result.error(`ReadFileAndParseJsonUseCase is not json type file parse path: ${path}`);
}
} catch (error) {
return Result.error(`ReadFileAndParseJsonUseCase error:${error}`);
}
}
}

View file

@ -0,0 +1,13 @@
import { Result } from "../helpers/result";
import { FileSystemRepository } from "../repository/file_system_repository";
export class WriteFileSystemFileUseCase {
call = async (path: string, fileData: string): Promise<Result<string, void>> => {
try {
await new FileSystemRepository().writeFileAsync(path, fileData);
return Result.ok(undefined);
} catch (error) {
return Result.error(`WriteFileSystemFileUseCase error: ${error}`);
}
};
}

View file

@ -0,0 +1,8 @@
interface IMessage {
message: string;
}
export abstract class CoreValidation {
abstract regExp: RegExp;
abstract message: IMessage;
}

View file

@ -0,0 +1,6 @@
import { CoreValidation } from "./core_validation";
export class MongoIdValidation extends CoreValidation {
regExp = RegExp("^[0-9a-fA-F]{24}$");
message = { message: "is do not mongo db object uuid" };
}

View file

@ -1,41 +0,0 @@
import { IsMongoId, IsEnum } from "class-validator";
import { Schema, model } from "mongoose";
import { TriggerModel, triggerSchema } from "../triggers/trigger_model";
import { schemaProcess } from "../process/process_model";
import { StackGenerateType } from "../../core/models/process_model";
export const PipelineSchema = new Schema({
process: {
type: Schema.Types.ObjectId,
ref: schemaProcess,
autopopulate: true,
default: null,
},
trigger: {
type: Schema.Types.ObjectId,
ref: triggerSchema,
autopopulate: true,
default: null,
},
command: {
type: String,
},
}).plugin(require("mongoose-autopopulate"));
export const schemaPipeline = "Pipeline";
export const PipelineDBModel = model<PipelineModel>(schemaPipeline, PipelineSchema);
export class PipelineModel {
@IsMongoId()
public process: PipelineModel;
@IsMongoId()
//TODO(IDONTSUDO):NEED OPTION DECORATOR??
public trigger: TriggerModel;
public env = null;
@IsEnum(StackGenerateType)
public stackGenerateType: StackGenerateType;
}

View file

@ -1,59 +0,0 @@
import { IsString, IsOptional, IsEnum, IsNumber, IsBoolean } from "class-validator";
import { Schema, model } from "mongoose";
import { IProcess, IssueType } from "../../core/models/process_model";
import { EXEC_TYPE } from "../../core/models/exec_error_model";
export const ProcessSchema = new Schema({
type: {
type: String,
},
command: {
type: String,
},
isGenerating: {
type: String,
},
isLocaleCode: {
type: String,
},
issueType: {
type: String,
},
timeout: {
type: Number,
default: null,
},
commit: {
type: String,
default: null,
},
});
export const schemaProcess = "Process";
export const ProcessDBModel = model<IProcess>(schemaProcess, ProcessSchema);
export class ProcessModel implements IProcess {
@IsEnum(EXEC_TYPE)
public type: EXEC_TYPE;
@IsString()
public command: string;
@IsBoolean()
public isGenerating: boolean;
@IsBoolean()
public isLocaleCode: boolean;
@IsEnum(IssueType)
public issueType: IssueType;
@IsOptional()
@IsNumber()
public timeout?: number;
@IsOptional()
@IsString()
public commit?: string;
}

View file

@ -1,29 +1,21 @@
import { App } from "../../../core/controllers/app"; import { App } from "../../../core/controllers/app";
import { Result } from "../../../core/helpers/result"; import { Result } from "../../../core/helpers/result";
import { CreateDataBaseModelUseCase } from "../../../core/usecases/create_database_model_usecase"; import { CreateDataBaseModelUseCase } from "../../../core/usecases/create_database_model_usecase";
import { CreateFolderUseCase } from "../../../core/usecases/crete_folder_usecase"; import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
import { pipelineRealTimeService } from "../../realtime/realtime_presentation";
import { ProjectInstanceDbModel } from "../models/project_instance_database_model"; import { ProjectInstanceDbModel } from "../models/project_instance_database_model";
import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model"; import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { SetActiveProjectScenario } from "./set_active_project_use_scenario";
export class CreateNewProjectInstanceScenario { export class CreateNewProjectInstanceScenario {
call = async (model: ProjectInstanceValidationModel): Promise<Result<Error, any>> => { call = async (): Promise<Result<Error, any>> => {
try { try {
const folderName = uuidv4() + "/"; // (await new SetActiveProjectScenario().call(id)).map(() => {
const createFolderUseCase = await new CreateFolderUseCase().call(App.staticFilesStoreDir() + folderName); // return Result.ok({ status: "ok" });
if (createFolderUseCase.isFailure()) { // });
return createFolderUseCase.forward();
}
model.rootDir = folderName;
const createDataBaseModelUseCase = await new CreateDataBaseModelUseCase(ProjectInstanceDbModel).call(model);
if (createDataBaseModelUseCase.isFailure()) {
return createDataBaseModelUseCase.forward();
}
return Result.ok({ status: "ok" });
} catch (error) { } catch (error) {
console.log(error);
return Result.error(error as Error); return Result.error(error as Error);
} }
}; };

View file

@ -0,0 +1,33 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { RobossemblerAssets } from "../../../core/models/robossembler_assets";
import { StaticFiles } from "../../../core/models/static_files";
import { ReadingJsonFileAndConvertingToInstanceClassScenario } from "../../../core/scenarios/read_file_and_json_to_plain_instance_class_scenario";
import { GetServerAddressUseCase } from "../../../core/usecases/get_server_address_usecase";
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
export class RobossemblerAssetsNetworkMapperScenario extends CallbackStrategyWithEmpty {
async call(): ResponseBase {
try {
const result = await new PipelineStatusUseCase().call();
return await result.map(async (activeInstanceModel) => {
return (
await new ReadingJsonFileAndConvertingToInstanceClassScenario(RobossemblerAssets).call(
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`
)
).map((robossemblerAssets) => {
return new GetServerAddressUseCase().call().map((address) => {
return Result.ok(
robossemblerAssets.convertLocalPathsToServerPaths(
`${address}/${activeInstanceModel.rootDir.pathNormalize()}`
)
);
});
});
});
} catch (error) {
return Result.error(error);
}
}
}

View file

@ -0,0 +1,28 @@
import { CallbackStrategyWithValidationModel, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { RobossemblerAssets } from "../../../core/models/robossembler_assets";
import { StaticFiles } from "../../../core/models/static_files";
import { ReadingJsonFileAndConvertingToInstanceClassScenario } from "../../../core/scenarios/read_file_and_json_to_plain_instance_class_scenario";
import { WriteFileSystemFileUseCase } from "../../../core/usecases/write_file_system_file_usecase";
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
export class SaveActiveSceneScenario extends CallbackStrategyWithValidationModel<RobossemblerAssets> {
validationModel: RobossemblerAssets = new RobossemblerAssets();
async call(model: RobossemblerAssets): ResponseBase {
return (await new PipelineStatusUseCase().call()).map(async (activeInstanceModel) =>
(
await new ReadingJsonFileAndConvertingToInstanceClassScenario(RobossemblerAssets).call(
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`.pathNormalize()
)
).map(async (prevModel) => {
model.assets = prevModel.assets;
return (
await new WriteFileSystemFileUseCase().call(
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`.pathNormalize(),
JSON.stringify(model)
)
).map(() => Result.ok("assets is rewrite"));
})
);
}
}

View file

@ -0,0 +1,35 @@
import { App } from "../../../core/controllers/app";
import { CallbackStrategyWithIdQuery, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { SetLastActivePipelineToRealTimeServiceScenario } from "../../../core/scenarios/set_active_pipeline_to_realtime_service_scenario";
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
import { UpdateDataBaseModelUseCase } from "../../../core/usecases/update_database_model_usecase";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { IProjectInstanceModel, ProjectInstanceDbModel } from "../models/project_instance_database_model";
export class SetActiveProjectScenario extends CallbackStrategyWithIdQuery {
idValidationExpression = new MongoIdValidation();
async call(id: string): ResponseBase {
const result = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call(id);
// id
if (result.isFailure()) {
return result.forward();
}
const model = result.value;
return await (
await new CreateFolderUseCase().call(App.staticFilesStoreDir() + model.rootDir)
).map(async () => {
model.isActive = true;
return (await new UpdateDataBaseModelUseCase(ProjectInstanceDbModel).call(model)).map(async (el) => {
// TODO(IDONTSUDO): move it to a separate UseCase
await ProjectInstanceDbModel.updateMany({ _id: { $ne: el._id }, isActive: { $eq: true } }, { isActive: false });
await new SetLastActivePipelineToRealTimeServiceScenario().call();
return Result.ok(`project ${id} is active`);
});
});
}
}

View file

@ -1,23 +1,37 @@
import { App } from "../../../core/controllers/app";
import { CallbackStrategyWithFileUpload, ResponseBase } from "../../../core/controllers/http_controller"; import { CallbackStrategyWithFileUpload, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result"; import { Result } from "../../../core/helpers/result";
import { IFile } from "../../../core/interfaces/file"; import { IFile } from "../../../core/interfaces/file";
import { CreateDataBaseModelUseCase } from "../../../core/usecases/create_database_model_usecase";
import { CreateFileUseCase } from "../../../core/usecases/create_file_usecase"; import { CreateFileUseCase } from "../../../core/usecases/create_file_usecase";
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase"; import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { ProjectInstanceDbModel } from "../models/project_instance_database_model";
import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model";
import { v4 as uuidv4 } from "uuid";
import { SetActiveProjectScenario } from "./set_active_project_use_scenario";
export class UploadCadFileToProjectScenario extends CallbackStrategyWithFileUpload { export class UploadCadFileToProjectScenario extends CallbackStrategyWithFileUpload {
checkingFileExpression: RegExp = RegExp(".FCStd"); checkingFileExpression: RegExp = RegExp(".FCStd");
idValidationExpression = new MongoIdValidation();
async call(file: IFile): ResponseBase { async call(file: IFile, id: string, description: string): ResponseBase {
const pipelineStatusUseCase = await new PipelineStatusUseCase().call(); const folderName = uuidv4() + "/";
if (pipelineStatusUseCase.isFailure()) { const model = new ProjectInstanceValidationModel();
return pipelineStatusUseCase.forward(); model["project"] = id;
} model["description"] = description;
const projectFolder = pipelineStatusUseCase.value.path; model["rootDir"] = folderName;
const createFileUseCase = await new CreateFileUseCase().call(projectFolder + file.name, file.data); model["isActive"] = true;
if (createFileUseCase.isFailure()) { return (await new CreateFolderUseCase().call(App.staticFilesStoreDir() + folderName)).map(async () =>
return createFileUseCase.forward(); (await new CreateDataBaseModelUseCase(ProjectInstanceDbModel).call(model)).map(async (databaseModel) =>
} (await new SetActiveProjectScenario().call(databaseModel.id)).map(async () =>
(await new CreateFileUseCase().call(App.staticFilesStoreDir() + folderName + file.name, file.data)).map(
return Result.ok("ok"); () => {
return Result.ok("ok");
}
)
)
)
);
} }
} }

View file

@ -9,4 +9,7 @@ export class ProjectInstanceValidationModel {
@IsOptional() @IsOptional()
public rootDir: string; public rootDir: string;
@IsOptional()
public isActive: boolean;
} }

View file

@ -1,5 +1,11 @@
import { CrudController } from "../../core/controllers/crud_controller"; import { CrudController } from "../../core/controllers/crud_controller";
import { CoreHttpController } from "../../core/controllers/http_controller";
import { RobossemblerAssets } from "../../core/models/robossembler_assets";
import { CreateNewProjectInstanceScenario } from "./domain/create_new_project_scenario"; import { CreateNewProjectInstanceScenario } from "./domain/create_new_project_scenario";
import { RobossemblerAssetsNetworkMapperScenario } from "./domain/robossembler_assets_network_mapper_scenario";
import { SaveActiveSceneScenario } from "./domain/save_active_scene_scenario";
import { SetActiveProjectScenario } from "./domain/set_active_project_use_scenario";
import { UploadCadFileToProjectScenario } from "./domain/upload_file_to_to_project_scenario"; import { UploadCadFileToProjectScenario } from "./domain/upload_file_to_to_project_scenario";
import { ProjectInstanceDbModel } from "./models/project_instance_database_model"; import { ProjectInstanceDbModel } from "./models/project_instance_database_model";
import { ProjectInstanceValidationModel } from "./models/project_instance_validation_model"; import { ProjectInstanceValidationModel } from "./models/project_instance_validation_model";
@ -14,14 +20,30 @@ export class ProjectInstancePresentation extends CrudController<
url: "project_instance", url: "project_instance",
databaseModel: ProjectInstanceDbModel, databaseModel: ProjectInstanceDbModel,
}); });
super.post(new CreateNewProjectInstanceScenario().call); super.post(new CreateNewProjectInstanceScenario().call);
super.subRoutes = [ this.subRoutes.push({
{ method: "POST",
method: "POST", subUrl: "set/active/project",
subUrl: "upload", fn: new SetActiveProjectScenario(),
fn: new UploadCadFileToProjectScenario(), });
},
]; this.subRoutes.push({
method: "POST",
subUrl: "upload",
fn: new UploadCadFileToProjectScenario(),
});
}
}
export class RobossemblerAssetsPresentation extends CoreHttpController<RobossemblerAssets> {
constructor() {
super({
url: "robossembler_assets",
validationModel: RobossemblerAssets,
});
super.get(new RobossemblerAssetsNetworkMapperScenario().call);
super.post(new SaveActiveSceneScenario().call);
} }
} }

View file

@ -1,31 +0,0 @@
import { Schema, model } from "mongoose";
import { PipelineModel, schemaPipeline } from "../pipelines/pipeline_model";
import { IsMongoId, IsString } from "class-validator";
export interface IProjectModel {
pipelines: [PipelineModel];
rootDir: string;
}
export const ProjectSchema = new Schema({
pipelines: {
type: Array<Schema.Types.ObjectId>,
ref: schemaPipeline,
autopopulate: true,
default: null,
},
rootDir: {
type: String,
},
}).plugin(require("mongoose-autopopulate"));
const schema = "Projects";
export const ProjectDBModel = model<IProjectModel>(schema, ProjectSchema);
export class ProjectModel implements IProjectModel {
@IsMongoId()
pipelines: [PipelineModel];
@IsString()
rootDir: string;
}

View file

@ -6,10 +6,11 @@ export class PipelineStatusUseCase {
async call(): Promise<Result<Error, ActivePipeline>> { async call(): Promise<Result<Error, ActivePipeline>> {
try { try {
const status = pipelineRealTimeService.status; const status = pipelineRealTimeService.status;
if (status.projectUUID !== null) { if (status.projectId !== null) {
return Result.ok(status); return Result.ok(status);
} }
if (status.projectUUID === null) {
if (status.projectId === null) {
return Result.error(new Error("pipelineRealTimeService does not have an active project instance")); return Result.error(new Error("pipelineRealTimeService does not have an active project instance"));
} }
} catch (error) { } catch (error) {

View file

@ -1,41 +1,60 @@
import { App } from "../../../core/controllers/app"; import { App } from "../../../core/controllers/app";
import { CallbackStrategyWithEmpty } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result"; import { Result } from "../../../core/helpers/result";
import { IPipeline } from "../../../core/models/process_model";
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase"; import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
import { UpdateDataBaseModelUseCase } from "../../../core/usecases/update_database_model_usecase"; import { PipelineValidationModel } from "../../pipelines/models/pipeline_validation_model";
import { import {
IProjectInstanceModel, IProjectInstanceModel,
ProjectInstanceDbModel, ProjectInstanceDbModel,
} from "../../project_instance/models/project_instance_database_model"; } from "../../project_instance/models/project_instance_database_model";
import { RealTimeValidationModel, pipelineRealTimeService } from "../realtime_presentation"; import { pipelineRealTimeService } from "../realtime_presentation";
import { PipelineStatusUseCase } from "./pipeline_status_usecase";
export class RunInstancePipelineUseCase { const mongoPipelineModelMapper = (el: PipelineValidationModel): IPipeline => {
async call(model: RealTimeValidationModel): Promise<Result<Error, any>> { const mapObj: IPipeline = {
const { id } = model; process: {
const readByIdDataBaseModelUseCase = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>( type: el.process.type,
ProjectInstanceDbModel command: el.process.command,
).call(id); isGenerating: Boolean(el.process.isGenerating),
isLocaleCode: Boolean(el.process.isLocaleCode),
issueType: el.process.issueType,
},
trigger: {
type: el.trigger.type,
value: el.trigger.value.map((el) => String(el)),
},
env: null,
stackGenerateType: el.stackGenerateType,
};
return mapObj;
};
if (readByIdDataBaseModelUseCase.isFailure()) { export class RunInstancePipelineUseCase extends CallbackStrategyWithEmpty {
return readByIdDataBaseModelUseCase.forward(); async call(): Promise<Result<any, string>> {
} return (await new PipelineStatusUseCase().call()).map(async (activePipelineModel) => {
if (activePipelineModel.pipelineIsRunning) {
return Result.error("pipeline is running");
}
const readByIdDataBaseModelUseCase = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(
ProjectInstanceDbModel
).call(activePipelineModel.projectId);
const projectModel = readByIdDataBaseModelUseCase.value; if (readByIdDataBaseModelUseCase.isFailure()) {
projectModel.isActive = true; return readByIdDataBaseModelUseCase.forward();
}
const projectModel = readByIdDataBaseModelUseCase.value;
const resultMapper = projectModel.project.pipelines.map((el) => mongoPipelineModelMapper(el));
const updateDataBaseModelUseCase = await new UpdateDataBaseModelUseCase<IProjectInstanceModel, any>( pipelineRealTimeService.setPipelineDependency(
ProjectInstanceDbModel resultMapper,
).call(projectModel); App.staticFilesStoreDir() + projectModel.rootDir + "/",
projectModel._id,
projectModel.rootDir
);
if (updateDataBaseModelUseCase.isFailure()) { pipelineRealTimeService.runPipeline();
return updateDataBaseModelUseCase.forward(); return Result.ok("ok");
} });
pipelineRealTimeService.setPipelineDependency(
projectModel.project.pipelines,
App.staticFilesStoreDir() + projectModel.rootDir + "/",
projectModel._id
);
pipelineRealTimeService.runPipeline();
return Result.ok({ status: "ok" });
} }
} }

View file

@ -18,7 +18,11 @@ export class RealTimePresentation extends CoreHttpController<RealTimeValidationM
url: "realtime", url: "realtime",
databaseModel: null, databaseModel: null,
}); });
super.post(new RunInstancePipelineUseCase().call);
super.get(new PipelineStatusUseCase().call); super.get(new PipelineStatusUseCase().call);
this.subRoutes.push({
method: "POST",
subUrl: "run",
fn: new RunInstancePipelineUseCase(),
});
} }
} }

View file

@ -1,43 +0,0 @@
import { IsArray, IsOptional, IsEnum} from "class-validator";
import { Schema, model } from "mongoose";
export interface ITriggerModel {
_id?: string;
type: string;
value: string[];
}
export const TriggerSchema = new Schema({
type: {
type: String,
require: true,
},
value: {
type: Array,
require: true,
},
});
export const triggerSchema = "Trigger";
export const TriggerDBModel = model<ITriggerModel>(triggerSchema, TriggerSchema);
export enum TriggerType {
PROCESS = "PROCESS",
FILE = "FILE",
}
export class TriggerModel implements ITriggerModel {
@IsOptional()
public _id: string;
@IsEnum(TriggerType)
public type: TriggerType;
@IsArray()
public value: string[];
}
export interface Trigger {
type: TriggerType;
value: string[];
}

View file

@ -1,13 +1,14 @@
import { CrudController } from "../../core/controllers/crud_controller"; import { CrudController } from "../../core/controllers/crud_controller";
import { TriggerDBModel } from "./models/trigger_database_model"; import { TriggerDBModel } from "./models/trigger_database_model";
import { TriggerModelValidationModel as TriggerValidationMode } from "./models/trigger_validation_model"; import { TriggerModelValidationModel } from "./models/trigger_validation_model";
export class TriggerPresentation extends CrudController<TriggerValidationMode, typeof TriggerDBModel> { export class TriggerPresentation extends CrudController<TriggerModelValidationModel, typeof TriggerDBModel> {
constructor() { constructor() {
super({ super({
url: "trigger", url: "trigger",
validationModel: TriggerValidationMode, validationModel: TriggerModelValidationModel,
databaseModel: TriggerDBModel, databaseModel: TriggerDBModel,
}); });
} }
} }
"".isEmpty();

View file

@ -4,9 +4,11 @@ import { SocketSubscriber } from "./core/controllers/socket_controller";
import { extensions } from "./core/extensions/extensions"; import { extensions } from "./core/extensions/extensions";
import { httpRoutes } from "./core/controllers/routes"; import { httpRoutes } from "./core/controllers/routes";
import { pipelineRealTimeService } from "./features/realtime/realtime_presentation"; import { pipelineRealTimeService } from "./features/realtime/realtime_presentation";
import { main } from "./p";
extensions(); extensions();
const socketSubscribers = [new SocketSubscriber(pipelineRealTimeService, "realtime")]; const socketSubscribers = [new SocketSubscriber(pipelineRealTimeService, "realtime")];
new App(httpRoutes, socketSubscribers).listen(); new App(httpRoutes, socketSubscribers).listen();
main();

View file

@ -3,19 +3,23 @@ import { StackService } from "../../src/core/services/stack_service";
import { delay } from "../../src/core/helpers/delay"; import { delay } from "../../src/core/helpers/delay";
import { assert, dirname__ } from "../test"; import { assert, dirname__ } from "../test";
import { mockSimplePipeline } from "../model/mock_pipelines"; import { mockSimplePipeline } from "../model/mock_pipelines";
import { readDirRecursive } from "../../src/core/repository/fs"; import { FileSystemRepository } from "../../src/core/repository/file_system_repository";
import { CreateFolderUseCase } from "../../src/core/usecases/create_folder_usecase";
abstract class IStackServiceTest { abstract class IStackServiceTest {
abstract test(): Promise<boolean>; abstract test(): Promise<boolean>;
} }
class SimpleTestStackServiceTest extends StackService implements IStackServiceTest { class SimpleTestStackServiceTest extends StackService implements IStackServiceTest {
fileSystemRepository: FileSystemRepository;
constructor() { constructor() {
super(mockSimplePipeline, dirname__ + "/context/"); super(mockSimplePipeline, dirname__ + "/context/");
this.fileSystemRepository = new FileSystemRepository();
} }
async test(): Promise<boolean> { async test(): Promise<boolean> {
await this.call(); await this.call();
const testResult = readDirRecursive(this.path).equals(["1.txt", "test.txt"], true); console.log(this.path);
const testResult = this.fileSystemRepository.readDirRecursive(this.path).equals(["1.txt", "test.txt"], true);
await delay(100); await delay(100);
rmSync(this.path + "example/", { recursive: true }); rmSync(this.path + "example/", { recursive: true });
return testResult; return testResult;
@ -24,12 +28,15 @@ class SimpleTestStackServiceTest extends StackService implements IStackServiceTe
export class StackServiceTest { export class StackServiceTest {
dirName: string; dirName: string;
fileSystemRepository: FileSystemRepository;
constructor(dirName: string) { constructor(dirName: string) {
this.dirName = dirName; this.dirName = dirName;
this.fileSystemRepository = new FileSystemRepository();
} }
public async test() { public async test() {
const tests = [new SimpleTestStackServiceTest()]; const tests = [new SimpleTestStackServiceTest()];
await new CreateFolderUseCase().call(this.dirName + "/context/");
for await (const el of tests) { for await (const el of tests) {
assert((await el.test()) === true, el.constructor.name); assert((await el.test()) === true, el.constructor.name);
await delay(3000); await delay(3000);

View file

@ -39,14 +39,14 @@ const unitTest = async () => {
await init(); await init();
await new ExecutorProgramServiceTest(dirname__).test(); await new ExecutorProgramServiceTest(dirname__).test();
await new FilesChangerTest(dirname__).test(); await new FilesChangerTest(dirname__).test();
await new StackServiceTest(dirname__ + "/context/").test(); await new StackServiceTest(dirname__).test();
await new TriggerServiceTest().test(); await new TriggerServiceTest().test();
await new CreateDataBaseModelUseCaseTest().test(); await new CreateDataBaseModelUseCaseTest().test();
await new CreateDataBaseModelUseCaseTest().test(); await new CreateDataBaseModelUseCaseTest().test();
await new DeleteDataBaseModelUseCaseTest().test(); await new DeleteDataBaseModelUseCaseTest().test();
await new ReadDataBaseModelUseCaseTest().test(); await new ReadDataBaseModelUseCaseTest().test();
await new UpdateDataBaseModelUseCaseTest().test(); await new UpdateDataBaseModelUseCaseTest().test();
// await new PipelineRealTimeServiceTest().test()
for await (const usecase of tests) { for await (const usecase of tests) {
testCore.assert(await new usecase().test(), usecase.name); testCore.assert(await new usecase().test(), usecase.name);
} }
@ -54,7 +54,6 @@ const unitTest = async () => {
const presentationCrudControllers = [new TriggerPresentation()]; const presentationCrudControllers = [new TriggerPresentation()];
const e2eTest = async () => { const e2eTest = async () => {
const app = new App(httpRoutes, [], Environment.E2E_TEST); const app = new App(httpRoutes, [], Environment.E2E_TEST);
app.listen();
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
app.on(async (e) => { app.on(async (e) => {
if (e === ServerStatus.finished) { if (e === ServerStatus.finished) {

View file

@ -13,6 +13,8 @@
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"@types/socket.io-client": "^3.0.0", "@types/socket.io-client": "^3.0.0",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"formik-antd": "^2.0.4", "formik-antd": "^2.0.4",
"i18next": "^23.6.0", "i18next": "^23.6.0",
"mobx": "^6.10.0", "mobx": "^6.10.0",
@ -24,9 +26,13 @@
"react-infinite-scroll-component": "^6.1.0", "react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^6.18.0", "react-router-dom": "^6.18.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"reflect-metadata": "^0.1.13",
"sass": "^1.66.1", "sass": "^1.66.1",
"socket.io-client": "^4.7.2", "socket.io-client": "^4.7.2",
"three": "^0.159.0", "three": "^0.159.0",
"three-stdlib": "^2.28.9",
"three-transform-controls": "^1.0.4",
"ts-pattern": "^5.0.6",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"urdf-loader": "^0.12.1", "urdf-loader": "^0.12.1",
"uuid": "^9.0.1", "uuid": "^9.0.1",

View file

@ -2,7 +2,8 @@ import { ArrayExtensions } from "./array";
import { MapExtensions } from "./map"; import { MapExtensions } from "./map";
import { StringExtensions } from "./string"; import { StringExtensions } from "./string";
export type CallBackFunction = <T>(value: T) => void; export type CallBackVoidFunction = <T>(value: T) => void;
export type CallBackStringVoidFunction = (value: string) => void;
declare global { declare global {
interface Array<T> { interface Array<T> {
@ -15,9 +16,10 @@ declare global {
} }
interface String { interface String {
isEmpty(): boolean; isEmpty(): boolean;
isNotEmpty(): boolean;
} }
interface Map<K, V> { interface Map<K, V> {
addValueOrMakeCallback(key: K, value: V, callBack: CallBackFunction): void; addValueOrMakeCallback(key: K, value: V, callBack: CallBackVoidFunction): void;
} }
} }
export const extensions = () => { export const extensions = () => {

View file

@ -5,4 +5,10 @@ export const StringExtensions = () => {
return this.length === 0; return this.length === 0;
}; };
} }
if ("".isNotEmpty === undefined) {
// eslint-disable-next-line no-extend-native
String.prototype.isNotEmpty = function () {
return this.length !== 0;
};
}
}; };

View file

@ -0,0 +1,82 @@
export type Options<Result> = {
isImmediate?: boolean;
maxWait?: number;
callback?: (data: Result) => void;
};
export interface DebouncedFunction<Args extends any[], F extends (...args: Args) => any> {
(this: ThisParameterType<F>, ...args: Args & Parameters<F>): Promise<ReturnType<F>>;
cancel: (reason?: any) => void;
}
interface DebouncedPromise<FunctionReturn> {
resolve: (result: FunctionReturn) => void;
reject: (reason?: any) => void;
}
export function debounce<Args extends any[], F extends (...args: Args) => any>(
func: F,
waitMilliseconds = 50,
options: Options<ReturnType<F>> = {}
): DebouncedFunction<Args, F> {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const isImmediate = options.isImmediate ?? false;
const callback = options.callback ?? false;
const maxWait = options.maxWait;
let lastInvokeTime = Date.now();
let promises: DebouncedPromise<ReturnType<F>>[] = [];
function nextInvokeTimeout() {
if (maxWait !== undefined) {
const timeSinceLastInvocation = Date.now() - lastInvokeTime;
if (timeSinceLastInvocation + waitMilliseconds >= maxWait) {
return maxWait - timeSinceLastInvocation;
}
}
return waitMilliseconds;
}
const debouncedFunction = function (this: ThisParameterType<F>, ...args: Parameters<F>) {
const context = this;
return new Promise<ReturnType<F>>((resolve, reject) => {
const invokeFunction = function () {
timeoutId = undefined;
lastInvokeTime = Date.now();
if (!isImmediate) {
const result = func.apply(context, args);
callback && callback(result);
promises.forEach(({ resolve }) => resolve(result));
promises = [];
}
};
const shouldCallNow = isImmediate && timeoutId === undefined;
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(invokeFunction, nextInvokeTimeout());
if (shouldCallNow) {
const result = func.apply(context, args);
callback && callback(result);
return resolve(result);
}
promises.push({ resolve, reject });
});
};
debouncedFunction.cancel = function (reason?: any) {
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
promises.forEach(({ reject }) => reject(reason));
promises = [];
};
return debouncedFunction;
}

View file

@ -0,0 +1,29 @@
export const throttle = <R, A extends any[]>(
fn: (...args: A) => R,
delay: number
): [(...args: A) => R | undefined, () => void] => {
let wait = false;
let timeout: undefined | number;
let cancelled = false;
return [
(...args: A) => {
if (cancelled) return undefined;
if (wait) return undefined;
const val = fn(...args);
wait = true;
timeout = window.setTimeout(() => {
wait = false;
}, delay);
return val;
},
() => {
cancelled = true;
clearTimeout(timeout);
},
];
};

View file

@ -1,7 +1,6 @@
export interface ActivePipeline { export interface ActivePipeline {
pipelineIsRunning: boolean; pipelineIsRunning: boolean;
projectUUID?: string | null; projectId?: string | null;
lastProcessCompleteCount: number | null; lastProcessCompleteCount: number | null;
error: any; error: any;
} }

View file

@ -0,0 +1,6 @@
export class UiBaseError {
text: string;
constructor(text: string) {
this.text = text;
}
}

View file

@ -1,189 +0,0 @@
import {
DirectionalLight,
Object3D,
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
Vector3,
MeshBasicMaterial,
Mesh,
BoxGeometry,
Object3DEventMap,
Box3,
Sphere,
LineBasicMaterial,
EdgesGeometry,
Raycaster,
LineSegments,
Vector2,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { BaseSceneItemModel, StaticAssetItemModel } from "../../features/scene_manager/scene_manager_store";
import { TypedEvent } from "../helper/typed_event";
import { Result } from "../helper/result";
interface IEmissiveCache {
status: boolean;
object3d: Object3D<Object3DEventMap>;
}
export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
scene = new Scene();
camera: PerspectiveCamera;
webGlRender: WebGLRenderer;
htmlCanvasRef: HTMLCanvasElement;
objectEmissive = new Map<string, IEmissiveCache>();
constructor(htmlCanvasRef: HTMLCanvasElement) {
super();
const renderer = new WebGLRenderer({
canvas: htmlCanvasRef as HTMLCanvasElement,
antialias: true,
alpha: true,
});
const aspectCamera = window.outerWidth / window.outerHeight;
this.camera = new PerspectiveCamera(800, aspectCamera, 0.1, 10000);
this.webGlRender = renderer;
this.htmlCanvasRef = htmlCanvasRef;
this.init();
}
setRayCastAndGetFirstObject(vector: Vector2): Result<void, string> {
const raycaster = new Raycaster();
raycaster.setFromCamera(vector, this.camera);
const intersects = raycaster.intersectObjects(this.scene.children);
if (intersects.length > 0) {
return Result.ok(intersects[0].object.name);
}
return Result.error(undefined);
}
addCube(num: number, translateTo: string = "X") {
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({ color: 0x00ff00 });
const cube = new Mesh(geometry, material);
cube.name = "Cube" + String(num);
eval(`cube.translate${translateTo}(${num * 10})`);
this.scene.add(cube);
}
init() {
const directionalLight = new DirectionalLight(0xffffff, 0.2);
directionalLight.castShadow = true;
directionalLight.position.set(-1, 2, 4);
this.scene.add(directionalLight);
const ambientLight = new AmbientLight(0xffffff, 0.7);
this.scene.add(ambientLight);
this.addCube(1);
this.addCube(2);
this.addCube(3);
this.addCube(4);
const onResize = () => {
this.camera.aspect = window.outerWidth / window.outerHeight;
this.camera.updateProjectionMatrix();
this.webGlRender.setSize(window.outerWidth, window.outerHeight);
};
window.addEventListener("resize", onResize, false);
new OrbitControls(this.camera, this.htmlCanvasRef);
}
render() {
this.webGlRender.setSize(window.outerWidth, window.outerHeight);
this.webGlRender.setAnimationLoop(() => {
this.webGlRender.render(this.scene, this.camera);
});
}
getAllSceneModels(): BaseSceneItemModel[] {
return this.getAllSceneNameModels().map((e) => new StaticAssetItemModel(e));
}
getAllSceneNameModels(): string[] {
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
}
getObjectsAtName(name: string): Object3D<Object3DEventMap> {
return this.scene.children.filter((el) => el.name === name)[0];
}
loader(urls: string[], callBack: Function) {}
fitCameraToCenteredObject(objects: string[], offset = 4) {
// https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/
const boundingBox = new Box3().setFromPoints(
objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position)
);
var size = new Vector3();
boundingBox.getSize(size);
const fov = this.camera.fov * (Math.PI / 180);
const fovh = 2 * Math.atan(Math.tan(fov / 2) * this.camera.aspect);
let dx = size.z / 2 + Math.abs(size.x / 2 / Math.tan(fovh / 2));
let dy = size.z / 2 + Math.abs(size.y / 2 / Math.tan(fov / 2));
let cameraZ = Math.max(dx, dy);
if (offset !== undefined && offset !== 0) cameraZ *= offset;
this.camera.position.set(0, 0, cameraZ);
const minZ = boundingBox.min.z;
const cameraToFarEdge = minZ < 0 ? -minZ + cameraZ : cameraZ - minZ;
this.camera.far = cameraToFarEdge * 3;
this.camera.updateProjectionMatrix();
let orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
orbitControls.maxDistance = cameraToFarEdge * 2;
new OrbitControls(this.camera, this.htmlCanvasRef);
}
switchObjectEmissive(name: string) {
const mesh = this.getObjectsAtName(name);
const result = this.objectEmissive.get(mesh.name);
if (result?.status) {
this.scene.remove(mesh);
this.scene.add(result.object3d);
this.objectEmissive.set(mesh.name, {
status: false,
object3d: mesh,
});
} else {
this.objectEmissive.set(mesh.name, {
status: true,
object3d: mesh,
});
if (mesh instanceof Mesh) {
const newMesh = new LineSegments(mesh.geometry, new LineBasicMaterial({ color: 0x000000 }));
newMesh.name = mesh.name;
newMesh.translateX(mesh.position.x);
newMesh.translateY(mesh.position.y);
newMesh.translateZ(mesh.position.z);
this.scene.remove(mesh);
this.scene.add(newMesh);
}
}
}
fitSelectedObjectToScreen(objects: string[]) {
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
let boundBox = new Box3().setFromPoints(objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position));
let boundSphere = boundBox.getBoundingSphere(new Sphere());
let vFoV = this.camera.getEffectiveFOV();
let hFoV = this.camera.fov * this.camera.aspect;
let FoV = Math.min(vFoV, hFoV);
let FoV2 = FoV / 2;
let dir = new Vector3();
this.camera.getWorldDirection(dir);
let bsWorld = boundSphere.center.clone();
let th = (FoV2 * Math.PI) / 180.0;
let sina = Math.sin(th);
let R = boundSphere.radius;
let FL = R / sina;
let cameraDir = new Vector3();
let cameraOffs = cameraDir.clone();
cameraOffs.multiplyScalar(-FL);
let newCameraPos = bsWorld.clone().add(cameraOffs);
this.camera.translateX(newCameraPos.x);
this.camera.translateY(newCameraPos.y);
this.camera.translateZ(newCameraPos.z);
this.camera.lookAt(bsWorld);
new OrbitControls(this.camera, this.htmlCanvasRef);
}
}

View file

@ -0,0 +1,401 @@
import {
DirectionalLight,
Object3D,
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
Vector3,
Mesh,
Object3DEventMap,
Box3,
Sphere,
LineBasicMaterial,
Intersection,
Raycaster,
LineSegments,
Vector2,
Color,
GridHelper,
CameraHelper,
Quaternion,
} from "three";
import { TypedEvent } from "../helper/typed_event";
import { Result } from "../helper/result";
import { GLTFLoader, OrbitControls, TransformControls, OBJLoader, STLLoader, ColladaLoader } from "three-stdlib";
import {
BaseSceneItemModel,
CameraViewModel,
StaticAssetItemModel,
} from "../../features/scene_manager/model/scene_assets";
import { SceneMode } from "../../features/scene_manager/model/scene_view";
import { throttle } from "../helper/throttle";
import {
InstanceRgbCamera,
RobossemblerAssets,
SceneSimpleObject,
} from "../../features/scene_manager/model/robossembler_assets";
export enum UserData {
selectedObject = "selected_object",
cameraInitialization = "camera_initialization",
}
interface IEventDraggingChange {
target: null;
type: string;
value: boolean;
}
interface IEmissiveCache {
status: boolean;
object3d: Object3D<Object3DEventMap>;
}
export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
scene = new Scene();
camera: PerspectiveCamera;
webGlRender: WebGLRenderer;
htmlCanvasRef: HTMLCanvasElement;
objectEmissive = new Map<string, IEmissiveCache>();
transformControls: TransformControls;
orbitControls: OrbitControls;
htmlSceneWidth: number;
htmlSceneHeight: number;
objLoader = new OBJLoader();
glbLoader = new GLTFLoader();
daeLoader = new ColladaLoader();
stlLoader = new STLLoader();
watcherSceneEditorObject: Function;
constructor(htmlCanvasRef: HTMLCanvasElement, watcherSceneEditorObject: Function) {
super();
this.htmlSceneWidth = window.innerWidth;
this.htmlSceneHeight = window.innerHeight;
this.watcherSceneEditorObject = watcherSceneEditorObject;
const renderer = new WebGLRenderer({
canvas: htmlCanvasRef as HTMLCanvasElement,
antialias: true,
alpha: true,
});
const aspectCamera = this.htmlSceneWidth / this.htmlSceneHeight;
this.camera = new PerspectiveCamera(800, aspectCamera, 0.1, 10000);
this.camera.position.set(60, 20, 10);
this.webGlRender = renderer;
this.htmlCanvasRef = htmlCanvasRef;
this.transformControls = new TransformControls(this.camera, htmlCanvasRef);
this.scene.add(this.transformControls);
this.orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
this.scene.background = new Color("black");
this.init();
}
deleteSceneItem(item: BaseSceneItemModel) {
const updateScene = this.scene;
updateScene.children = item.deleteToScene(updateScene);
}
loadInstances(robossemblerAssets: RobossemblerAssets) {
robossemblerAssets.instances.forEach(async (el) => {
if (el instanceof InstanceRgbCamera) {
const cameraModel = CameraViewModel.fromInstanceRgbCamera(el);
cameraModel.mapPerspectiveCamera(this.htmlSceneWidth, this.htmlSceneHeight).forEach((el) => this.scene.add(el));
this.emit(cameraModel);
}
if (el instanceof SceneSimpleObject) {
const asset = robossemblerAssets.getAssetAtInstance(el.instanceAt as string);
this.loader(
asset.meshPath,
() => {},
asset.name,
new Vector3(el.position.x, el.position.y, el.position.z),
new Quaternion(el.quaternion[0], el.quaternion[1], el.quaternion[2], el.quaternion[3])
);
}
});
}
setTransformMode(mode?: SceneMode) {
switch (mode) {
case undefined:
this.transformControls.detach();
this.transformControls.dispose();
break;
case SceneMode.MOVING:
this.transformControls.setMode("translate");
break;
case SceneMode.ROTATE:
this.transformControls.setMode("rotate");
break;
}
}
addSceneCamera(cameraModel: CameraViewModel) {
cameraModel.mapPerspectiveCamera(this.htmlSceneWidth, this.htmlSceneHeight).forEach((el) => this.scene.add(el));
}
disposeTransformControlsMode() {
this.transformControls.detach();
}
setRayCastAndGetFirstObjectName(vector: Vector2): Result<void, string> {
this.scene.add(this.transformControls);
const raycaster = new Raycaster();
raycaster.setFromCamera(vector, this.camera);
const intersects = raycaster.intersectObjects(this.scene.children);
if (intersects.length > 0) {
return Result.ok(intersects[0].object.name);
}
return Result.error(undefined);
}
setTransformControlsAttach(object: Object3D<Object3DEventMap>) {
if (object instanceof CameraHelper) {
this.transformControls.attach(object.camera);
return;
}
this.transformControls.attach(object);
}
setRayCastAndGetFirstObject(vector: Vector2): Result<void, Object3D<Object3DEventMap>> {
try {
const result = this.setRayCast(vector);
return result.fold(
(intersects) => {
const result = intersects.find((element) => element.object.userData[UserData.selectedObject] !== undefined);
if (result === undefined) {
return Result.error(undefined);
}
return Result.ok(result.object);
},
(_error) => {
return Result.error(undefined);
}
);
} catch (error) {
return Result.error(undefined);
}
}
setRayCast(vector: Vector2): Result<void, Intersection<Object3D<Object3DEventMap>>[]> {
const raycaster = new Raycaster();
raycaster.setFromCamera(vector, this.camera);
const intersects = raycaster.intersectObjects(this.scene.children);
if (intersects.length > 0) {
return Result.ok(intersects);
}
return Result.error(undefined);
}
setRayCastAndGetFirstObjectAndPointToObject(vector: Vector2): Result<void, Vector3> {
this.setRayCast(vector).map((intersects) => {
if (intersects.length > 0) {
return Result.ok(intersects[0].point);
}
});
return Result.error(undefined);
}
light() {
const directionalLight = new DirectionalLight(0xffffff, 0.2);
directionalLight.castShadow = true;
directionalLight.position.set(-1, 2, 4);
this.scene.add(directionalLight);
const ambientLight = new AmbientLight(0xffffff, 0.7);
this.scene.add(ambientLight);
}
addListeners() {
window.addEventListener(
"resize",
() => {
this.camera.aspect = this.htmlSceneWidth / this.htmlSceneHeight;
this.camera.updateProjectionMatrix();
this.webGlRender.setSize(this.htmlSceneWidth, this.htmlSceneHeight);
},
false
);
this.transformControls.addEventListener("dragging-changed", (event) => {
const e = event as unknown as IEventDraggingChange;
this.orbitControls.enabled = !e.value;
});
this.transformControls.addEventListener("objectChange", (event) => {
//@ts-expect-error
const sceneObject = event.target.object;
//TODO:(IDONTSUDO) Trotting doesn't work, need to figure out why
const fn = () => this.watcherSceneEditorObject(sceneObject);
const [throttleFn] = throttle(fn, 1000);
throttleFn();
});
}
init() {
this.light();
this.addListeners();
const floor = new GridHelper(100, 100, 0x888888, 0x444444);
floor.userData = {};
floor.userData[UserData.cameraInitialization] = true;
this.scene.add(floor);
}
render() {
this.webGlRender.setSize(this.htmlSceneWidth, this.htmlSceneHeight);
this.webGlRender.setAnimationLoop(() => {
this.webGlRender.render(this.scene, this.camera);
});
}
getAllSceneModels(): BaseSceneItemModel[] {
return this.getAllSceneNameModels().map(
(name) =>
new StaticAssetItemModel(name, this.getObjectsAtName(name).position, this.getObjectsAtName(name).quaternion)
);
}
getAllSceneNameModels(): string[] {
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
}
getObjectsAtName(name: string): Object3D<Object3DEventMap> {
return this.scene.children.filter((el) => el.name === name)[0];
}
loader(url: string, callBack: Function, name: string, position?: Vector3, quaternion?: Quaternion) {
const ext = url.split(/\./g).pop()!.toLowerCase();
switch (ext) {
case "gltf":
case "glb":
this.glbLoader.load(
url,
(result) => {},
(err) => {}
);
break;
case "obj":
this.objLoader.load(
url,
(result) => {
result.userData[UserData.selectedObject] = true;
result.children.forEach((el) => {
el.userData[UserData.selectedObject] = true;
el.name = name;
if (position) el.position.copy(position);
if (quaternion) el.quaternion.copy(quaternion);
this.emit(new StaticAssetItemModel(el.name, el.position, el.quaternion));
this.scene.add(el);
});
},
(err) => {}
);
break;
case "dae":
this.daeLoader.load(
url,
(result) => {},
(err) => {}
);
break;
case "stl":
this.stlLoader.load(
url,
(result) => {},
(err) => {}
);
break;
}
}
fitCameraToCenteredObject(objects: string[], offset = 4) {
const boundingBox = new Box3().setFromPoints(
objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position)
);
var size = new Vector3();
boundingBox.getSize(size);
const fov = this.camera.fov * (Math.PI / 180);
const fovh = 2 * Math.atan(Math.tan(fov / 2) * this.camera.aspect);
let dx = size.z / 2 + Math.abs(size.x / 2 / Math.tan(fovh / 2));
let dy = size.z / 2 + Math.abs(size.y / 2 / Math.tan(fov / 2));
let cameraZ = Math.max(dx, dy);
if (offset !== undefined && offset !== 0) cameraZ *= offset;
this.camera.position.set(0, 0, cameraZ);
const minZ = boundingBox.min.z;
const cameraToFarEdge = minZ < 0 ? -minZ + cameraZ : cameraZ - minZ;
this.camera.far = cameraToFarEdge * 3;
this.camera.updateProjectionMatrix();
let orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
orbitControls.maxDistance = cameraToFarEdge * 2;
this.orbitControls = orbitControls;
}
switchObjectEmissive(name: string) {
const mesh = this.getObjectsAtName(name);
const result = this.objectEmissive.get(mesh.name);
if (result?.status) {
this.scene.remove(mesh);
this.scene.add(result.object3d);
this.objectEmissive.set(mesh.name, {
status: false,
object3d: mesh,
});
} else {
this.objectEmissive.set(mesh.name, {
status: true,
object3d: mesh,
});
if (mesh instanceof Mesh) {
const newMesh = new LineSegments(mesh.geometry, new LineBasicMaterial({ color: 0x000000 }));
newMesh.name = mesh.name;
newMesh.position.copy(mesh.position);
this.scene.remove(mesh);
this.scene.add(newMesh);
}
}
}
fitSelectedObjectToScreen(objects: string[]) {
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
let boundBox = new Box3().setFromPoints(objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position));
let boundSphere = boundBox.getBoundingSphere(new Sphere());
let vFoV = this.camera.getEffectiveFOV();
let hFoV = this.camera.fov * this.camera.aspect;
let FoV = Math.min(vFoV, hFoV);
let FoV2 = FoV / 2;
let dir = new Vector3();
this.camera.getWorldDirection(dir);
let bsWorld = boundSphere.center.clone();
let th = (FoV2 * Math.PI) / 180.0;
let sina = Math.sin(th);
let R = boundSphere.radius;
let FL = R / sina;
let cameraDir = new Vector3();
let cameraOffs = cameraDir.clone();
cameraOffs.multiplyScalar(-FL);
let newCameraPos = bsWorld.clone().add(cameraOffs);
this.camera.position.copy(newCameraPos);
this.camera.lookAt(bsWorld);
this.orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
}
}

View file

@ -1,3 +1,4 @@
import { ClassConstructor, plainToInstance } from "class-transformer";
import { Result } from "../helper/result"; import { Result } from "../helper/result";
export enum HttpMethod { export enum HttpMethod {
@ -16,12 +17,22 @@ export class HttpError extends Error {
export class HttpRepository { export class HttpRepository {
private server = "http://localhost:4001"; private server = "http://localhost:4001";
public async _formDataRequest<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
let formData = new FormData();
formData.append("file", data);
public async jsonRequest<T>( const reqInit = {
method: HttpMethod, body: formData,
url: string, method: method,
data?: any };
): Promise<Result<HttpError, T>> {
const response = await fetch(this.server + url, reqInit);
if (response.status !== 200) {
throw Result.error(new Error(await response.json()));
}
return Result.ok(response.text as T);
}
public async _jsonRequest<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
try { try {
const reqInit = { const reqInit = {
body: data, body: data,
@ -37,28 +48,50 @@ export class HttpRepository {
return Result.error(new HttpError(this.server + url, response.status)); return Result.error(new HttpError(this.server + url, response.status));
} }
return Result.ok(await response.json()); return Result.ok(await response.json());
} catch (error) { } catch (error) {
return Result.error(new HttpError(error, 0)); return Result.error(new HttpError(error, 0));
} }
} }
public async request<T>( public async _request<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
method: HttpMethod,
url: string,
data?: any
): Promise<T> {
const reqInit = { const reqInit = {
body: data, body: data,
method: method, method: method,
}; };
if (data !== undefined) { if (data !== undefined) {
reqInit["body"] = data; reqInit["body"] = data;
} }
const response = await fetch(this.server + url, reqInit); const response = await fetch(this.server + url, reqInit);
if (response.status !== 200) { if (response.status !== 200) {
throw new Error(await response.json()); throw Result.error(new Error(await response.json()));
}
return Result.ok(response.text as T);
}
public async _jsonToClassInstanceRequest<T>(
method: HttpMethod,
url: string,
instance: ClassConstructor<T>,
data?: any
) {
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));
}
return Result.ok(plainToInstance(instance, await response.json()) as T);
} catch (error) {
return Result.error(new HttpError(error, 0));
} }
return response.json();
} }
} }

View file

@ -21,18 +21,17 @@ import {
CreateProcessScreen, CreateProcessScreen,
CreateProcessScreenPath, CreateProcessScreenPath,
} from "../../features/create_process/presentation/create_process_screen"; } from "../../features/create_process/presentation/create_process_screen";
import { ProjectRepository } from "../../features/all_projects/data/project_repository";
import { import {
CreateProjectInstancePath, CreateProjectInstancePath,
CreateProjectInstanceScreen, CreateProjectInstanceScreen,
} from "../../features/create_project_instance/create_project_instance"; } from "../../features/create_project_instance/create_project_instance";
import { SceneManger, SceneManagerPath } from "../../features/scene_manager/presentation/scene_manager";
const idURL = ":id"; const idURL = ":id";
export const router = createBrowserRouter([ export const router = createBrowserRouter([
{ {
path: AllProjectScreenPath, path: AllProjectScreenPath,
loader: new ProjectRepository().loader,
element: <AllProjectScreen />, element: <AllProjectScreen />,
}, },
{ {
@ -63,4 +62,8 @@ export const router = createBrowserRouter([
path: CreateProjectInstancePath + idURL, path: CreateProjectInstancePath + idURL,
element: <CreateProjectInstanceScreen />, element: <CreateProjectInstanceScreen />,
}, },
{
path: SceneManagerPath + idURL,
element: <SceneManger />,
},
]); ]);

View file

@ -1,22 +1,50 @@
// TODO(IDONTSUDO): нужно переписать все запросы под BaseStore // TODO(IDONTSUDO): нужно переписать все запросы под BaseStore
import { NavigateFunction } from "react-router-dom";
import { Result } from "../helper/result"; import { Result } from "../helper/result";
import { UiBaseError } from "../model/ui_base_error";
import { HttpError } from "../repository/http_repository";
export class BaseStore { export type CoreError = HttpError | Error;
export abstract class UiLoader {
isLoading = false; isLoading = false;
isError = false; async httpHelper<T>(callBack: Promise<Result<any, T>>) {
async loadingHelper<T>(callBack: Promise<Result<any, T>>) {
this.isLoading = true; this.isLoading = true;
const result = await callBack; const result = await callBack;
if (result.isFailure()) { if (result.isFailure()) {
this.isError = true;
this.isLoading = false; this.isLoading = false;
this.errorHandingStrategy(result.error);
return result.forward(); return result.forward();
} }
this.isLoading = false; this.isLoading = false;
return result; return result;
} }
abstract errorHandingStrategy: (error?: any) => void;
mapOk = async <T>(property: string, callBack: Promise<Result<CoreError, T>>) => {
return (
(await this.httpHelper(callBack))
// eslint-disable-next-line array-callback-return
.map((el) => {
// @ts-ignore
this[property] = el;
})
);
};
}
export class SimpleErrorState extends UiLoader {
errorHandingStrategy = () => {
this.isError = true;
};
isError = false;
}
export abstract class UiErrorState<T> extends UiLoader {
abstract errorHandingStrategy: (error: T) => void;
abstract init(navigate?: NavigateFunction): Promise<any>;
dispose() {}
errors: UiBaseError[] = [];
} }

View file

@ -1,27 +1,16 @@
import { redirect } from "react-router-dom"; import { ActivePipeline } from "../../../core/model/active_pipeline";
import { ActivePipeline } from "../../../core/model/active_pipiline"; import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { PipelineInstanceScreenPath } from "../../pipeline_instance_main_screen/pipeline_instance_screen";
import { IProjectModel } from "../model/project_model"; import { IProjectModel } from "../model/project_model";
export class ProjectRepository extends HttpRepository { export class ProjectRepository extends HttpRepository {
async getAllProject() { async getAllProject() {
return this.jsonRequest<IProjectModel[]>(HttpMethod.GET, "/project"); return this._jsonRequest<IProjectModel[]>(HttpMethod.GET, "/project_instance");
} }
async getActivePipeline() { async getActivePipeline() {
return this.jsonRequest<ActivePipeline>(HttpMethod.GET, "/realtime"); return this._jsonRequest<ActivePipeline>(HttpMethod.GET, "/realtime");
}
async setActivePipeline(id: string) {
return this._jsonRequest(HttpMethod.POST, `/project_instance/set/active/project?id=${id}`);
} }
loader = async () => {
const result = await this.getActivePipeline();
// if (result.isSuccess() && result.value.projectUUID !== null) {
// return redirect(PipelineInstanceScreenPath + result.value.projectUUID);
// }
return null;
};
} }

View file

@ -4,13 +4,18 @@ import { ProjectRepository } from "../data/project_repository";
import { LoadPage } from "../../../core/ui/pages/load_page"; import { LoadPage } from "../../../core/ui/pages/load_page";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { SelectProjectScreenPath } from "../../select_project/presentation/select_project"; import { SelectProjectScreenPath } from "../../select_project/presentation/select_project";
import { useNavigate } from "react-router-dom";
import { Button } from "antd";
import { PipelineInstanceScreenPath } from "../../pipeline_instance_main_screen/pipeline_instance_screen";
export const AllProjectScreenPath = "/"; export const AllProjectScreenPath = "/";
export const AllProjectScreen: React.FunctionComponent = observer(() => { export const AllProjectScreen: React.FunctionComponent = observer(() => {
const [allProjectStore] = React.useState( const [allProjectStore] = React.useState(() => new AllProjectStore(new ProjectRepository()));
() => new AllProjectStore(new ProjectRepository()) const navigate = useNavigate();
);
React.useEffect(() => {
allProjectStore.init();
}, [allProjectStore]);
return ( return (
<> <>
@ -23,8 +28,33 @@ export const AllProjectScreen: React.FunctionComponent = observer(() => {
isLoading={allProjectStore.isLoading} isLoading={allProjectStore.isLoading}
children={ children={
<div> <div>
<h1>Projects</h1>
<h5 style={{ backgroundColor: "ButtonShadow" }}>
<Button
onClick={() => {
navigate(PipelineInstanceScreenPath + allProjectStore.activePipeline?.projectId ?? "");
}}
>
Project main panel
</Button>
{allProjectStore.activePipeline?.projectId ?? "loading"}
</h5>
{allProjectStore.projectsModels?.map((el) => { {allProjectStore.projectsModels?.map((el) => {
return <div>{el.description}</div>; return (
<div style={{ margin: "10px", backgroundColor: "Highlight" }}>
<Button
onClick={() => {
allProjectStore.setPipelineActive(el._id ?? "").then(() => {
allProjectStore.init();
navigate(PipelineInstanceScreenPath + el._id ?? "");
});
}}
>
set active project
</Button>
<div style={{ margin: "10px", display: "contents" }}> {el.description}</div>
</div>
);
})} })}
</div> </div>
} }

View file

@ -1,24 +1,49 @@
import makeAutoObservable from "mobx-store-inheritance"; import makeAutoObservable from "mobx-store-inheritance";
import { ProjectRepository } from "../data/project_repository"; import { ProjectRepository } from "../data/project_repository";
import { IProjectModel } from "../model/project_model"; import { IProjectModel } from "../model/project_model";
import { BaseStore } from "../../../core/store/base_store"; import { SimpleErrorState } from "../../../core/store/base_store";
import { ActivePipeline } from "../../../core/model/active_pipeline";
export class AllProjectStore extends BaseStore { interface IProjectView {
isActive: boolean;
description: string;
id: string;
}
export class ProjectView {
isActive: boolean;
description: string;
id: string;
constructor(view: IProjectView) {
this.isActive = view.isActive;
this.description = view.description;
this.id = view.id;
}
}
export class AllProjectStore extends SimpleErrorState {
projectsModels?: IProjectModel[]; projectsModels?: IProjectModel[];
repository: ProjectRepository; repository: ProjectRepository;
redirect = false; activePipeline?: ActivePipeline;
constructor(repository: ProjectRepository) { constructor(repository: ProjectRepository) {
super(); super();
this.repository = repository; this.repository = repository;
makeAutoObservable(this); makeAutoObservable(this);
} }
async getProjects(): Promise<void> {
async getProjects() { await this.mapOk<IProjectModel[]>("projectsModels", this.repository.getAllProject());
const result = await this.loadingHelper(this.repository.getAllProject());
if (result.isSuccess()) {
this.projectsModels = result.value;
}
} }
async getActiveProject(): Promise<void> {
await this.mapOk<ActivePipeline>("activePipeline", this.repository.getActivePipeline());
}
async init() {
await Promise.all([this.getProjects(), this.getActiveProject()]);
await this.projectViewGenerate();
}
projectViewGenerate() {
this.projectsModels = this.projectsModels?.filter((el) => el._id !== this.activePipeline?.projectId);
}
async setPipelineActive(id: string) {
await this.httpHelper(this.repository.setActivePipeline(id));
}
} }

View file

@ -1,26 +1,18 @@
import { import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { ITriggerModel } from "../../../core/model/trigger_model"; import { ITriggerModel } from "../../../core/model/trigger_model";
import { Result } from "../../../core/helper/result"; import { Result } from "../../../core/helper/result";
import { IProcess } from "../../create_process/model/process_model"; import { IProcess } from "../../create_process/model/process_model";
import { PipelineModelDataBase } from "../model/pipeline_model"; import { PipelineModelDataBase } from "../model/pipeline_model";
export class CreatePipelineRepository extends HttpRepository { export class CreatePipelineRepository extends HttpRepository {
async savePipeline( async savePipeline(model: PipelineModelDataBase): Promise<Result<Error, any>> {
model: PipelineModelDataBase return await this._jsonRequest(HttpMethod.POST, `/pipeline`, model);
): Promise<Result<Error, any>> {
return await this.jsonRequest(HttpMethod.POST, `/pipeline`, model);
} }
async getTriggers(page = 1): Promise<Result<Error, ITriggerModel[]>> { async getTriggers(page = 1): Promise<Result<Error, ITriggerModel[]>> {
return await this.jsonRequest(HttpMethod.GET, `/trigger?${page}`); return await this._jsonRequest(HttpMethod.GET, `/trigger?${page}`);
} }
async getProcessed(page = 1): Promise<Result<Error, IProcess[]>> { async getProcessed(page = 1): Promise<Result<Error, IProcess[]>> {
return await this.jsonRequest<IProcess[]>( return await this._jsonRequest<IProcess[]>(HttpMethod.GET, `/process?${page}`);
HttpMethod.GET,
`/process?${page}`
);
} }
} }

View file

@ -1,15 +1,20 @@
import * as React from "react"; import * as React from "react";
import { Row, Button } from "antd"; import { Row, Button } from "antd";
import { LoadPage } from "../../../core/ui/pages/load_page"; import { LoadPage } from "../../../core/ui/pages/load_page";
import { createPipelineStore } from "./create_pipeline_store";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Icon, List } from "../../../core/ui/list/list"; import { Icon, List } from "../../../core/ui/list/list";
import { CreateTriggerScreenPath } from "../../create_trigger/presentation/create_trigger_screen"; import { CreateTriggerScreenPath } from "../../create_trigger/presentation/create_trigger_screen";
import { CreateProcessScreenPath } from "../../create_process/presentation/create_process_screen"; import { CreateProcessScreenPath } from "../../create_process/presentation/create_process_screen";
import { CreatePipelineStore } from "./create_pipeline_store";
import { CreatePipelineRepository } from "../data/create_pipeline_repository";
export const CreatePipelineScreenPath = "/create_pipeline"; export const CreatePipelineScreenPath = "/create_pipeline";
export const CreatePipelineScreen: React.FunctionComponent = observer(() => { export const CreatePipelineScreen: React.FunctionComponent = observer(() => {
const [createPipelineStore] = React.useState(() => new CreatePipelineStore(new CreatePipelineRepository()));
React.useEffect(() => {}, [createPipelineStore]);
return ( return (
<> <>
<LoadPage <LoadPage
@ -29,9 +34,7 @@ export const CreatePipelineScreen: React.FunctionComponent = observer(() => {
icon={Icon.add} icon={Icon.add}
/> />
<div style={{ flexGrow: "1" }}> <div style={{ flexGrow: "1" }}>
<Button onClick={() => createPipelineStore.createPipeline()}> <Button onClick={() => createPipelineStore.createPipeline()}>Save result</Button>
Save result
</Button>
<List <List
headers="new pipeline" headers="new pipeline"
values={createPipelineStore.pipelineViewModels} values={createPipelineStore.pipelineViewModels}

View file

@ -3,7 +3,7 @@ import { CreatePipelineRepository } from "../data/create_pipeline_repository";
import { ITriggerModel } from "../../../core/model/trigger_model"; import { ITriggerModel } from "../../../core/model/trigger_model";
import { IProcess } from "../../create_process/model/process_model"; import { IProcess } from "../../create_process/model/process_model";
import { message } from "antd"; import { message } from "antd";
import { BaseStore } from "../../../core/store/base_store"; import { SimpleErrorState } from "../../../core/store/base_store";
enum Type { enum Type {
PROCESS, PROCESS,
@ -16,7 +16,7 @@ export interface UnionView {
uuid?: string; uuid?: string;
} }
export class CreatePipelineStore extends BaseStore { export class CreatePipelineStore extends SimpleErrorState {
repository: CreatePipelineRepository; repository: CreatePipelineRepository;
triggersModels: ITriggerModel[] = []; triggersModels: ITriggerModel[] = [];
processModels: IProcess[] = []; processModels: IProcess[] = [];
@ -34,9 +34,7 @@ export class CreatePipelineStore extends BaseStore {
} }
filterPipelineViewModel(index: number): void { filterPipelineViewModel(index: number): void {
this.pipelineViewModels = this.pipelineViewModels.filter( this.pipelineViewModels = this.pipelineViewModels.filter((_el, i) => i !== index);
(_el, i) => i !== index
);
} }
addTrigger(e: string, id: string): void { addTrigger(e: string, id: string): void {
const lastElement = this.pipelineViewModels.lastElement(); const lastElement = this.pipelineViewModels.lastElement();
@ -82,12 +80,8 @@ export class CreatePipelineStore extends BaseStore {
message.error("not found pipelines process"); message.error("not found pipelines process");
return; return;
} }
const triggerId = this.pipelineViewModels.find( const triggerId = this.pipelineViewModels.find((el) => el.type === Type.TRIGGER)!.uuid as string;
(el) => el.type === Type.TRIGGER const processId = this.pipelineViewModels.find((el) => el.type === Type.PROCESS)!.uuid as string;
)!.uuid as string;
const processId = this.pipelineViewModels.find(
(el) => el.type === Type.PROCESS
)!.uuid as string;
this.repository.savePipeline({ this.repository.savePipeline({
process: processId, process: processId,
@ -96,33 +90,10 @@ export class CreatePipelineStore extends BaseStore {
} }
async loadProcess() { async loadProcess() {
this.isLoading = true; this.mapOk("processModels", this.repository.getProcessed());
const result = await this.repository.getProcessed();
result.fold(
(s) => {
this.processModels = s;
},
(_e) => {
this.isError = true;
}
);
this.isLoading = false;
} }
async loadTriggers() { async loadTriggers() {
this.isLoading = true; this.mapOk("triggersModels", this.repository.getTriggers());
const result = await this.repository.getTriggers(1);
result.fold(
(s) => {
this.triggersModels = s;
},
(_e) => {
this.isError = true;
}
);
this.isLoading = false;
} }
} }
export const createPipelineStore = new CreatePipelineStore(
new CreatePipelineRepository()
);

View file

@ -1,11 +1,8 @@
import { import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { IProcess } from "../model/process_model"; import { IProcess } from "../model/process_model";
export class ProcessRepository extends HttpRepository { export class ProcessRepository extends HttpRepository {
async save(model: IProcess): Promise<void> { async save(model: IProcess): Promise<void> {
await this.jsonRequest(HttpMethod.POST, "/process", model); await this._jsonRequest(HttpMethod.POST, "/process", model);
} }
} }

View file

@ -1,18 +1,11 @@
import * as React from "react"; import * as React from "react";
import { processStore } from "./logic/process_store"; import { processStore } from "./logic/process_store";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { import { SubmitButton, Input, ResetButton, Form, Radio, Switch } from "formik-antd";
SubmitButton,
Input,
ResetButton,
Form,
Radio,
Switch,
} from "formik-antd";
import { Formik } from "formik"; import { Formik } from "formik";
import { Row, Col } from "antd"; import { Row, Col } from "antd";
import { EXEC_TYPE, IssueType, processModelMock } from "../model/process_model"; import { EXEC_TYPE, IssueType, processModelMock } from "../model/process_model";
export const CreateProcessScreenPath = '/create/process' export const CreateProcessScreenPath = "/create/process";
export const CreateProcessScreen = observer(() => { export const CreateProcessScreen = observer(() => {
return ( return (
<div> <div>
@ -49,22 +42,11 @@ export const CreateProcessScreen = observer(() => {
<Radio.Group name="type" options={Object.values(EXEC_TYPE)} /> <Radio.Group name="type" options={Object.values(EXEC_TYPE)} />
<Radio.Group <Radio.Group name="issueType" options={Object.values(IssueType)} />
name="issueType"
options={Object.values(IssueType)}
/>
<Col style={{ marginTop: 20, justifyContent: "center" }}> <Col style={{ marginTop: 20, justifyContent: "center" }}>
<Switch <Switch name="isGenerating" checkedChildren="is generating" unCheckedChildren="is generating" />
name="isGenerating" <Switch name="isLocalCode" checkedChildren="is local code" unCheckedChildren="is local code" />
checkedChildren="is generating"
unCheckedChildren="is generating"
/>
<Switch
name="isLocalCode"
checkedChildren="is local code"
unCheckedChildren="is local code"
/>
</Col> </Col>
<Row style={{ marginTop: 20, justifyContent: "center" }}> <Row style={{ marginTop: 20, justifyContent: "center" }}>

View file

@ -8,8 +8,8 @@ class ProcessStore {
this.repository = repository; this.repository = repository;
makeAutoObservable(this); makeAutoObservable(this);
} }
async saveResult(model:IProcess) { async saveResult(model: IProcess) {
await this.repository.save(model) await this.repository.save(model);
} }
} }

View file

@ -1,10 +1,7 @@
import { Result } from "../../core/helper/result"; import { Result } from "../../core/helper/result";
import { DatabaseModel } from "../../core/model/database_model"; import { DatabaseModel } from "../../core/model/database_model";
import { ITriggerModel } from "../../core/model/trigger_model"; import { ITriggerModel } from "../../core/model/trigger_model";
import { import { HttpMethod, HttpRepository } from "../../core/repository/http_repository";
HttpMethod,
HttpRepository,
} from "../../core/repository/http_repository";
import { IProcess } from "../create_process/model/process_model"; import { IProcess } from "../create_process/model/process_model";
import { ICreateProjectViewModel } from "./project_model"; import { ICreateProjectViewModel } from "./project_model";
@ -15,11 +12,9 @@ export interface PipelineModel extends DatabaseModel {
export class CreateProjectRepository extends HttpRepository { export class CreateProjectRepository extends HttpRepository {
async getAllPipelines(page = 1): Promise<Result<Error, PipelineModel[]>> { async getAllPipelines(page = 1): Promise<Result<Error, PipelineModel[]>> {
return await this.jsonRequest<PipelineModel[]>(HttpMethod.GET, "/pipeline"); return await this._jsonRequest<PipelineModel[]>(HttpMethod.GET, "/pipeline");
} }
async saveProject( async saveProject(model: ICreateProjectViewModel): Promise<Result<Error, void>> {
model: ICreateProjectViewModel return await this._jsonRequest<void>(HttpMethod.POST, "/project", model);
): Promise<Result<Error, void>> {
return await this.jsonRequest<void>(HttpMethod.POST, "/project", model);
} }
} }

View file

@ -1,17 +1,23 @@
import * as React from "react"; import * as React from "react";
import { LoadPage } from "../../core/ui/pages/load_page"; import { LoadPage as MainPage } from "../../core/ui/pages/load_page";
import { createProjectStore } from "./create_project_store";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Col, Row, Input, Button } from "antd"; import { Col, Row, Input, Button } from "antd";
import { ReactComponent as AddIcon } from "../../core/assets/icons/add.svg"; import { ReactComponent as AddIcon } from "../../core/assets/icons/add.svg";
import { CreatePipelineScreenPath } from "../create_pipeline/presentation/create_pipeline_screen"; import { CreatePipelineScreenPath } from "../create_pipeline/presentation/create_pipeline_screen";
import { CreateProjectStore } from "./create_project_store";
import { CreateProjectRepository } from "./create_project_repository";
export const CreateProjectScreenPath = "/create_project"; export const CreateProjectScreenPath = "/create_project";
export const CreateProjectScreen: React.FunctionComponent = observer(() => { export const CreateProjectScreen: React.FunctionComponent = observer(() => {
const [createProjectStore] = React.useState(() => new CreateProjectStore(new CreateProjectRepository()));
React.useEffect(() => {
createProjectStore.init();
}, [createProjectStore]);
return ( return (
<> <>
<LoadPage <MainPage
path={CreatePipelineScreenPath} path={CreatePipelineScreenPath}
largeText={"Create project"} largeText={"Create project"}
minText={"add new pipelines?"} minText={"add new pipelines?"}
@ -48,21 +54,14 @@ export const CreateProjectScreen: React.FunctionComponent = observer(() => {
})} })}
</Col> </Col>
<Col> <Col>
<Row> <Row>
<Input <Input
style={{ width: "250px" }} style={{ width: "250px" }}
onChange={(e) => onChange={(e) => createProjectStore.setDescriptionToNewProject(e.target.value)}
createProjectStore.setDescriptionToNewProject(
e.target.value
)
}
placeholder="project description" placeholder="project description"
/> />
<Button onClick={() => createProjectStore.saveProject()}> <Button onClick={() => createProjectStore.saveProject()}>save</Button>
save
</Button>
</Row> </Row>
{createProjectStore.newProjectViews.map((el, index) => { {createProjectStore.newProjectViews.map((el, index) => {
@ -87,4 +86,3 @@ export const CreateProjectScreen: React.FunctionComponent = observer(() => {
</> </>
); );
}); });

View file

@ -1,12 +1,9 @@
import makeAutoObservable from "mobx-store-inheritance"; import makeAutoObservable from "mobx-store-inheritance";
import { import { CreateProjectRepository, PipelineModel } from "./create_project_repository";
CreateProjectRepository,
PipelineModel,
} from "./create_project_repository";
import { message } from "antd"; import { message } from "antd";
import { BaseStore } from "../../core/store/base_store"; import { SimpleErrorState } from "../../core/store/base_store";
class CreateProjectStore extends BaseStore { export class CreateProjectStore extends SimpleErrorState {
repository: CreateProjectRepository; repository: CreateProjectRepository;
pipelineModels?: PipelineModel[]; pipelineModels?: PipelineModel[];
@ -17,25 +14,16 @@ class CreateProjectStore extends BaseStore {
super(); super();
this.repository = repository; this.repository = repository;
makeAutoObservable(this); makeAutoObservable(this);
this.loadPipelines();
} }
async init() {
await this.loadPipelines();
}
async addPipeline(model: PipelineModel) { async addPipeline(model: PipelineModel) {
this.newProjectViews.push(model); this.newProjectViews.push(model);
} }
async loadPipelines() { async loadPipelines() {
this.isLoading = true; this.mapOk("pipelineModels", this.repository.getAllPipelines());
const result = await this.repository.getAllPipelines();
result.fold(
(s) => {
this.pipelineModels = s;
},
(_e) => {
this.isError = true;
}
);
this.isLoading = false;
} }
setDescriptionToNewProject(value: string): void { setDescriptionToNewProject(value: string): void {
@ -71,7 +59,3 @@ class CreateProjectStore extends BaseStore {
); );
} }
} }
export const createProjectStore = new CreateProjectStore(
new CreateProjectRepository()
);

View file

@ -3,7 +3,8 @@ import { CreateProjectInstanceStore } from "./create_project_instance_store";
import { CreateProjectInstanceRepository } from "./create_project_instance_repository"; import { CreateProjectInstanceRepository } from "./create_project_instance_repository";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Upload, Button } from "antd"; import { Upload, Button } from "antd";
import { useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Input } from "antd";
export const CreateProjectInstancePath = "/create/project/instance/"; export const CreateProjectInstancePath = "/create/project/instance/";
@ -12,16 +13,25 @@ export const CreateProjectInstanceScreen = observer(() => {
() => new CreateProjectInstanceStore(new CreateProjectInstanceRepository()) () => new CreateProjectInstanceStore(new CreateProjectInstanceRepository())
); );
const id = useParams().id; const id = useParams().id;
createProjectInstanceStore.getProjectById(id as string) const navigate = useNavigate();
React.useEffect(() => {
createProjectInstanceStore.init(navigate, id as string);
}, [id, createProjectInstanceStore, navigate]);
return ( return (
<> <>
<h1>project description</h1>
<Input onChange={(e) => createProjectInstanceStore.setProjectDescription(e.target.value)}></Input>
<h1>root entity</h1>
<Upload <Upload
onChange={(e) => { onChange={(e) => {
console.log(e); createProjectInstanceStore.file = e.file.originFileObj;
}} }}
> >
<Button>Upload root entity</Button> <Button>Upload root entity</Button>
</Upload> </Upload>
<Button onClick={() => createProjectInstanceStore.saveInstance()}>Save</Button>
</> </>
); );
}); });

View file

@ -1,10 +1,16 @@
import { import { HttpMethod, HttpRepository } from "../../core/repository/http_repository";
HttpMethod, import { NewProjectModel } from "./new_project_model";
HttpRepository,
} from "../../core/repository/http_repository";
export class CreateProjectInstanceRepository extends HttpRepository { export class CreateProjectInstanceRepository extends HttpRepository {
async getProjectInstance(id: string) { async setProjectRootFile(file: File) {
return await this.jsonRequest(HttpMethod.GET, ""); return await this._formDataRequest(HttpMethod.POST, "/project_instance/upload", file);
}
async createNewProject(project: NewProjectModel) {
return await this._jsonRequest(HttpMethod.POST, "/project_instance", project);
}
async setActiveProject(id: string) {
return await this._jsonRequest(HttpMethod.POST, `/project_instance/set/active/project?id=${id}`);
} }
} }

View file

@ -1,18 +1,39 @@
import makeAutoObservable from "mobx-store-inheritance"; import makeAutoObservable from "mobx-store-inheritance";
import { BaseStore } from "../../core/store/base_store"; import { SimpleErrorState } from "../../core/store/base_store";
import { CreateProjectInstanceRepository } from "./create_project_instance_repository"; import { CreateProjectInstanceRepository } from "./create_project_instance_repository";
import { message } from "antd";
import { HttpMethod } from "../../core/repository/http_repository";
import { NavigateFunction } from "react-router-dom";
import { NewProjectModel } from "./new_project_model";
export class CreateProjectInstanceStore extends BaseStore { export class CreateProjectInstanceStore extends SimpleErrorState {
newProjectModel: NewProjectModel;
repository: CreateProjectInstanceRepository;
file?: File;
navigate?: NavigateFunction;
constructor(repository: CreateProjectInstanceRepository) { constructor(repository: CreateProjectInstanceRepository) {
super(); super();
this.repository = repository; this.repository = repository;
makeAutoObservable(this); makeAutoObservable(this);
this.newProjectModel = NewProjectModel.empty();
} }
repository: CreateProjectInstanceRepository;
async getProjectById(id: string) { setProjectDescription(value: string): void {
const result = await this.loadingHelper(this.repository.getProjectInstance(id)) this.newProjectModel.description = value;
if(result.isSuccess()){ }
init(navigate: NavigateFunction, projectId: string) {
this.navigate = navigate;
this.newProjectModel.project = projectId;
}
async saveInstance(): Promise<void> {
if (this.file === undefined) {
message.error("Need upload file");
} else {
if (this.newProjectModel.isValid()) {
await this.repository.createNewProject(this.newProjectModel);
await this.repository.setProjectRootFile(this.file);
if (this.navigate !== undefined) this.navigate("/");
}
} }
} }
} }

View file

@ -0,0 +1,17 @@
export class NewProjectModel {
project: string;
description: string;
constructor(project: string, description: string) {
this.project = project;
this.description = description;
}
static empty() {
return new NewProjectModel("", "");
}
isValid(): boolean {
return this.project.isNotEmpty() && this.description.isNotEmpty();
}
messages() {
return "";
}
}

View file

@ -1,11 +1,8 @@
import { import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { ITriggerModel } from "../../../core/model/trigger_model"; import { ITriggerModel } from "../../../core/model/trigger_model";
export class TriggerRepository extends HttpRepository { export class TriggerRepository extends HttpRepository {
public async save(model: ITriggerModel) { public async save(model: ITriggerModel) {
return await this.jsonRequest(HttpMethod.POST, "/trigger", model); return await this._jsonRequest(HttpMethod.POST, "/trigger", model);
} }
} }

View file

@ -2,36 +2,40 @@ import * as React from "react";
import Editor from "@monaco-editor/react"; import Editor from "@monaco-editor/react";
import { Button } from "antd"; import { Button } from "antd";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { triggerStore } from "../trigger_store"; import { CallBackStringVoidFunction } from "../../../../core/extensions/extensions";
export const CodeTriggerForm: React.FunctionComponent = observer(() => { interface ICodeTriggerFormProps {
return ( codeTriggerValue: string;
<> clearTriggerCode: VoidFunction;
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} /> saveCode: VoidFunction;
writeNewTrigger: CallBackStringVoidFunction;
}
<Editor export const CodeTriggerForm: React.FunctionComponent<ICodeTriggerFormProps> = observer(
height="40vh" (props: ICodeTriggerFormProps) => {
defaultLanguage="javascript" return (
value={triggerStore.codeTriggerValue} <>
onChange={(v) => { <div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
triggerStore.writeNewTrigger(v);
}}
onValidate={(_m) => {}}
/>
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} /> <Editor
<div style={{ height: "10px" }} /> height="40vh"
defaultLanguage="javascript"
value={props.codeTriggerValue}
onChange={(v) => {
props.writeNewTrigger(v ?? "");
}}
onValidate={(_m) => {}}
/>
<Button <div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
onClick={() => triggerStore.saveCode()} <div style={{ height: "10px" }} />
style={{ marginLeft: "10px", marginRight: "10px" }}
>
Save code
</Button>
<Button onClick={() => triggerStore.clearTriggerCode()}> <Button onClick={() => props.saveCode()} style={{ marginLeft: "10px", marginRight: "10px" }}>
Reset code Save code
</Button> </Button>
</>
); <Button onClick={() => props.clearTriggerCode()}>Reset code</Button>
}); </>
);
}
);

View file

@ -3,50 +3,49 @@ import { Formik } from "formik";
import { SubmitButton, Input, ResetButton, Form, FormItem } from "formik-antd"; import { SubmitButton, Input, ResetButton, Form, FormItem } from "formik-antd";
import { Row, Col } from "antd"; import { Row, Col } from "antd";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { triggerStore } from "../trigger_store";
import { TriggerType } from "../../../../core/model/trigger_model"; import { TriggerType } from "../../../../core/model/trigger_model";
import { validateRequired } from "../../../../core/helper/validate"; import { validateRequired } from "../../../../core/helper/validate";
export interface IFileTriggerFormProps {
export const FileTriggerForm: React.FunctionComponent = observer(() => { pushTrigger: (value: string, type: TriggerType) => void;
return ( }
<> export const FileTriggerForm: React.FunctionComponent<IFileTriggerFormProps> = observer(
<div style={{ marginTop: 80 }}> (props: IFileTriggerFormProps) => {
<Formik return (
initialValues={{ <>
value: "", <div style={{ marginTop: 80 }}>
}} <Formik
onSubmit={(values, actions) => { initialValues={{
triggerStore.pushTrigger(values.value, TriggerType.FILE); value: "",
actions.setSubmitting(false); }}
actions.resetForm(); onSubmit={(values, actions) => {
}} props.pushTrigger(values.value, TriggerType.FILE);
validate={(values) => { actions.setSubmitting(false);
if (values.value.length === 0) { actions.resetForm();
return false; }}
} validate={(values) => {
return {}; if (values.value.length === 0) {
}} return false;
render={() => ( }
<Form> return {};
<div style={{ background: "white", flex: 1, padding: 40 }}> }}
<FormItem render={() => (
name="value" <Form>
required={true} <div style={{ background: "white", flex: 1, padding: 40 }}>
validate={validateRequired} <FormItem name="value" required={true} validate={validateRequired}>
> <Input name="value" placeholder="regExp file" />
<Input name="value" placeholder="regExp file" /> </FormItem>
</FormItem> <Row style={{ marginTop: 60 }}>
<Row style={{ marginTop: 60 }}> <Col offset={8}>
<Col offset={8}> <ResetButton>Reset</ResetButton>
<ResetButton>Reset</ResetButton> <SubmitButton>Submit</SubmitButton>
<SubmitButton>Submit</SubmitButton> </Col>
</Col> </Row>
</Row> </div>
</div> </Form>
</Form> )}
)} />
/> </div>
</div> </>
</> );
); }
}); );

View file

@ -2,35 +2,20 @@ import * as React from "react";
import { Button, Col, Row, Switch, Typography, Input } from "antd"; import { Button, Col, Row, Switch, Typography, Input } from "antd";
import { CodeTriggerForm } from "./components/code_trigger_form"; import { CodeTriggerForm } from "./components/code_trigger_form";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { triggerStore } from "./trigger_store";
import { FileTriggerForm } from "./components/file_trigger_form"; import { FileTriggerForm } from "./components/file_trigger_form";
import { ReactComponent as DeleteIcon } from "../../../core/assets/icons/delete.svg"; import { ReactComponent as DeleteIcon } from "../../../core/assets/icons/delete.svg";
import { Loader } from "../../../core/ui/loader/loader"; import { Loader } from "../../../core/ui/loader/loader";
import { TriggerRepository } from "../data/trigger_repository";
import { TriggerStore } from "./trigger_store";
import { TriggerViewModel } from "../model/trigger_form_view_model";
import { CallBackStringVoidFunction } from "../../../core/extensions/extensions";
const { Title } = Typography; const { Title } = Typography;
const Header = observer(() => { const Bottom = observer((props: { triggers: TriggerViewModel[]; callBack: CallBackStringVoidFunction }) => {
return (
<Row style={{ justifyItems: "center", alignItems: "center" }}>
<div style={{ height: "37px" }}>
<Switch
checked={triggerStore.getTriggerType()}
onChange={() => triggerStore.setTriggerType()}
/>
</div>
<Title level={2}>
Trigger editor: {triggerStore.getTriggerDescription()}
</Title>
<div style={{ width: "10px" }}></div>
<Button onClick={() => triggerStore.saveResult()}>Save result</Button>
</Row>
);
});
const Bottom = observer(() => {
return ( return (
<Col> <Col>
{triggerStore.triggers.map((el) => { {props.triggers.map((el) => {
return ( return (
<Row <Row
style={{ style={{
@ -38,40 +23,50 @@ const Bottom = observer(() => {
}} }}
> >
{el.value} {el.value}
<DeleteIcon onClick={() => triggerStore.deleteItem(el.id)} /> <DeleteIcon onClick={() => props.callBack(el.id)} />
</Row> </Row>
); );
})} })}
</Col> </Col>
); );
}); });
export const CreateTriggerScreenPath = '/create/trigger' export const CreateTriggerScreenPath = "/create/trigger";
export const TriggerScreen: React.FunctionComponent = observer(() => { export const TriggerScreen: React.FunctionComponent = observer(() => {
const [triggerStore] = React.useState(() => new TriggerStore(new TriggerRepository()));
return ( return (
<> <>
<main> <main>
{!triggerStore.isLoading ? ( {!triggerStore.isLoading ? (
<> <>
<Header />, <Row style={{ justifyItems: "center", alignItems: "center" }}>
<Input <div style={{ height: "37px" }}>
placeholder="trigger description" <Switch checked={triggerStore.getTriggerType()} onChange={() => triggerStore.setTriggerType()} />
onChange={() => triggerStore.changeTriggerDescription} </div>
/> <Title level={2}>Trigger editor: {triggerStore.getTriggerDescription()}</Title>
<div style={{ width: "10px" }}></div>
<Button onClick={() => triggerStore.saveResult()}>Save result</Button>
</Row>
<Input placeholder="trigger description" onChange={() => triggerStore.changeTriggerDescription} />
{triggerStore.getTriggerType() ? ( {triggerStore.getTriggerType() ? (
<> <>
<FileTriggerForm /> <FileTriggerForm pushTrigger={triggerStore.pushTrigger} />
</> </>
) : ( ) : (
<> <>
<CodeTriggerForm /> <CodeTriggerForm
codeTriggerValue={triggerStore.codeTriggerValue}
clearTriggerCode={triggerStore.clearTriggerCode}
saveCode={triggerStore.saveCode}
writeNewTrigger={triggerStore.writeNewTrigger}
/>
</> </>
)} )}
<Bottom /> <Bottom triggers={triggerStore.triggers} callBack={triggerStore.deleteItem} />
</> </>
) : ( ) : (
<> <>
<Loader />
<Loader/>
</> </>
)} )}
</main> </main>

View file

@ -3,9 +3,9 @@ import { v4 as uuidv4 } from "uuid";
import { TriggerType } from "../../../core/model/trigger_model"; import { TriggerType } from "../../../core/model/trigger_model";
import { TriggerRepository } from "../data/trigger_repository"; import { TriggerRepository } from "../data/trigger_repository";
import { TriggerViewModel } from "../model/trigger_form_view_model"; import { TriggerViewModel } from "../model/trigger_form_view_model";
import { BaseStore } from "../../../core/store/base_store"; import { SimpleErrorState } from "../../../core/store/base_store";
class TriggerStore extends BaseStore { export class TriggerStore extends SimpleErrorState {
constructor(repository: TriggerRepository) { constructor(repository: TriggerRepository) {
super(); super();
this.triggerType = TriggerType.FILE; this.triggerType = TriggerType.FILE;
@ -39,9 +39,7 @@ class TriggerStore extends BaseStore {
this.triggerType = TriggerType.FILE; this.triggerType = TriggerType.FILE;
}; };
getTriggerDescription = (): string => { getTriggerDescription = (): string => {
return this.triggerType === TriggerType.FILE return this.triggerType === TriggerType.FILE ? TriggerType.FILE : TriggerType.PROCESS;
? TriggerType.FILE
: TriggerType.PROCESS;
}; };
pushTrigger = (value: string, type: TriggerType): void => { pushTrigger = (value: string, type: TriggerType): void => {
this.triggers.push({ this.triggers.push({
@ -72,16 +70,15 @@ class TriggerStore extends BaseStore {
} }
} }
async saveResult(): Promise<void> { async saveResult(): Promise<void> {
this.isLoading = true; await this.httpHelper(
await this.repository.save({ this.repository.save({
type: this.getTriggerDescription(), type: this.getTriggerDescription(),
description: this.triggerDescription, description: this.triggerDescription,
value: this.triggers.map((el) => { value: this.triggers.map((el) => {
return el.value; return el.value;
}), }),
}); })
this.isLoading = false; );
} }
} }
export const triggerStore = new TriggerStore(new TriggerRepository()); export const triggerStore = new TriggerStore(new TriggerRepository());

68
ui/src/features/p.tsx Normal file
View file

@ -0,0 +1,68 @@
export {};
// import React from "react";
// import { CoreError, UiErrorState } from "../core/store/base_store";
// import { SelectProjectStore } from "./select_project/presentation/select_project_store";
// export declare type ClassConstructor<T> = {
// new (...args: any[]): T;
// };
// interface MobxReactComponentProps<T extends UiErrorState<CoreError>, ClassConstructor> {
// store: ClassConstructor;
// children: (element: T) => React.ReactElement;
// }
// class UiStateErrorComponent<T extends UiErrorState<CoreError>, K> extends React.Component<
// MobxReactComponentProps<T, K>,
// { store: T | undefined }
// > {
// async componentDidMount(): Promise<void> {
// const store = this.props.store as ClassConstructor<T>;
// console.log(store);
// const s = new store();
// this.setState({ store: s });
// if (this.state !== null) {
// await this.state.store?.init();
// }
// }
// componentWillUnmount(): void {
// if (this.state.store !== undefined) {
// this.state.store.dispose();
// }
// }
// render() {
// if (this.state !== null) {
// if (this.state.store?.isLoading) {
// return <>Loading</>;
// }
// if (this.state.store !== undefined) {
// return this.props.children(this.state.store);
// }
// }
// return (
// <div>
// <>{this.props.children}</>
// </div>
// );
// }
// }
// export const ExampleScreen: React.FC = () => {
// return (
// <div>
// <UiStateErrorComponent<SelectProjectStore, {}> store={SelectProjectStore}>
// {(store) => {
// console.log(store);
// return (
// <div>
// {store.projects.map((el) => {
// return <>{el}</>;
// })}
// </div>
// );
// }}
// </UiStateErrorComponent>
// </div>
// );
// };

View file

@ -4,13 +4,12 @@ import { PipelineInstanceStore } from "./pipeline_instance_store";
export const PipelineInstanceScreenPath = "/pipeline_instance/"; export const PipelineInstanceScreenPath = "/pipeline_instance/";
export const PipelineInstanceScreen: React.FunctionComponent = () => { export const PipelineInstanceScreen: React.FunctionComponent = () => {
const [pipelineInstanceStore] = React.useState( const [pipelineInstanceStore] = React.useState(() => new PipelineInstanceStore());
() => new PipelineInstanceStore() React.useEffect(() => {}, [pipelineInstanceStore]);
);
return ( return (
<LoadPage <LoadPage
needBackButton={false} needBackButton={true}
largeText={"Project instance active"}
isError={pipelineInstanceStore.isError} isError={pipelineInstanceStore.isError}
isLoading={pipelineInstanceStore.isLoading} isLoading={pipelineInstanceStore.isLoading}
children={<div></div>} children={<div></div>}

View file

@ -1,7 +1,7 @@
import makeAutoObservable from "mobx-store-inheritance"; import makeAutoObservable from "mobx-store-inheritance";
import { BaseStore } from "../../core/store/base_store"; import { SimpleErrorState } from "../../core/store/base_store";
export class PipelineInstanceStore extends BaseStore { export class PipelineInstanceStore extends SimpleErrorState {
constructor() { constructor() {
super(); super();
makeAutoObservable(this); makeAutoObservable(this);

View file

@ -1,14 +0,0 @@
import * as React from "react";
import { StaticAssetItemModel } from "../scene_manager_store";
export interface IStaticAssetModelViewProps {
model: StaticAssetItemModel;
}
export function StaticAssetModelView(props: IStaticAssetModelViewProps) {
return (
<div style={{ width: "100px", textAlignLast: "center", backgroundColor: "aqua", margin: "10px" }}>
{props.model.name}
</div>
);
}

View file

@ -0,0 +1,19 @@
import { Result } from "../../../core/helper/result";
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
import { CoreError } from "../../../core/store/base_store";
import { RobossemblerAssets } from "../model/robossembler_assets";
export class SceneHttpRepository extends HttpRepository {
async getRobossemblerAssets() {
return this._jsonToClassInstanceRequest<RobossemblerAssets>(
HttpMethod.GET,
"/robossembler_assets",
RobossemblerAssets
) as unknown as Promise<Result<CoreError, RobossemblerAssets>>;
}
async saveScene(robossemblerAssets: RobossemblerAssets) {
return this._jsonRequest(HttpMethod.POST, "/robossembler_assets", robossemblerAssets) as unknown as Promise<
Result<CoreError, void>
>;
}
}

View file

@ -0,0 +1,160 @@
import { IsArray, IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
export class Gravity {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export class Pose {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
@IsNumber()
roll: number;
@IsNumber()
pitch: number;
@IsNumber()
yaw: number;
}
export class Position {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export enum InstanceType {
RGB_CAMERA = "rgb_camera",
SCENE_SIMPLE_OBJECT = "scene_simple_object",
}
abstract class CoreInstances {}
export class Instance extends CoreInstances {
@IsEnum(InstanceType)
instanceType: InstanceType;
@Type(() => Position)
position: Position;
@IsArray()
quaternion: number[];
@IsOptional()
@IsString()
instanceAt: null | string = null;
}
export class SceneSimpleObject extends Instance {}
export class InstanceRgbCamera extends Instance {
@IsString()
cameraLink: string;
@IsString()
topicCameraInfo: string;
@IsOptional()
@IsString()
topicDepth: string | null;
@IsString()
topicImage: string;
}
export class Asset {
@IsString()
name: string;
@IsString()
ixx: string;
@IsString()
ixy: string;
@IsString()
ixz: string;
@IsString()
iyy: string;
@IsString()
izz: string;
@IsString()
mass: string;
@IsString()
posX: string;
@IsString()
posY: string;
@IsString()
posZ: string;
@IsString()
eulerX: string;
@IsString()
eulerY: string;
@IsString()
eulerZ: string;
@IsString()
iyz: string;
@IsString()
meshPath: string;
@IsString()
friction: string;
@IsString()
centerMassX: string;
@IsString()
centerMassY: string;
@IsString()
centerMassZ: string;
}
export class Physics {
@IsString()
engine_name: string;
@Type(() => Gravity)
gravity: Gravity;
}
export class RobossemblerAssets {
@ValidateNested()
@Type(() => Asset)
assets: Asset[];
@IsArray()
@Type(() => Instance, {
discriminator: {
property: "instanceType",
subTypes: [
{ value: InstanceRgbCamera, name: InstanceType.RGB_CAMERA },
{ value: SceneSimpleObject, name: InstanceType.SCENE_SIMPLE_OBJECT },
],
},
keepDiscriminatorProperty: true,
})
instances: Instance[];
@IsOptional()
@ValidateNested()
@Type(() => Physics)
physics: Physics;
convertLocalPathsToServerPaths(server_address: string): RobossemblerAssets {
this.assets = this.assets.map((el) => {
el.meshPath = server_address + el.meshPath;
return el;
});
return this;
}
getAssetPath(assetName: string): string {
const findElement = this.assets.find((el) => el.name === assetName);
if (findElement === undefined) {
throw new Error("RobossemblerAssets.getAssetPath not found asset by name:" + assetName);
}
return findElement.meshPath;
}
getAssetAtInstance(instanceAt: string): Asset {
return this.assets.filter((el) => el.name === instanceAt)[0];
}
}

View file

@ -0,0 +1,164 @@
import { CameraHelper, Object3D, PerspectiveCamera, Quaternion, Scene, Vector3 } from "three";
import { v4 as uuidv4 } from "uuid";
import { UserData } from "../../../core/repository/core_three_repository";
import { Asset, Instance, InstanceRgbCamera, InstanceType, SceneSimpleObject } from "./robossembler_assets";
export enum RobossemblerFiles {
robossemblerAssets = "robossembler_assets.json",
}
export enum SceneModelsType {
ASSET,
}
export abstract class BaseSceneItemModel {
id: string;
name: string;
position: Vector3;
quaternion: Quaternion;
constructor(name: string) {
this.id = uuidv4();
this.name = name;
}
abstract toInstance(): Instance;
abstract deleteToScene(scene: Scene): Object3D[];
toQuaternionArray() {
return [this.quaternion.x, this.quaternion.y, this.quaternion.z, this.quaternion.w];
}
}
export class StaticAssetItemModel extends BaseSceneItemModel {
name: string;
type = SceneModelsType.ASSET;
constructor(name: string, position: Vector3, quaternion: Quaternion) {
super(name);
this.name = name;
this.position = position;
this.quaternion = quaternion;
}
static fromSceneSimpleObjectAndAsset(sceneSimpleObject: SceneSimpleObject, asset: Asset) {
const { x, y, z } = sceneSimpleObject.position;
const quaternion = sceneSimpleObject.quaternion;
return new StaticAssetItemModel(
asset.name,
new Vector3(x, y, z),
new Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3])
);
}
toInstance(): Instance {
const instance = new Instance();
instance.instanceType = InstanceType.SCENE_SIMPLE_OBJECT;
instance.position = this.position;
instance.instanceAt = this.name;
instance.quaternion = this.toQuaternionArray();
return instance;
}
deleteToScene(scene: Scene): Object3D[] {
return scene.children.filter((el) => el.name !== this.name);
}
}
export interface ICoreInstance {
instanceAt: null | string;
type: string;
position: Vector3;
quaternion: Quaternion;
}
export interface ICameraInstance extends ICoreInstance {
cameraLink: string;
topicImage: string;
topicCameraInfo: string;
topicDepth: string | null;
}
export class CameraViewModel extends BaseSceneItemModel {
cameraLink: string;
topicImage: string;
topicCameraInfo: string;
topicDepth: null | string;
constructor(cameraLink: string, topicImage: string, topicCameraInfo: string, topicDepth: string | null) {
super(cameraLink);
this.cameraLink = cameraLink;
this.topicImage = topicImage;
this.topicCameraInfo = topicCameraInfo;
this.topicDepth = topicDepth;
}
static fromInstanceRgbCamera(instanceRgbCamera: InstanceRgbCamera) {
const { cameraLink, topicImage, topicCameraInfo, topicDepth, position, quaternion } = instanceRgbCamera;
const instance = new CameraViewModel(cameraLink, topicImage, topicCameraInfo, topicDepth);
const { x, y, z } = position;
instance.position = new Vector3(x, y, z);
instance.quaternion = new Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
return instance;
}
deleteToScene(scene: Scene): Object3D[] {
return scene.children.filter((el) => {
if (el.name === this.name) {
return false;
}
if (el.name === this.cameraLink + "camera_helper") {
return false;
}
return true;
});
}
toInstance(): InstanceRgbCamera {
return {
instanceType: InstanceType.RGB_CAMERA,
position: this.position,
quaternion: this.toQuaternionArray(),
instanceAt: null,
cameraLink: this.cameraLink,
topicCameraInfo: this.topicCameraInfo,
topicDepth: this.topicDepth,
topicImage: this.topicImage,
};
}
validate(cameraLinksNames: string[]) {
if (cameraLinksNames.filter((el) => this.cameraLink === el).isNotEmpty()) {
return { cameraLink: "the name for the camera is not unique" };
}
if (this.cameraLink.isEmpty()) {
return { cameraLink: "is empty" };
}
if (this.topicImage.isEmpty()) {
return { topicImage: "is empty" };
}
if (this.topicCameraInfo.isEmpty()) {
return { topicCameraInfo: "is empty" };
}
}
mapPerspectiveCamera(htmlSceneWidth: number, htmlSceneHeight: number) {
const perspectiveCamera = new PerspectiveCamera(48, htmlSceneWidth / htmlSceneHeight, 7.1, 28.5);
perspectiveCamera.position.copy(this.position);
perspectiveCamera.quaternion.copy(this.quaternion);
perspectiveCamera.name = this.cameraLink;
const cameraHelper = new CameraHelper(perspectiveCamera);
perspectiveCamera.userData[UserData.selectedObject] = true;
cameraHelper.userData[UserData.selectedObject] = true;
cameraHelper.name = this.cameraLink + "camera_helper";
return [cameraHelper, perspectiveCamera];
}
static empty() {
return new CameraViewModel("", "", "", "");
}
}

View file

@ -0,0 +1,27 @@
export class SceneMenu {
x?: number;
y?: number;
isShow?: boolean;
constructor(x: number | undefined, y: number | undefined, isShow: boolean | undefined) {
this.x = x;
this.y = y;
this.isShow = isShow;
}
static empty() {
return new SceneMenu(undefined, undefined, false);
}
}
export interface SceneManagerView {
name: string;
clickHandel: Function;
}
export enum SceneMode {
ROTATE = "Rotate",
MOVING = "Moving",
EMPTY = "Empty",
ADD_CAMERA = "Add camera",
}

View file

@ -0,0 +1,27 @@
interface SceneMenuProps {
x?: number;
y?: number;
}
export const SceneMenu = (props: SceneMenuProps) => {
const sceneMenuStyle = {
transform: "rotate(0deg)",
background: "rgb(73 73 73)",
color: "#FFFFFF",
fontSize: "20px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderColor: "aqua",
border: "solid",
borderRadius: "30px",
width: "150px",
height: "300px",
};
return (
<div style={{ position: "absolute", top: "0px", left: "0px" }}>
<div style={{ ...sceneMenuStyle, marginLeft: `${props.x}px`, marginTop: `${props.y}px` }}></div>
</div>
);
};

View file

@ -0,0 +1,68 @@
import React from "react";
export const SceneWidget = () => {
const [pressed, setPressed] = React.useState(false);
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
if (pressed) {
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", togglePressed);
}
return () => {
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", togglePressed);
};
}, [position, pressed]);
const onMouseMove = (event: any) => {
const x = position.x + event.movementX;
const y = position.y + event.movementY;
setPosition({ x, y });
};
const togglePressed = () => {
setPressed((prev) => !prev);
};
const quickAndDirtyStyle = {
transform: "rotate(0deg)",
background: "rgb(73 73 73)",
color: "#FFFFFF",
fontSize: "20px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderColor: "aqua",
border: "solid",
borderRadius: "30px",
};
return (
<div style={{ position: "absolute", top: "0px", left: "0px" }}>
<div
className={pressed ? "box_0-active" : "box-0"}
style={{
...quickAndDirtyStyle,
marginLeft: `${position.x}px`,
marginTop: `${position.y}px`,
}}
onClickCapture={(event) => {
event.stopPropagation();
setPressed(false);
}}
onMouseDown={togglePressed}
>
<p>{pressed ? "Dragging..." : "Press to drag"}</p>
<h1
onClick={(event) => {
event.stopPropagation();
console.log(201);
}}
>
HYO
</h1>
</div>
</div>
);
};

View file

@ -0,0 +1,45 @@
import * as React from "react";
import { BaseSceneItemModel, CameraViewModel, StaticAssetItemModel } from "../../model/scene_assets";
import { Button } from "antd";
export interface IStaticAssetModelViewProps {
model: BaseSceneItemModel;
onTap: Function;
}
export function StaticAssetModelView(props: IStaticAssetModelViewProps) {
if (props.model instanceof CameraViewModel) {
return (
<div
style={{
backgroundColor: "ActiveBorder",
padding: "10px",
color: "white",
width: "100px",
textAlignLast: "center",
}}
>
{props.model.cameraLink}
<Button onClick={() => props.onTap()}>delete</Button>
</div>
);
}
if (props.model instanceof StaticAssetItemModel) {
return (
<div
style={{
backgroundColor: "brown",
padding: "10px",
color: "white",
width: "100px",
textAlignLast: "center",
}}
>
{props.model.name}
<Button onClick={() => props.onTap()}>delete</Button>
</div>
);
}
return <></>;
}

Some files were not shown because too many files have changed in this diff Show more