Реализовать загрузку 3D-моделей в Scene manager и выгрузку файлов для запуска симуляции
This commit is contained in:
parent
11ca9cdb5e
commit
3fefd60b72
109 changed files with 2726 additions and 1190 deletions
|
@ -13,6 +13,8 @@
|
|||
"@types/react-dom": "^18.2.7",
|
||||
"@types/socket.io-client": "^3.0.0",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"formik-antd": "^2.0.4",
|
||||
"i18next": "^23.6.0",
|
||||
"mobx": "^6.10.0",
|
||||
|
@ -24,9 +26,13 @@
|
|||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-router-dom": "^6.18.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sass": "^1.66.1",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"three": "^0.159.0",
|
||||
"three-stdlib": "^2.28.9",
|
||||
"three-transform-controls": "^1.0.4",
|
||||
"ts-pattern": "^5.0.6",
|
||||
"typescript": "^4.9.5",
|
||||
"urdf-loader": "^0.12.1",
|
||||
"uuid": "^9.0.1",
|
||||
|
|
|
@ -2,7 +2,8 @@ import { ArrayExtensions } from "./array";
|
|||
import { MapExtensions } from "./map";
|
||||
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 {
|
||||
interface Array<T> {
|
||||
|
@ -15,9 +16,10 @@ declare global {
|
|||
}
|
||||
interface String {
|
||||
isEmpty(): boolean;
|
||||
isNotEmpty(): boolean;
|
||||
}
|
||||
interface Map<K, V> {
|
||||
addValueOrMakeCallback(key: K, value: V, callBack: CallBackFunction): void;
|
||||
addValueOrMakeCallback(key: K, value: V, callBack: CallBackVoidFunction): void;
|
||||
}
|
||||
}
|
||||
export const extensions = () => {
|
||||
|
|
|
@ -5,4 +5,10 @@ export const StringExtensions = () => {
|
|||
return this.length === 0;
|
||||
};
|
||||
}
|
||||
if ("".isNotEmpty === undefined) {
|
||||
// eslint-disable-next-line no-extend-native
|
||||
String.prototype.isNotEmpty = function () {
|
||||
return this.length !== 0;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
82
ui/src/core/helper/debounce.ts
Normal file
82
ui/src/core/helper/debounce.ts
Normal 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;
|
||||
}
|
29
ui/src/core/helper/throttle.ts
Normal file
29
ui/src/core/helper/throttle.ts
Normal 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);
|
||||
},
|
||||
];
|
||||
};
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
export interface ActivePipeline {
|
||||
pipelineIsRunning: boolean;
|
||||
projectUUID?: string | null;
|
||||
projectId?: string | null;
|
||||
lastProcessCompleteCount: number | null;
|
||||
error: any;
|
||||
}
|
6
ui/src/core/model/ui_base_error.ts
Normal file
6
ui/src/core/model/ui_base_error.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export class UiBaseError {
|
||||
text: string;
|
||||
constructor(text: string) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
401
ui/src/core/repository/core_three_repository.ts
Normal file
401
ui/src/core/repository/core_three_repository.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { ClassConstructor, plainToInstance } from "class-transformer";
|
||||
import { Result } from "../helper/result";
|
||||
|
||||
export enum HttpMethod {
|
||||
|
@ -16,12 +17,22 @@ export class HttpError extends Error {
|
|||
|
||||
export class HttpRepository {
|
||||
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>(
|
||||
method: HttpMethod,
|
||||
url: string,
|
||||
data?: any
|
||||
): Promise<Result<HttpError, T>> {
|
||||
const reqInit = {
|
||||
body: formData,
|
||||
method: method,
|
||||
};
|
||||
|
||||
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 {
|
||||
const reqInit = {
|
||||
body: data,
|
||||
|
@ -37,28 +48,50 @@ export class HttpRepository {
|
|||
return Result.error(new HttpError(this.server + url, response.status));
|
||||
}
|
||||
|
||||
return Result.ok(await response.json());
|
||||
return Result.ok(await response.json());
|
||||
} catch (error) {
|
||||
return Result.error(new HttpError(error, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public async request<T>(
|
||||
method: HttpMethod,
|
||||
url: string,
|
||||
data?: any
|
||||
): Promise<T> {
|
||||
public async _request<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
|
||||
const reqInit = {
|
||||
body: data,
|
||||
method: method,
|
||||
};
|
||||
|
||||
if (data !== undefined) {
|
||||
reqInit["body"] = data;
|
||||
}
|
||||
const response = await fetch(this.server + url, reqInit);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,18 +21,17 @@ import {
|
|||
CreateProcessScreen,
|
||||
CreateProcessScreenPath,
|
||||
} from "../../features/create_process/presentation/create_process_screen";
|
||||
import { ProjectRepository } from "../../features/all_projects/data/project_repository";
|
||||
import {
|
||||
CreateProjectInstancePath,
|
||||
CreateProjectInstanceScreen,
|
||||
} from "../../features/create_project_instance/create_project_instance";
|
||||
import { SceneManger, SceneManagerPath } from "../../features/scene_manager/presentation/scene_manager";
|
||||
|
||||
const idURL = ":id";
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
{
|
||||
path: AllProjectScreenPath,
|
||||
loader: new ProjectRepository().loader,
|
||||
element: <AllProjectScreen />,
|
||||
},
|
||||
{
|
||||
|
@ -63,4 +62,8 @@ export const router = createBrowserRouter([
|
|||
path: CreateProjectInstancePath + idURL,
|
||||
element: <CreateProjectInstanceScreen />,
|
||||
},
|
||||
{
|
||||
path: SceneManagerPath + idURL,
|
||||
element: <SceneManger />,
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -1,22 +1,50 @@
|
|||
// TODO(IDONTSUDO): нужно переписать все запросы под BaseStore
|
||||
|
||||
import { NavigateFunction } from "react-router-dom";
|
||||
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;
|
||||
isError = false;
|
||||
|
||||
async loadingHelper<T>(callBack: Promise<Result<any, T>>) {
|
||||
async httpHelper<T>(callBack: Promise<Result<any, T>>) {
|
||||
this.isLoading = true;
|
||||
|
||||
const result = await callBack;
|
||||
if (result.isFailure()) {
|
||||
this.isError = true;
|
||||
this.isLoading = false;
|
||||
this.errorHandingStrategy(result.error);
|
||||
return result.forward();
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
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[] = [];
|
||||
}
|
||||
|
|
|
@ -1,27 +1,16 @@
|
|||
import { redirect } from "react-router-dom";
|
||||
import { ActivePipeline } from "../../../core/model/active_pipiline";
|
||||
import {
|
||||
HttpMethod,
|
||||
HttpRepository,
|
||||
} from "../../../core/repository/http_repository";
|
||||
import { PipelineInstanceScreenPath } from "../../pipeline_instance_main_screen/pipeline_instance_screen";
|
||||
import { ActivePipeline } from "../../../core/model/active_pipeline";
|
||||
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
|
||||
import { IProjectModel } from "../model/project_model";
|
||||
|
||||
export class ProjectRepository extends HttpRepository {
|
||||
async getAllProject() {
|
||||
return this.jsonRequest<IProjectModel[]>(HttpMethod.GET, "/project");
|
||||
return this._jsonRequest<IProjectModel[]>(HttpMethod.GET, "/project_instance");
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,13 +4,18 @@ import { ProjectRepository } from "../data/project_repository";
|
|||
import { LoadPage } from "../../../core/ui/pages/load_page";
|
||||
import { observer } from "mobx-react-lite";
|
||||
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 AllProjectScreen: React.FunctionComponent = observer(() => {
|
||||
const [allProjectStore] = React.useState(
|
||||
() => new AllProjectStore(new ProjectRepository())
|
||||
);
|
||||
const [allProjectStore] = React.useState(() => new AllProjectStore(new ProjectRepository()));
|
||||
const navigate = useNavigate();
|
||||
|
||||
React.useEffect(() => {
|
||||
allProjectStore.init();
|
||||
}, [allProjectStore]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -23,8 +28,33 @@ export const AllProjectScreen: React.FunctionComponent = observer(() => {
|
|||
isLoading={allProjectStore.isLoading}
|
||||
children={
|
||||
<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) => {
|
||||
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>
|
||||
}
|
||||
|
|
|
@ -1,24 +1,49 @@
|
|||
import makeAutoObservable from "mobx-store-inheritance";
|
||||
import { ProjectRepository } from "../data/project_repository";
|
||||
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[];
|
||||
repository: ProjectRepository;
|
||||
redirect = false;
|
||||
activePipeline?: ActivePipeline;
|
||||
constructor(repository: ProjectRepository) {
|
||||
super();
|
||||
this.repository = repository;
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
async getProjects() {
|
||||
const result = await this.loadingHelper(this.repository.getAllProject());
|
||||
if (result.isSuccess()) {
|
||||
this.projectsModels = result.value;
|
||||
}
|
||||
async getProjects(): Promise<void> {
|
||||
await this.mapOk<IProjectModel[]>("projectsModels", this.repository.getAllProject());
|
||||
}
|
||||
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,18 @@
|
|||
import {
|
||||
HttpMethod,
|
||||
HttpRepository,
|
||||
} from "../../../core/repository/http_repository";
|
||||
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
|
||||
import { ITriggerModel } from "../../../core/model/trigger_model";
|
||||
import { Result } from "../../../core/helper/result";
|
||||
import { IProcess } from "../../create_process/model/process_model";
|
||||
import { PipelineModelDataBase } from "../model/pipeline_model";
|
||||
|
||||
export class CreatePipelineRepository extends HttpRepository {
|
||||
async savePipeline(
|
||||
model: PipelineModelDataBase
|
||||
): Promise<Result<Error, any>> {
|
||||
return await this.jsonRequest(HttpMethod.POST, `/pipeline`, model);
|
||||
async savePipeline(model: PipelineModelDataBase): Promise<Result<Error, any>> {
|
||||
return await this._jsonRequest(HttpMethod.POST, `/pipeline`, model);
|
||||
}
|
||||
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[]>> {
|
||||
return await this.jsonRequest<IProcess[]>(
|
||||
HttpMethod.GET,
|
||||
`/process?${page}`
|
||||
);
|
||||
return await this._jsonRequest<IProcess[]>(HttpMethod.GET, `/process?${page}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import * as React from "react";
|
||||
import { Row, Button } from "antd";
|
||||
import { LoadPage } from "../../../core/ui/pages/load_page";
|
||||
import { createPipelineStore } from "./create_pipeline_store";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Icon, List } from "../../../core/ui/list/list";
|
||||
import { CreateTriggerScreenPath } from "../../create_trigger/presentation/create_trigger_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 CreatePipelineScreen: React.FunctionComponent = observer(() => {
|
||||
const [createPipelineStore] = React.useState(() => new CreatePipelineStore(new CreatePipelineRepository()));
|
||||
|
||||
React.useEffect(() => {}, [createPipelineStore]);
|
||||
return (
|
||||
<>
|
||||
<LoadPage
|
||||
|
@ -29,9 +34,7 @@ export const CreatePipelineScreen: React.FunctionComponent = observer(() => {
|
|||
icon={Icon.add}
|
||||
/>
|
||||
<div style={{ flexGrow: "1" }}>
|
||||
<Button onClick={() => createPipelineStore.createPipeline()}>
|
||||
Save result
|
||||
</Button>
|
||||
<Button onClick={() => createPipelineStore.createPipeline()}>Save result</Button>
|
||||
<List
|
||||
headers="new pipeline"
|
||||
values={createPipelineStore.pipelineViewModels}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { CreatePipelineRepository } from "../data/create_pipeline_repository";
|
|||
import { ITriggerModel } from "../../../core/model/trigger_model";
|
||||
import { IProcess } from "../../create_process/model/process_model";
|
||||
import { message } from "antd";
|
||||
import { BaseStore } from "../../../core/store/base_store";
|
||||
import { SimpleErrorState } from "../../../core/store/base_store";
|
||||
|
||||
enum Type {
|
||||
PROCESS,
|
||||
|
@ -16,7 +16,7 @@ export interface UnionView {
|
|||
uuid?: string;
|
||||
}
|
||||
|
||||
export class CreatePipelineStore extends BaseStore {
|
||||
export class CreatePipelineStore extends SimpleErrorState {
|
||||
repository: CreatePipelineRepository;
|
||||
triggersModels: ITriggerModel[] = [];
|
||||
processModels: IProcess[] = [];
|
||||
|
@ -34,9 +34,7 @@ export class CreatePipelineStore extends BaseStore {
|
|||
}
|
||||
|
||||
filterPipelineViewModel(index: number): void {
|
||||
this.pipelineViewModels = this.pipelineViewModels.filter(
|
||||
(_el, i) => i !== index
|
||||
);
|
||||
this.pipelineViewModels = this.pipelineViewModels.filter((_el, i) => i !== index);
|
||||
}
|
||||
addTrigger(e: string, id: string): void {
|
||||
const lastElement = this.pipelineViewModels.lastElement();
|
||||
|
@ -82,12 +80,8 @@ export class CreatePipelineStore extends BaseStore {
|
|||
message.error("not found pipelines process");
|
||||
return;
|
||||
}
|
||||
const triggerId = this.pipelineViewModels.find(
|
||||
(el) => el.type === Type.TRIGGER
|
||||
)!.uuid as string;
|
||||
const processId = this.pipelineViewModels.find(
|
||||
(el) => el.type === Type.PROCESS
|
||||
)!.uuid as string;
|
||||
const triggerId = this.pipelineViewModels.find((el) => el.type === Type.TRIGGER)!.uuid as string;
|
||||
const processId = this.pipelineViewModels.find((el) => el.type === Type.PROCESS)!.uuid as string;
|
||||
|
||||
this.repository.savePipeline({
|
||||
process: processId,
|
||||
|
@ -96,33 +90,10 @@ export class CreatePipelineStore extends BaseStore {
|
|||
}
|
||||
|
||||
async loadProcess() {
|
||||
this.isLoading = true;
|
||||
const result = await this.repository.getProcessed();
|
||||
result.fold(
|
||||
(s) => {
|
||||
this.processModels = s;
|
||||
},
|
||||
(_e) => {
|
||||
this.isError = true;
|
||||
}
|
||||
);
|
||||
this.isLoading = false;
|
||||
this.mapOk("processModels", this.repository.getProcessed());
|
||||
}
|
||||
|
||||
async loadTriggers() {
|
||||
this.isLoading = true;
|
||||
const result = await this.repository.getTriggers(1);
|
||||
result.fold(
|
||||
(s) => {
|
||||
this.triggersModels = s;
|
||||
},
|
||||
(_e) => {
|
||||
this.isError = true;
|
||||
}
|
||||
);
|
||||
this.isLoading = false;
|
||||
this.mapOk("triggersModels", this.repository.getTriggers());
|
||||
}
|
||||
}
|
||||
export const createPipelineStore = new CreatePipelineStore(
|
||||
new CreatePipelineRepository()
|
||||
);
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import {
|
||||
HttpMethod,
|
||||
HttpRepository,
|
||||
} from "../../../core/repository/http_repository";
|
||||
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
|
||||
import { IProcess } from "../model/process_model";
|
||||
|
||||
export class ProcessRepository extends HttpRepository {
|
||||
async save(model: IProcess): Promise<void> {
|
||||
await this.jsonRequest(HttpMethod.POST, "/process", model);
|
||||
await this._jsonRequest(HttpMethod.POST, "/process", model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
import * as React from "react";
|
||||
import { processStore } from "./logic/process_store";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import {
|
||||
SubmitButton,
|
||||
Input,
|
||||
ResetButton,
|
||||
Form,
|
||||
Radio,
|
||||
Switch,
|
||||
} from "formik-antd";
|
||||
import { SubmitButton, Input, ResetButton, Form, Radio, Switch } from "formik-antd";
|
||||
import { Formik } from "formik";
|
||||
import { Row, Col } from "antd";
|
||||
import { EXEC_TYPE, IssueType, processModelMock } from "../model/process_model";
|
||||
export const CreateProcessScreenPath = '/create/process'
|
||||
export const CreateProcessScreenPath = "/create/process";
|
||||
export const CreateProcessScreen = observer(() => {
|
||||
return (
|
||||
<div>
|
||||
|
@ -49,22 +42,11 @@ export const CreateProcessScreen = observer(() => {
|
|||
|
||||
<Radio.Group name="type" options={Object.values(EXEC_TYPE)} />
|
||||
|
||||
<Radio.Group
|
||||
name="issueType"
|
||||
options={Object.values(IssueType)}
|
||||
/>
|
||||
<Radio.Group name="issueType" options={Object.values(IssueType)} />
|
||||
|
||||
<Col style={{ marginTop: 20, justifyContent: "center" }}>
|
||||
<Switch
|
||||
name="isGenerating"
|
||||
checkedChildren="is generating"
|
||||
unCheckedChildren="is generating"
|
||||
/>
|
||||
<Switch
|
||||
name="isLocalCode"
|
||||
checkedChildren="is local code"
|
||||
unCheckedChildren="is local code"
|
||||
/>
|
||||
<Switch name="isGenerating" checkedChildren="is generating" unCheckedChildren="is generating" />
|
||||
<Switch name="isLocalCode" checkedChildren="is local code" unCheckedChildren="is local code" />
|
||||
</Col>
|
||||
|
||||
<Row style={{ marginTop: 20, justifyContent: "center" }}>
|
||||
|
|
|
@ -8,8 +8,8 @@ class ProcessStore {
|
|||
this.repository = repository;
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
async saveResult(model:IProcess) {
|
||||
await this.repository.save(model)
|
||||
async saveResult(model: IProcess) {
|
||||
await this.repository.save(model);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { Result } from "../../core/helper/result";
|
||||
import { DatabaseModel } from "../../core/model/database_model";
|
||||
import { ITriggerModel } from "../../core/model/trigger_model";
|
||||
import {
|
||||
HttpMethod,
|
||||
HttpRepository,
|
||||
} from "../../core/repository/http_repository";
|
||||
import { HttpMethod, HttpRepository } from "../../core/repository/http_repository";
|
||||
import { IProcess } from "../create_process/model/process_model";
|
||||
import { ICreateProjectViewModel } from "./project_model";
|
||||
|
||||
|
@ -15,11 +12,9 @@ export interface PipelineModel extends DatabaseModel {
|
|||
|
||||
export class CreateProjectRepository extends HttpRepository {
|
||||
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(
|
||||
model: ICreateProjectViewModel
|
||||
): Promise<Result<Error, void>> {
|
||||
return await this.jsonRequest<void>(HttpMethod.POST, "/project", model);
|
||||
async saveProject(model: ICreateProjectViewModel): Promise<Result<Error, void>> {
|
||||
return await this._jsonRequest<void>(HttpMethod.POST, "/project", model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
import * as React from "react";
|
||||
import { LoadPage } from "../../core/ui/pages/load_page";
|
||||
import { createProjectStore } from "./create_project_store";
|
||||
import { LoadPage as MainPage } from "../../core/ui/pages/load_page";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Col, Row, Input, Button } from "antd";
|
||||
import { ReactComponent as AddIcon } from "../../core/assets/icons/add.svg";
|
||||
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 CreateProjectScreen: React.FunctionComponent = observer(() => {
|
||||
const [createProjectStore] = React.useState(() => new CreateProjectStore(new CreateProjectRepository()));
|
||||
|
||||
React.useEffect(() => {
|
||||
createProjectStore.init();
|
||||
}, [createProjectStore]);
|
||||
return (
|
||||
<>
|
||||
<LoadPage
|
||||
<MainPage
|
||||
path={CreatePipelineScreenPath}
|
||||
largeText={"Create project"}
|
||||
minText={"add new pipelines?"}
|
||||
|
@ -48,21 +54,14 @@ export const CreateProjectScreen: React.FunctionComponent = observer(() => {
|
|||
})}
|
||||
</Col>
|
||||
<Col>
|
||||
|
||||
<Row>
|
||||
<Input
|
||||
style={{ width: "250px" }}
|
||||
onChange={(e) =>
|
||||
createProjectStore.setDescriptionToNewProject(
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
onChange={(e) => createProjectStore.setDescriptionToNewProject(e.target.value)}
|
||||
placeholder="project description"
|
||||
/>
|
||||
|
||||
<Button onClick={() => createProjectStore.saveProject()}>
|
||||
save
|
||||
</Button>
|
||||
<Button onClick={() => createProjectStore.saveProject()}>save</Button>
|
||||
</Row>
|
||||
|
||||
{createProjectStore.newProjectViews.map((el, index) => {
|
||||
|
@ -87,4 +86,3 @@ export const CreateProjectScreen: React.FunctionComponent = observer(() => {
|
|||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
import makeAutoObservable from "mobx-store-inheritance";
|
||||
import {
|
||||
CreateProjectRepository,
|
||||
PipelineModel,
|
||||
} from "./create_project_repository";
|
||||
import { CreateProjectRepository, PipelineModel } from "./create_project_repository";
|
||||
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;
|
||||
|
||||
pipelineModels?: PipelineModel[];
|
||||
|
@ -17,25 +14,16 @@ class CreateProjectStore extends BaseStore {
|
|||
super();
|
||||
this.repository = repository;
|
||||
makeAutoObservable(this);
|
||||
this.loadPipelines();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.loadPipelines();
|
||||
}
|
||||
async addPipeline(model: PipelineModel) {
|
||||
this.newProjectViews.push(model);
|
||||
}
|
||||
|
||||
async loadPipelines() {
|
||||
this.isLoading = true;
|
||||
const result = await this.repository.getAllPipelines();
|
||||
result.fold(
|
||||
(s) => {
|
||||
this.pipelineModels = s;
|
||||
},
|
||||
(_e) => {
|
||||
this.isError = true;
|
||||
}
|
||||
);
|
||||
this.isLoading = false;
|
||||
this.mapOk("pipelineModels", this.repository.getAllPipelines());
|
||||
}
|
||||
|
||||
setDescriptionToNewProject(value: string): void {
|
||||
|
@ -71,7 +59,3 @@ class CreateProjectStore extends BaseStore {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const createProjectStore = new CreateProjectStore(
|
||||
new CreateProjectRepository()
|
||||
);
|
||||
|
|
|
@ -3,7 +3,8 @@ import { CreateProjectInstanceStore } from "./create_project_instance_store";
|
|||
import { CreateProjectInstanceRepository } from "./create_project_instance_repository";
|
||||
import { observer } from "mobx-react-lite";
|
||||
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/";
|
||||
|
||||
|
@ -12,16 +13,25 @@ export const CreateProjectInstanceScreen = observer(() => {
|
|||
() => new CreateProjectInstanceStore(new CreateProjectInstanceRepository())
|
||||
);
|
||||
const id = useParams().id;
|
||||
createProjectInstanceStore.getProjectById(id as string)
|
||||
const navigate = useNavigate();
|
||||
|
||||
React.useEffect(() => {
|
||||
createProjectInstanceStore.init(navigate, id as string);
|
||||
}, [id, createProjectInstanceStore, navigate]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>project description</h1>
|
||||
<Input onChange={(e) => createProjectInstanceStore.setProjectDescription(e.target.value)}></Input>
|
||||
<h1>root entity</h1>
|
||||
<Upload
|
||||
onChange={(e) => {
|
||||
console.log(e);
|
||||
createProjectInstanceStore.file = e.file.originFileObj;
|
||||
}}
|
||||
>
|
||||
<Button>Upload root entity</Button>
|
||||
</Upload>
|
||||
<Button onClick={() => createProjectInstanceStore.saveInstance()}>Save</Button>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import {
|
||||
HttpMethod,
|
||||
HttpRepository,
|
||||
} from "../../core/repository/http_repository";
|
||||
import { HttpMethod, HttpRepository } from "../../core/repository/http_repository";
|
||||
import { NewProjectModel } from "./new_project_model";
|
||||
|
||||
export class CreateProjectInstanceRepository extends HttpRepository {
|
||||
async getProjectInstance(id: string) {
|
||||
return await this.jsonRequest(HttpMethod.GET, "");
|
||||
async setProjectRootFile(file: File) {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,39 @@
|
|||
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 { 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) {
|
||||
super();
|
||||
this.repository = repository;
|
||||
makeAutoObservable(this);
|
||||
this.newProjectModel = NewProjectModel.empty();
|
||||
}
|
||||
repository: CreateProjectInstanceRepository;
|
||||
async getProjectById(id: string) {
|
||||
const result = await this.loadingHelper(this.repository.getProjectInstance(id))
|
||||
if(result.isSuccess()){
|
||||
|
||||
|
||||
setProjectDescription(value: string): void {
|
||||
this.newProjectModel.description = value;
|
||||
}
|
||||
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("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
ui/src/features/create_project_instance/new_project_model.ts
Normal file
17
ui/src/features/create_project_instance/new_project_model.ts
Normal 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 "";
|
||||
}
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
import {
|
||||
HttpMethod,
|
||||
HttpRepository,
|
||||
} from "../../../core/repository/http_repository";
|
||||
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
|
||||
import { ITriggerModel } from "../../../core/model/trigger_model";
|
||||
|
||||
export class TriggerRepository extends HttpRepository {
|
||||
public async save(model: ITriggerModel) {
|
||||
return await this.jsonRequest(HttpMethod.POST, "/trigger", model);
|
||||
return await this._jsonRequest(HttpMethod.POST, "/trigger", model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,36 +2,40 @@ import * as React from "react";
|
|||
import Editor from "@monaco-editor/react";
|
||||
import { Button } from "antd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { triggerStore } from "../trigger_store";
|
||||
import { CallBackStringVoidFunction } from "../../../../core/extensions/extensions";
|
||||
|
||||
export const CodeTriggerForm: React.FunctionComponent = observer(() => {
|
||||
return (
|
||||
<>
|
||||
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
|
||||
interface ICodeTriggerFormProps {
|
||||
codeTriggerValue: string;
|
||||
clearTriggerCode: VoidFunction;
|
||||
saveCode: VoidFunction;
|
||||
writeNewTrigger: CallBackStringVoidFunction;
|
||||
}
|
||||
|
||||
<Editor
|
||||
height="40vh"
|
||||
defaultLanguage="javascript"
|
||||
value={triggerStore.codeTriggerValue}
|
||||
onChange={(v) => {
|
||||
triggerStore.writeNewTrigger(v);
|
||||
}}
|
||||
onValidate={(_m) => {}}
|
||||
/>
|
||||
export const CodeTriggerForm: React.FunctionComponent<ICodeTriggerFormProps> = observer(
|
||||
(props: ICodeTriggerFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
|
||||
|
||||
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
|
||||
<div style={{ height: "10px" }} />
|
||||
<Editor
|
||||
height="40vh"
|
||||
defaultLanguage="javascript"
|
||||
value={props.codeTriggerValue}
|
||||
onChange={(v) => {
|
||||
props.writeNewTrigger(v ?? "");
|
||||
}}
|
||||
onValidate={(_m) => {}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={() => triggerStore.saveCode()}
|
||||
style={{ marginLeft: "10px", marginRight: "10px" }}
|
||||
>
|
||||
Save code
|
||||
</Button>
|
||||
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
|
||||
<div style={{ height: "10px" }} />
|
||||
|
||||
<Button onClick={() => triggerStore.clearTriggerCode()}>
|
||||
Reset code
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
});
|
||||
<Button onClick={() => props.saveCode()} style={{ marginLeft: "10px", marginRight: "10px" }}>
|
||||
Save code
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => props.clearTriggerCode()}>Reset code</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,50 +3,49 @@ import { Formik } from "formik";
|
|||
import { SubmitButton, Input, ResetButton, Form, FormItem } from "formik-antd";
|
||||
import { Row, Col } from "antd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { triggerStore } from "../trigger_store";
|
||||
import { TriggerType } from "../../../../core/model/trigger_model";
|
||||
import { validateRequired } from "../../../../core/helper/validate";
|
||||
|
||||
export const FileTriggerForm: React.FunctionComponent = observer(() => {
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginTop: 80 }}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
value: "",
|
||||
}}
|
||||
onSubmit={(values, actions) => {
|
||||
triggerStore.pushTrigger(values.value, TriggerType.FILE);
|
||||
actions.setSubmitting(false);
|
||||
actions.resetForm();
|
||||
}}
|
||||
validate={(values) => {
|
||||
if (values.value.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return {};
|
||||
}}
|
||||
render={() => (
|
||||
<Form>
|
||||
<div style={{ background: "white", flex: 1, padding: 40 }}>
|
||||
<FormItem
|
||||
name="value"
|
||||
required={true}
|
||||
validate={validateRequired}
|
||||
>
|
||||
<Input name="value" placeholder="regExp file" />
|
||||
</FormItem>
|
||||
<Row style={{ marginTop: 60 }}>
|
||||
<Col offset={8}>
|
||||
<ResetButton>Reset</ResetButton>
|
||||
<SubmitButton>Submit</SubmitButton>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
export interface IFileTriggerFormProps {
|
||||
pushTrigger: (value: string, type: TriggerType) => void;
|
||||
}
|
||||
export const FileTriggerForm: React.FunctionComponent<IFileTriggerFormProps> = observer(
|
||||
(props: IFileTriggerFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginTop: 80 }}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
value: "",
|
||||
}}
|
||||
onSubmit={(values, actions) => {
|
||||
props.pushTrigger(values.value, TriggerType.FILE);
|
||||
actions.setSubmitting(false);
|
||||
actions.resetForm();
|
||||
}}
|
||||
validate={(values) => {
|
||||
if (values.value.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return {};
|
||||
}}
|
||||
render={() => (
|
||||
<Form>
|
||||
<div style={{ background: "white", flex: 1, padding: 40 }}>
|
||||
<FormItem name="value" required={true} validate={validateRequired}>
|
||||
<Input name="value" placeholder="regExp file" />
|
||||
</FormItem>
|
||||
<Row style={{ marginTop: 60 }}>
|
||||
<Col offset={8}>
|
||||
<ResetButton>Reset</ResetButton>
|
||||
<SubmitButton>Submit</SubmitButton>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,35 +2,20 @@ import * as React from "react";
|
|||
import { Button, Col, Row, Switch, Typography, Input } from "antd";
|
||||
import { CodeTriggerForm } from "./components/code_trigger_form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { triggerStore } from "./trigger_store";
|
||||
import { FileTriggerForm } from "./components/file_trigger_form";
|
||||
import { ReactComponent as DeleteIcon } from "../../../core/assets/icons/delete.svg";
|
||||
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 Header = observer(() => {
|
||||
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(() => {
|
||||
const Bottom = observer((props: { triggers: TriggerViewModel[]; callBack: CallBackStringVoidFunction }) => {
|
||||
return (
|
||||
<Col>
|
||||
{triggerStore.triggers.map((el) => {
|
||||
{props.triggers.map((el) => {
|
||||
return (
|
||||
<Row
|
||||
style={{
|
||||
|
@ -38,40 +23,50 @@ const Bottom = observer(() => {
|
|||
}}
|
||||
>
|
||||
{el.value}
|
||||
<DeleteIcon onClick={() => triggerStore.deleteItem(el.id)} />
|
||||
<DeleteIcon onClick={() => props.callBack(el.id)} />
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</Col>
|
||||
);
|
||||
});
|
||||
export const CreateTriggerScreenPath = '/create/trigger'
|
||||
export const CreateTriggerScreenPath = "/create/trigger";
|
||||
|
||||
export const TriggerScreen: React.FunctionComponent = observer(() => {
|
||||
const [triggerStore] = React.useState(() => new TriggerStore(new TriggerRepository()));
|
||||
return (
|
||||
<>
|
||||
<main>
|
||||
{!triggerStore.isLoading ? (
|
||||
<>
|
||||
<Header />,
|
||||
<Input
|
||||
placeholder="trigger description"
|
||||
onChange={() => triggerStore.changeTriggerDescription}
|
||||
/>
|
||||
<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>
|
||||
<Input placeholder="trigger description" onChange={() => triggerStore.changeTriggerDescription} />
|
||||
{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>
|
||||
|
|
|
@ -3,9 +3,9 @@ import { v4 as uuidv4 } from "uuid";
|
|||
import { TriggerType } from "../../../core/model/trigger_model";
|
||||
import { TriggerRepository } from "../data/trigger_repository";
|
||||
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) {
|
||||
super();
|
||||
this.triggerType = TriggerType.FILE;
|
||||
|
@ -39,9 +39,7 @@ class TriggerStore extends BaseStore {
|
|||
this.triggerType = TriggerType.FILE;
|
||||
};
|
||||
getTriggerDescription = (): string => {
|
||||
return this.triggerType === TriggerType.FILE
|
||||
? TriggerType.FILE
|
||||
: TriggerType.PROCESS;
|
||||
return this.triggerType === TriggerType.FILE ? TriggerType.FILE : TriggerType.PROCESS;
|
||||
};
|
||||
pushTrigger = (value: string, type: TriggerType): void => {
|
||||
this.triggers.push({
|
||||
|
@ -72,16 +70,15 @@ class TriggerStore extends BaseStore {
|
|||
}
|
||||
}
|
||||
async saveResult(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
await this.repository.save({
|
||||
type: this.getTriggerDescription(),
|
||||
description: this.triggerDescription,
|
||||
value: this.triggers.map((el) => {
|
||||
return el.value;
|
||||
}),
|
||||
});
|
||||
this.isLoading = false;
|
||||
await this.httpHelper(
|
||||
this.repository.save({
|
||||
type: this.getTriggerDescription(),
|
||||
description: this.triggerDescription,
|
||||
value: this.triggers.map((el) => {
|
||||
return el.value;
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const triggerStore = new TriggerStore(new TriggerRepository());
|
||||
|
|
68
ui/src/features/p.tsx
Normal file
68
ui/src/features/p.tsx
Normal 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>
|
||||
// );
|
||||
// };
|
|
@ -4,13 +4,12 @@ import { PipelineInstanceStore } from "./pipeline_instance_store";
|
|||
|
||||
export const PipelineInstanceScreenPath = "/pipeline_instance/";
|
||||
export const PipelineInstanceScreen: React.FunctionComponent = () => {
|
||||
const [pipelineInstanceStore] = React.useState(
|
||||
() => new PipelineInstanceStore()
|
||||
);
|
||||
|
||||
const [pipelineInstanceStore] = React.useState(() => new PipelineInstanceStore());
|
||||
React.useEffect(() => {}, [pipelineInstanceStore]);
|
||||
return (
|
||||
<LoadPage
|
||||
needBackButton={false}
|
||||
needBackButton={true}
|
||||
largeText={"Project instance active"}
|
||||
isError={pipelineInstanceStore.isError}
|
||||
isLoading={pipelineInstanceStore.isLoading}
|
||||
children={<div></div>}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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() {
|
||||
super();
|
||||
makeAutoObservable(this);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
19
ui/src/features/scene_manager/data/scene_repository.ts
Normal file
19
ui/src/features/scene_manager/data/scene_repository.ts
Normal 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>
|
||||
>;
|
||||
}
|
||||
}
|
160
ui/src/features/scene_manager/model/robossembler_assets.ts
Normal file
160
ui/src/features/scene_manager/model/robossembler_assets.ts
Normal 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];
|
||||
}
|
||||
}
|
164
ui/src/features/scene_manager/model/scene_assets.ts
Normal file
164
ui/src/features/scene_manager/model/scene_assets.ts
Normal 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("", "", "", "");
|
||||
}
|
||||
}
|
27
ui/src/features/scene_manager/model/scene_view.ts
Normal file
27
ui/src/features/scene_manager/model/scene_view.ts
Normal 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",
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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 <></>;
|
||||
}
|
162
ui/src/features/scene_manager/presentation/scene_manager.tsx
Normal file
162
ui/src/features/scene_manager/presentation/scene_manager.tsx
Normal file
|
@ -0,0 +1,162 @@
|
|||
import * as React from "react";
|
||||
import { SceneMangerStore } from "./scene_manager_store";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { StaticAssetModelView } from "./components/static_asset_item_view";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { SceneManagerView, SceneMode } from "../model/scene_view";
|
||||
import { Button } from "antd";
|
||||
import { Form, Input, ResetButton, SubmitButton } from "formik-antd";
|
||||
import { Formik } from "formik";
|
||||
import { CameraViewModel } from "../model/scene_assets";
|
||||
|
||||
export const SceneManagerPath = "/scene/manager/";
|
||||
|
||||
export const SceneManger = observer(() => {
|
||||
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
||||
const [sceneMangerStore] = React.useState(() => new SceneMangerStore());
|
||||
const id = useParams().id as string;
|
||||
|
||||
React.useEffect(() => {
|
||||
sceneMangerStore.init();
|
||||
sceneMangerStore.loadScene(id, canvasRef.current!);
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.body.style.overflow = "scroll";
|
||||
sceneMangerStore.dispose();
|
||||
};
|
||||
}, [id, sceneMangerStore]);
|
||||
|
||||
const sceneIcons: SceneManagerView[] = [SceneMode.ROTATE, SceneMode.MOVING, SceneMode.ADD_CAMERA].map((el) => {
|
||||
return { name: el, clickHandel: () => sceneMangerStore.setSceneMode(el) };
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} style={{ position: "absolute", overflow: "hidden" }} />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignContent: "center",
|
||||
justifyContent: "space-between",
|
||||
position: "absolute",
|
||||
width: "100vw",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{sceneIcons.map((el) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: sceneMangerStore.sceneMode === el.name ? "aqua" : "ActiveBorder",
|
||||
width: "100px",
|
||||
}}
|
||||
onClick={() => {
|
||||
el.clickHandel();
|
||||
}}
|
||||
>
|
||||
{el.name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
backgroundColor: "GrayText",
|
||||
border: "solid",
|
||||
borderRadius: "10px",
|
||||
padding: "8px",
|
||||
borderColor: "white",
|
||||
}}
|
||||
>
|
||||
<div style={{ color: "white" }}>Scene manager</div>
|
||||
{sceneMangerStore.isVisibleSaveButton ? (
|
||||
<>
|
||||
<Button onClick={() => sceneMangerStore.onTapSave()}>Save</Button>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{sceneMangerStore.isLoading ? <>Loading...</> : <></>}
|
||||
{sceneMangerStore.sceneMode === SceneMode.ADD_CAMERA ? (
|
||||
<div>
|
||||
<Formik
|
||||
initialValues={CameraViewModel.empty()}
|
||||
onSubmit={async (model, actions) => {
|
||||
sceneMangerStore.addNewCamera(model);
|
||||
actions.setSubmitting(false);
|
||||
actions.resetForm();
|
||||
}}
|
||||
validate={(model) => {
|
||||
return model.validate(sceneMangerStore.getCameraLinkNames());
|
||||
}}
|
||||
render={() => (
|
||||
<Form>
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
flex: 1,
|
||||
padding: 40,
|
||||
width: "400px",
|
||||
}}
|
||||
>
|
||||
<Input name="cameraLink" placeholder="Camera link" />
|
||||
<Input name="topicImage" placeholder="Topic Image" />
|
||||
<Input name="topicCameraInfo" placeholder="Topic Camera Info" />
|
||||
<Input name="topicDepth" placeholder="Topic Depth" />
|
||||
|
||||
<ResetButton>Reset</ResetButton>
|
||||
<SubmitButton>Submit</SubmitButton>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{sceneMangerStore.sceneMode === SceneMode.MOVING || SceneMode.ROTATE ? (
|
||||
<>
|
||||
{sceneMangerStore.robossemblerAssets?.assets.map((el) => {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ color: "white", marginLeft: "10px", marginRight: "10px", display: "contents" }}>
|
||||
{el.name}
|
||||
{sceneMangerStore.isRenderedAsset(el.name) ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
sceneMangerStore.loadSceneRobossemblerAsset(el.name);
|
||||
}}
|
||||
>
|
||||
add scene
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{sceneMangerStore.sceneModels.map((el) => {
|
||||
return <StaticAssetModelView onTap={() => sceneMangerStore.deleteSceneItem(el)} model={el} />;
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* {sceneMangerStore.sceneMenuIsShow ? (
|
||||
<>
|
||||
<SceneMenu x={sceneMangerStore.sceneMenu.x} y={sceneMangerStore.sceneMenu.y} />
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -0,0 +1,179 @@
|
|||
import makeAutoObservable from "mobx-store-inheritance";
|
||||
import { CoreThreeRepository } from "../../../core/repository/core_three_repository";
|
||||
import { Object3D, Vector2 } from "three";
|
||||
import { HttpError } from "../../../core/repository/http_repository";
|
||||
import { UiErrorState } from "../../../core/store/base_store";
|
||||
import { UiBaseError } from "../../../core/model/ui_base_error";
|
||||
import { SceneMenu, SceneMode } from "../model/scene_view";
|
||||
import { BaseSceneItemModel, CameraViewModel, RobossemblerFiles, StaticAssetItemModel } from "../model/scene_assets";
|
||||
import { SceneHttpRepository } from "../data/scene_repository";
|
||||
import { message } from "antd";
|
||||
import { RobossemblerAssets } from "../model/robossembler_assets";
|
||||
|
||||
export class SceneMangerStore extends UiErrorState<HttpError> {
|
||||
sceneMode: SceneMode;
|
||||
sceneMenu: SceneMenu;
|
||||
isVisibleSaveButton: boolean = false;
|
||||
coreThereRepository: null | CoreThreeRepository = null;
|
||||
sceneHttpRepository: SceneHttpRepository;
|
||||
sceneModels: BaseSceneItemModel[] = [];
|
||||
isSceneMenuShow = false;
|
||||
robossemblerAssets?: RobossemblerAssets;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
makeAutoObservable(this);
|
||||
this.sceneHttpRepository = new SceneHttpRepository();
|
||||
this.sceneMode = SceneMode.EMPTY;
|
||||
this.sceneMenu = SceneMenu.empty();
|
||||
}
|
||||
|
||||
onTapSave(): void {
|
||||
this.robossemblerAssets!.instances = [];
|
||||
this.sceneModels.forEach((el) => this.robossemblerAssets?.instances.push(el.toInstance()));
|
||||
this.httpHelper(this.sceneHttpRepository.saveScene(this.robossemblerAssets as RobossemblerAssets));
|
||||
this.isVisibleSaveButton = false;
|
||||
}
|
||||
|
||||
deleteSceneItem(item: BaseSceneItemModel) {
|
||||
const itm = this.sceneModels.filter((el) => el.id === item.id);
|
||||
this.coreThereRepository!.deleteSceneItem(itm[0]);
|
||||
this.sceneModels = this.sceneModels.filter((el) => el.name !== item.name);
|
||||
this.visibleSaveButton();
|
||||
}
|
||||
|
||||
visibleSaveButton() {
|
||||
this.isVisibleSaveButton = true;
|
||||
}
|
||||
|
||||
addNewCamera(model: CameraViewModel) {
|
||||
model.position = this.coreThereRepository!.camera.position;
|
||||
model.quaternion = this.coreThereRepository!.camera.quaternion;
|
||||
this.sceneModels.push(model);
|
||||
this.coreThereRepository?.addSceneCamera(model);
|
||||
this.visibleSaveButton();
|
||||
}
|
||||
|
||||
getCameraLinkNames(): string[] {
|
||||
return this.sceneModels.map((el) => {
|
||||
if (el instanceof CameraViewModel) {
|
||||
return el.cameraLink;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
loaderWatcher() {}
|
||||
|
||||
loadSceneRobossemblerAsset(name: string) {
|
||||
try {
|
||||
const assetPath = this.robossemblerAssets?.getAssetPath(name) as string;
|
||||
this.coreThereRepository?.loader(assetPath, this.loaderWatcher, name);
|
||||
this.visibleSaveButton();
|
||||
} catch (error) {
|
||||
message.error(String(error));
|
||||
}
|
||||
}
|
||||
|
||||
isRenderedAsset(name: string): boolean {
|
||||
return this.sceneModels
|
||||
.filter((el) => {
|
||||
if (el instanceof StaticAssetItemModel) {
|
||||
return el.name === name;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.isNotEmpty();
|
||||
}
|
||||
|
||||
hiddenMenu() {
|
||||
this.isSceneMenuShow = false;
|
||||
}
|
||||
|
||||
setSceneMode = (mode: SceneMode) => {
|
||||
if (this.sceneMode === undefined || this.sceneMode !== mode) {
|
||||
this.sceneMode = mode;
|
||||
} else if (this.sceneMode === mode) {
|
||||
this.sceneMode = SceneMode.EMPTY;
|
||||
}
|
||||
this.coreThereRepository?.setTransformMode(this.sceneMode);
|
||||
this.sceneModeWatcher();
|
||||
};
|
||||
|
||||
sceneModeWatcher() {}
|
||||
|
||||
async init(): Promise<any> {}
|
||||
|
||||
errorHandingStrategy = (error: HttpError) => {
|
||||
if (error.status === 404) {
|
||||
this.errors.push(new UiBaseError(`${RobossemblerFiles.robossemblerAssets} not found to project`));
|
||||
}
|
||||
};
|
||||
|
||||
async loadScene(sceneId: string, canvasRef: HTMLCanvasElement) {
|
||||
this.loadWebGl(canvasRef);
|
||||
await this.mapOk<RobossemblerAssets>("robossemblerAssets", this.sceneHttpRepository.getRobossemblerAssets());
|
||||
if (this.robossemblerAssets) {
|
||||
this.coreThereRepository?.loadInstances(this.robossemblerAssets);
|
||||
}
|
||||
}
|
||||
|
||||
loadWebGl(canvasRef: HTMLCanvasElement): void {
|
||||
this.coreThereRepository = new CoreThreeRepository(canvasRef as HTMLCanvasElement, this.watcherSceneEditorObject);
|
||||
this.coreThereRepository.on(this.watcherThereObjects);
|
||||
this.coreThereRepository.render();
|
||||
this.sceneModels = this.coreThereRepository.getAllSceneModels();
|
||||
|
||||
window.addEventListener("click", (event) => this.clickLister(event));
|
||||
window.addEventListener("mousedown", (e) => this.sceneContextMenu(e));
|
||||
}
|
||||
|
||||
clickLister(event: MouseEvent) {
|
||||
if (this.sceneMode === SceneMode.EMPTY) {
|
||||
return;
|
||||
}
|
||||
if (this.sceneMode === SceneMode.MOVING || this.sceneMode === SceneMode.ROTATE) {
|
||||
const vector = new Vector2();
|
||||
vector.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
vector.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
|
||||
this.transformContollsCall(vector);
|
||||
}
|
||||
}
|
||||
|
||||
sceneContextMenu(e: MouseEvent): void {
|
||||
if (e.button === 2) {
|
||||
this.isSceneMenuShow = true;
|
||||
this.sceneMenu.x = e.clientX;
|
||||
this.sceneMenu.y = e.clientY;
|
||||
}
|
||||
}
|
||||
|
||||
watcherThereObjects = (sceneItemModel: BaseSceneItemModel): void => {
|
||||
this.sceneModels.push(sceneItemModel);
|
||||
};
|
||||
|
||||
watcherSceneEditorObject = (mesh: Object3D) => {
|
||||
this.sceneModels = this.sceneModels.map((el) => {
|
||||
if (el.name === mesh.name) {
|
||||
el.position = mesh.position;
|
||||
el.quaternion = mesh.quaternion;
|
||||
return el;
|
||||
}
|
||||
return el;
|
||||
});
|
||||
this.visibleSaveButton();
|
||||
};
|
||||
|
||||
transformContollsCall = (vector: Vector2) => {
|
||||
this.coreThereRepository?.setRayCastAndGetFirstObject(vector).fold(
|
||||
(success) => this.coreThereRepository?.setTransformControlsAttach(success),
|
||||
(_error) => this.coreThereRepository?.disposeTransformControlsMode()
|
||||
);
|
||||
};
|
||||
|
||||
dispose() {
|
||||
window.removeEventListener("click", this.clickLister);
|
||||
window.removeEventListener("mousedown", (e) => this.sceneContextMenu(e));
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { SceneMangerStore, StaticAssetItemModel } from "./scene_manager_store";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { StaticAssetModelView } from "./components/static_asset_item_view";
|
||||
|
||||
// const useKeyLister = (fn: Function) => {
|
||||
// const pressed = new Map();
|
||||
|
||||
// const registerKeyPress = React.useCallback(
|
||||
// (event: KeyboardEvent, codes: string[], callBack: Function) => {
|
||||
// if (codes.hasIncludeElement(event.code)) {
|
||||
// pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
|
||||
// if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
|
||||
// callBack();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// [pressed]
|
||||
// );
|
||||
|
||||
// React.useEffect(() => {
|
||||
// window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
|
||||
// window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
|
||||
// }, [fn, registerKeyPress]);
|
||||
|
||||
// return [];
|
||||
// };
|
||||
|
||||
export const SceneManger = observer(() => {
|
||||
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const [sceneMangerStore] = React.useState(() => new SceneMangerStore());
|
||||
|
||||
React.useEffect(() => {
|
||||
sceneMangerStore.loadGl(canvasRef.current!);
|
||||
return () => {
|
||||
sceneMangerStore.dispose();
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ position: "absolute" }}>
|
||||
{sceneMangerStore.sceneItems.map((el) => {
|
||||
if (el instanceof StaticAssetItemModel) {
|
||||
return StaticAssetModelView({ model: el });
|
||||
}
|
||||
return <></>;
|
||||
})}
|
||||
</div>
|
||||
<canvas ref={canvasRef} />
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -1,70 +0,0 @@
|
|||
/* eslint-disable array-callback-return */
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import { CoreThereRepository } from "../../core/repository/core_there_repository";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Vector2 } from "three";
|
||||
|
||||
export class BaseSceneItemModel {
|
||||
id: string;
|
||||
constructor() {
|
||||
this.id = uuidv4();
|
||||
}
|
||||
}
|
||||
|
||||
enum SceneModelsType {
|
||||
ASSET,
|
||||
}
|
||||
|
||||
export class StaticAssetItemModel extends BaseSceneItemModel {
|
||||
name: string;
|
||||
type = SceneModelsType.ASSET;
|
||||
constructor(name: string) {
|
||||
super();
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneMangerStore {
|
||||
coreThereRepository: null | CoreThereRepository = null;
|
||||
sceneItems: BaseSceneItemModel[] = [];
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
loadGl(canvasRef: HTMLCanvasElement): void {
|
||||
this.coreThereRepository = new CoreThereRepository(canvasRef as HTMLCanvasElement);
|
||||
this.coreThereRepository.on(this.watcherThereObjects);
|
||||
this.coreThereRepository.render();
|
||||
this.coreThereRepository.fitCameraToCenteredObject(this.coreThereRepository.getAllSceneNameModels());
|
||||
|
||||
this.sceneItems = this.coreThereRepository.getAllSceneModels();
|
||||
|
||||
window.addEventListener("click", this.handleMouseClick);
|
||||
}
|
||||
|
||||
watcherThereObjects(sceneItemModel: BaseSceneItemModel): void {
|
||||
this.sceneItems.push(sceneItemModel);
|
||||
}
|
||||
|
||||
handleMouseClick = (event: MouseEvent) => {
|
||||
const vector = new Vector2();
|
||||
console.log("====");
|
||||
console.log(event.pageX);
|
||||
console.log(event.clientX);
|
||||
console.log(event.x);
|
||||
console.log(event.movementX);
|
||||
console.log(event.screenX);
|
||||
console.log("====");
|
||||
vector.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
vector.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
|
||||
this.coreThereRepository?.setRayCastAndGetFirstObject(vector).map((el) => {
|
||||
this.coreThereRepository?.switchObjectEmissive(el);
|
||||
});
|
||||
};
|
||||
|
||||
dispose() {
|
||||
window.removeEventListener("click", this.handleMouseClick);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { Result } from "../../../core/helper/result";
|
||||
import {
|
||||
HttpMethod,
|
||||
HttpRepository,
|
||||
} from "../../../core/repository/http_repository";
|
||||
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
|
||||
import { IProjectModel } from "../model/project_model";
|
||||
|
||||
export class SelectProjectRepository extends HttpRepository {
|
||||
async setActiveProject(id: string) {
|
||||
return await this._jsonRequest(HttpMethod.POST, `/project?${id}`);
|
||||
}
|
||||
async getAllProjects(page = 1): Promise<Result<Error, IProjectModel[]>> {
|
||||
return await this.jsonRequest(HttpMethod.GET, `/project?${page}`);
|
||||
return await this._jsonRequest(HttpMethod.GET, `/project?${page}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,19 @@ import { observer } from "mobx-react-lite";
|
|||
import { LoadPage } from "../../../core/ui/pages/load_page";
|
||||
import { CreateProjectScreenPath } from "../../create_project/create_project_screen";
|
||||
import { SelectProjectStore } from "./select_project_store";
|
||||
import { SelectProjectRepository } from "../data/select_project_repository";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CreateProjectInstancePath } from "../../create_project_instance/create_project_instance";
|
||||
import { Button } from "antd";
|
||||
export const SelectProjectScreenPath = "/select_project";
|
||||
|
||||
export const SelectProjectScreen: React.FunctionComponent = observer(() => {
|
||||
const [selectProjectStore] = React.useState(
|
||||
() => new SelectProjectStore(new SelectProjectRepository())
|
||||
);
|
||||
const [selectProjectStore] = React.useState(() => new SelectProjectStore());
|
||||
const navigate = useNavigate();
|
||||
|
||||
React.useEffect(() => {
|
||||
selectProjectStore.init();
|
||||
}, [selectProjectStore, navigate]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<LoadPage
|
||||
|
@ -22,17 +23,13 @@ export const SelectProjectScreen: React.FunctionComponent = observer(() => {
|
|||
largeText={"Select project"}
|
||||
minText={"add new project?"}
|
||||
isLoading={selectProjectStore.isLoading}
|
||||
isError={selectProjectStore.isError}
|
||||
isError={selectProjectStore.errors.isNotEmpty()}
|
||||
children={selectProjectStore.projects.map((el) => {
|
||||
return (
|
||||
<>
|
||||
<div>{el.description}</div>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => navigate(CreateProjectInstancePath + el._id)}
|
||||
>
|
||||
create instance
|
||||
</Button>
|
||||
<Button onClick={() => navigate(CreateProjectInstancePath + el._id)}>create instance</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,33 +1,28 @@
|
|||
import makeAutoObservable from "mobx-store-inheritance";
|
||||
import { SelectProjectRepository } from "../data/select_project_repository";
|
||||
import { IProjectModel } from "../model/project_model";
|
||||
import { BaseStore } from "../../../core/store/base_store";
|
||||
import { CoreError, UiErrorState } from "../../../core/store/base_store";
|
||||
|
||||
export class SelectProjectStore extends BaseStore {
|
||||
export class SelectProjectStore extends UiErrorState<CoreError> {
|
||||
errorHandingStrategy = (error: CoreError) => {};
|
||||
repository: SelectProjectRepository;
|
||||
|
||||
errors = [];
|
||||
page = 1;
|
||||
projects: IProjectModel[] = [];
|
||||
|
||||
constructor(repository: SelectProjectRepository) {
|
||||
super()
|
||||
this.repository = repository;
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.repository = new SelectProjectRepository();
|
||||
makeAutoObservable(this);
|
||||
this.getPipelines();
|
||||
}
|
||||
|
||||
async setActiveProject(id: string): Promise<void> {
|
||||
this.httpHelper(this.repository.setActiveProject(id));
|
||||
}
|
||||
async getPipelines(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
const result = await this.repository.getAllProjects(this.page);
|
||||
result.fold(
|
||||
(s) => {
|
||||
this.projects = s;
|
||||
},
|
||||
(_e) => {
|
||||
this.isError = true;
|
||||
}
|
||||
);
|
||||
this.isLoading = false;
|
||||
await this.mapOk("projects", this.repository.getAllProjects(this.page));
|
||||
}
|
||||
async init() {
|
||||
await this.getPipelines();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,26 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "reflect-metadata";
|
||||
|
||||
import "antd/dist/antd.min.css";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
|
||||
import { router } from "./core/routers/routers";
|
||||
import { SocketLister } from "./features/socket_lister/socket_lister";
|
||||
import { extensions } from "./core/extensions/extensions";
|
||||
import { SceneManger } from "./features/scene_manager/scene_manager";
|
||||
import { SocketLister } from "./features/socket_lister/socket_lister";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { router } from "./core/routers/routers";
|
||||
import { SceneManger } from "./features/scene_manager/presentation/scene_manager";
|
||||
|
||||
extensions();
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
|
||||
root.render(
|
||||
<>
|
||||
{/* <SocketLister>
|
||||
<RouterProvider router={router} />
|
||||
</SocketLister> */}
|
||||
<SceneManger />
|
||||
{/* <SocketLister> */}
|
||||
{/* <RouterProvider router={router} /> */}
|
||||
|
||||
{/* </SocketLister> */}
|
||||
<>
|
||||
<SceneManger></SceneManger>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"useDefineForClassFields": true
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strictPropertyInitialization": false
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue