From 3ff2186deb8226255f904c7f45bacbb091a24d53 Mon Sep 17 00:00:00 2001 From: IDONTSUDO Date: Tue, 19 Dec 2023 11:54:47 +0300 Subject: [PATCH] webgl test and class validator mocker --- .vscode/settings.json | 2 +- server/src/core/controllers/app.ts | 4 +- .../core/helpers/class_validator_mocket.ts | 82 +++++++++++++++++++ .../pipelines/models/pipeline_model.ts | 6 +- .../models/pipeline_validation_model.ts | 4 +- .../models/trigger_validation_model.ts | 2 +- .../triggers/triggers_presentation.ts | 6 +- server/src/main.ts | 4 +- .../helper/class_validator_mocker_test.ts | 25 ++++++ server/test/test.ts | 30 ++++--- ui/package.json | 2 +- ui/src/core/helper/typed_event.ts | 42 ++++++++++ .../core/repository/core_there_repository.ts | 72 ++++++++++++---- .../components/static_asset_item_view.tsx | 14 ++++ .../features/scene_manager/scene_manager.tsx | 70 +++++++++------- .../scene_manager/scene_manager_store.ts | 70 ++++++++++++++++ ui/src/index.tsx | 5 +- 17 files changed, 368 insertions(+), 72 deletions(-) create mode 100644 server/src/core/helpers/class_validator_mocket.ts create mode 100644 server/test/helper/class_validator_mocker_test.ts create mode 100644 ui/src/core/helper/typed_event.ts create mode 100644 ui/src/features/scene_manager/components/static_asset_item_view.tsx create mode 100644 ui/src/features/scene_manager/scene_manager_store.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 39cf20f..3ed5ae2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,6 @@ "*ui": false, "*ui.*": false }, - "cSpell.words": ["antd", "fileupload", "uuidv"], + "cSpell.words": ["antd", "fileupload", "metadatas", "uuidv"], "editor.rulers": [100] } diff --git a/server/src/core/controllers/app.ts b/server/src/core/controllers/app.ts index c312f72..40d0c69 100644 --- a/server/src/core/controllers/app.ts +++ b/server/src/core/controllers/app.ts @@ -76,7 +76,9 @@ export class App { } async loadAppDependencies() { - await new DataBaseConnectUseCase().call(); + if ((await new DataBaseConnectUseCase().call()).isFailure()) { + console.log("database connect error"); + } await new CheckAndCreateStaticFilesFolderUseCase().call(); await new SetLastActivePipelineToRealTimeServiceScenario().call(); } diff --git a/server/src/core/helpers/class_validator_mocket.ts b/server/src/core/helpers/class_validator_mocket.ts new file mode 100644 index 0000000..1f2157b --- /dev/null +++ b/server/src/core/helpers/class_validator_mocket.ts @@ -0,0 +1,82 @@ +import { randomBytes, randomInt, randomUUID } from "crypto"; +import { getMetadataStorage, IS_BOOLEAN, IS_MONGO_ID, IS_NUMBER, IS_STRING, IS_UUID } from "class-validator"; +import { ValidationMetadata } from "class-validator/types/metadata/ValidationMetadata"; + +type AvailableTypes = string | number | boolean | undefined; + +export class ClassValidatorMocker { + // eslint-disable-next-line @typescript-eslint/ban-types + public static create(constructor: Function, partial: Partial = {}): T { + return new ClassValidatorMocker().create(constructor, partial); + } + + // eslint-disable-next-line @typescript-eslint/ban-types + public create(constructor: Function, partial: Partial = {}): T { + const metadataStorage = getMetadataStorage(); + const targetMetadatas = metadataStorage.getTargetValidationMetadatas(constructor, "", false, false); + const groupedMetadatas = metadataStorage.groupByPropertyName(targetMetadatas); + // nestedValidation + console.log(targetMetadatas); + let randomFixture = {} as T; + + for (const propertyName of Object.keys(groupedMetadatas)) { + const metadatas = groupedMetadatas[propertyName]; + const value = this.generatePropertyValueFromMetadatas(metadatas); + + if (value !== undefined) { + randomFixture = { + ...randomFixture, + [propertyName]: value, + }; + } + } + + return { ...randomFixture, ...partial }; + } + + private generatePropertyValueFromMetadatas(metadatas: ValidationMetadata[]): AvailableTypes { + for (const metadata of metadatas) { + const constraints = getMetadataStorage().getTargetValidatorConstraints(metadata.constraintCls); + + for (const constraint of constraints) { + switch (constraint.name) { + case IS_MONGO_ID: + return this.randomUUID(); + case IS_STRING: + return this.randomString(); + case IS_NUMBER: + return this.randomNumber(); + case IS_BOOLEAN: + return this.randomBoolean(); + case IS_UUID: + return this.randomUUID(); + + default: + break; + } + } + } + + return undefined; + } + + private randomString(): string { + return randomBytes(randomInt(1, 10)).toString("hex"); + } + + private randomNumber(): number { + return randomInt(0, 99_999); + } + + private randomBoolean(): boolean { + return randomInt(0, 1) === 1; + } + + private randomUUID(): string { + if (randomUUID != null) { + return randomUUID(); + } + + return randomBytes(16).toString("hex"); + } +} diff --git a/server/src/features/pipelines/models/pipeline_model.ts b/server/src/features/pipelines/models/pipeline_model.ts index 9f4fd80..69cd0fc 100644 --- a/server/src/features/pipelines/models/pipeline_model.ts +++ b/server/src/features/pipelines/models/pipeline_model.ts @@ -2,7 +2,7 @@ import { IsOptional, ValidateNested } from "class-validator"; import { IPipeline, IProcess, StackGenerateType } from "../../../core/models/process_model"; import { Type } from "class-transformer"; import { ProcessModel } from "../../process/models/process_validation_model"; -import { TriggerModel } from "../../triggers/models/trigger_validation_model"; +import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model"; export class PipelineModel implements IPipeline { @ValidateNested() @@ -10,8 +10,8 @@ export class PipelineModel implements IPipeline { public process: IProcess; @ValidateNested() - @Type(() => TriggerModel) - public trigger: TriggerModel; + @Type(() => TriggerModelValidationModel) + public trigger: TriggerModelValidationModel; @IsOptional() public env = null; diff --git a/server/src/features/pipelines/models/pipeline_validation_model.ts b/server/src/features/pipelines/models/pipeline_validation_model.ts index ac3ba78..d16382e 100644 --- a/server/src/features/pipelines/models/pipeline_validation_model.ts +++ b/server/src/features/pipelines/models/pipeline_validation_model.ts @@ -1,13 +1,13 @@ import { IsMongoId, IsOptional } from "class-validator"; import { IProcess, StackGenerateType } from "../../../core/models/process_model"; -import { TriggerModel } from "../../triggers/models/trigger_validation_model"; +import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model"; export class PipelineValidationModel { @IsMongoId() public process: IProcess; @IsMongoId() - public trigger: TriggerModel; + public trigger: TriggerModelValidationModel; @IsOptional() public env = null; diff --git a/server/src/features/triggers/models/trigger_validation_model.ts b/server/src/features/triggers/models/trigger_validation_model.ts index d54868b..86b9174 100644 --- a/server/src/features/triggers/models/trigger_validation_model.ts +++ b/server/src/features/triggers/models/trigger_validation_model.ts @@ -1,7 +1,7 @@ import { IsArray, IsOptional, IsEnum, IsString } from "class-validator"; import { ITriggerModel, TriggerType } from "./trigger_database_model"; -export class TriggerModel implements ITriggerModel { +export class TriggerModelValidationModel implements ITriggerModel { @IsOptional() public _id: string; diff --git a/server/src/features/triggers/triggers_presentation.ts b/server/src/features/triggers/triggers_presentation.ts index 25146f5..f48f7dd 100644 --- a/server/src/features/triggers/triggers_presentation.ts +++ b/server/src/features/triggers/triggers_presentation.ts @@ -1,12 +1,12 @@ import { CrudController } from "../../core/controllers/crud_controller"; import { TriggerDBModel } from "./models/trigger_database_model"; -import { TriggerModel } from "./models/trigger_validation_model"; +import { TriggerModelValidationModel as TriggerValidationMode } from "./models/trigger_validation_model"; -export class TriggerPresentation extends CrudController { +export class TriggerPresentation extends CrudController { constructor() { super({ url: "trigger", - validationModel: TriggerModel, + validationModel: TriggerValidationMode, databaseModel: TriggerDBModel, }); } diff --git a/server/src/main.ts b/server/src/main.ts index 73b36da..9f6f0fa 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -9,11 +9,11 @@ import { ProcessPresentation } from "./features/process/process_presentation"; import { RealTimePresentation, pipelineRealTimeService } from "./features/realtime/realtime_presentation"; import { extensions } from "./core/extensions/extensions"; import { ProjectInstancePresentation } from "./features/project_instance/project_instance_presentation"; -import { NixStoreManagerPresentation as NixStoreManagerPresentation } from "./features/nix_store_manager/nix_store_manager"; +import { NixStoreManagerPresentation } from "./features/nix_store_manager/nix_store_manager"; extensions(); -const httpRoutes: Routes[] = [ +export const httpRoutes: Routes[] = [ new TriggerPresentation(), new ProjectsPresentation(), new ProcessPresentation(), diff --git a/server/test/helper/class_validator_mocker_test.ts b/server/test/helper/class_validator_mocker_test.ts new file mode 100644 index 0000000..b468fd3 --- /dev/null +++ b/server/test/helper/class_validator_mocker_test.ts @@ -0,0 +1,25 @@ +import { Type } from "class-transformer"; +import { ClassValidatorMocker } from "../../src/core/helpers/class_validator_mocket"; +import { IsString, IsNumber, IsBoolean, IsUUID, IsMongoId, ValidateNested } from "class-validator"; + +class Foo {} +class MyClass { + @ValidateNested() + @Type(() => Foo) + model: Foo; + + @IsNumber() + numberProperty: number; + + @IsBoolean() + booleanProperty: boolean; + + @IsUUID() + uuidProperty: string; +} + +const myClassDataMock = ClassValidatorMocker.create(MyClass); + +export const mainTest = () => { + console.log(myClassDataMock); +}; diff --git a/server/test/test.ts b/server/test/test.ts index fc4f9c2..e1fee40 100644 --- a/server/test/test.ts +++ b/server/test/test.ts @@ -1,3 +1,4 @@ +import "reflect-metadata"; import { TestCore } from "./core/test_core"; // import { UnitTestEnv } from "../src/core/di/env"; import { dirname } from "path"; @@ -13,6 +14,7 @@ import { UpdateDataBaseModelUseCaseTest } from "./usecases/update_database_model import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test"; import { extensions } from "../src/core/extensions/extensions"; import { DataBaseConnectUseCase } from "../src/core/usecases/database_connect_usecase"; +import { mainTest } from "./helper/class_validator_mocker_test"; extensions(); @@ -34,20 +36,22 @@ const init = async () => { }; const test = async () => { - await new ExecutorProgramServiceTest(dirname__).test(); - await new FilesChangerTest(dirname__).test(); - await new StackServiceTest(dirname__ + "/context/").test(); - await new TriggerServiceTest().test(); - await new CreateDataBaseModelUseCaseTest().test(); + // await new ExecutorProgramServiceTest(dirname__).test(); + // await new FilesChangerTest(dirname__).test(); + // await new StackServiceTest(dirname__ + "/context/").test(); + // await new TriggerServiceTest().test(); + // await new CreateDataBaseModelUseCaseTest().test(); - await new CreateDataBaseModelUseCaseTest().test(); - await new DeleteDataBaseModelUseCaseTest().test(); - await new ReadDataBaseModelUseCaseTest().test(); - await new UpdateDataBaseModelUseCaseTest().test(); - // await new PipelineRealTimeServiceTest().test() - for await (const usecase of tests) { - testCore.assert(await new usecase().test(), usecase.name); - } + // await new CreateDataBaseModelUseCaseTest().test(); + // await new DeleteDataBaseModelUseCaseTest().test(); + // await new ReadDataBaseModelUseCaseTest().test(); + // await new UpdateDataBaseModelUseCaseTest().test(); + // // await new PipelineRealTimeServiceTest().test() + // for await (const usecase of tests) { + // testCore.assert(await new usecase().test(), usecase.name); + // } + + mainTest(); }; const main = async () => { await init(); diff --git a/ui/package.json b/ui/package.json index 2aef67f..f03a316 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,7 +29,7 @@ "three": "^0.159.0", "typescript": "^4.9.5", "urdf-loader": "^0.12.1", - "uuid": "^9.0.0", + "uuid": "^9.0.1", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/ui/src/core/helper/typed_event.ts b/ui/src/core/helper/typed_event.ts new file mode 100644 index 0000000..e7002c3 --- /dev/null +++ b/ui/src/core/helper/typed_event.ts @@ -0,0 +1,42 @@ +export interface Listener { + (event: T): any; +} + +export interface Disposable { + dispose(): void; +} + +export class TypedEvent { + private listeners: Listener[] = []; + public listenersOnces: Listener[] = []; + + on = (listener: Listener): Disposable => { + this.listeners.push(listener); + return { + dispose: () => this.off(listener), + }; + }; + + once = (listener: Listener): void => { + this.listenersOnces.push(listener); + }; + + off = (listener: Listener) => { + const callbackIndex = this.listeners.indexOf(listener); + if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1); + }; + + emit = (event: T) => { + this.listeners.forEach((listener) => listener(event)); + + if (this.listenersOnces.length > 0) { + const toCall = this.listenersOnces; + this.listenersOnces = []; + toCall.forEach((listener) => listener(event)); + } + }; + + pipe = (te: TypedEvent): Disposable => { + return this.on((e) => te.emit(e)); + }; +} diff --git a/ui/src/core/repository/core_there_repository.ts b/ui/src/core/repository/core_there_repository.ts index 2c466c7..82b8da9 100644 --- a/ui/src/core/repository/core_there_repository.ts +++ b/ui/src/core/repository/core_there_repository.ts @@ -12,15 +12,29 @@ import { 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"; -export class CoreThereRepository { +interface IEmissiveCache { + status: boolean; + object3d: Object3D; +} +export class CoreThereRepository extends TypedEvent { scene = new Scene(); camera: PerspectiveCamera; webGlRender: WebGLRenderer; htmlCanvasRef: HTMLCanvasElement; + objectEmissive = new Map(); constructor(htmlCanvasRef: HTMLCanvasElement) { + super(); const renderer = new WebGLRenderer({ canvas: htmlCanvasRef as HTMLCanvasElement, antialias: true, @@ -32,14 +46,22 @@ export class CoreThereRepository { this.htmlCanvasRef = htmlCanvasRef; this.init(); } + setRayCastAndGetFirstObject(vector: Vector2): Result { + 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); - // cube.translateX(position.x); - // cube.translateY(position.y); - // cube.translateZ(position.z); + eval(`cube.translate${translateTo}(${num * 10})`); this.scene.add(cube); } @@ -70,6 +92,9 @@ export class CoreThereRepository { 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); } @@ -106,36 +131,55 @@ export class CoreThereRepository { 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(); - console.log(cameraOffs); cameraOffs.multiplyScalar(-FL); - console.log(-FL); + let newCameraPos = bsWorld.clone().add(cameraOffs); - console.log(newCameraPos); + this.camera.translateX(newCameraPos.x); this.camera.translateY(newCameraPos.y); this.camera.translateZ(newCameraPos.z); diff --git a/ui/src/features/scene_manager/components/static_asset_item_view.tsx b/ui/src/features/scene_manager/components/static_asset_item_view.tsx new file mode 100644 index 0000000..a60272b --- /dev/null +++ b/ui/src/features/scene_manager/components/static_asset_item_view.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; +import { StaticAssetItemModel } from "../scene_manager_store"; + +export interface IStaticAssetModelViewProps { + model: StaticAssetItemModel; +} + +export function StaticAssetModelView(props: IStaticAssetModelViewProps) { + return ( +
+ {props.model.name} +
+ ); +} diff --git a/ui/src/features/scene_manager/scene_manager.tsx b/ui/src/features/scene_manager/scene_manager.tsx index b1b3b30..d3e03b7 100644 --- a/ui/src/features/scene_manager/scene_manager.tsx +++ b/ui/src/features/scene_manager/scene_manager.tsx @@ -1,43 +1,55 @@ import * as React from "react"; -import { CoreThereRepository } from "../../core/repository/core_there_repository"; +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 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] - ); +// 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]); +// React.useEffect(() => { +// window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn)); +// window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {})); +// }, [fn, registerKeyPress]); - return []; -}; -export function SceneManger() { +// return []; +// }; + +export const SceneManger = observer(() => { const canvasRef = React.useRef(null); - let thereRepository: null | CoreThereRepository = null; + + const [sceneMangerStore] = React.useState(() => new SceneMangerStore()); + React.useEffect(() => { - // eslint-disable-next-line react-hooks/exhaustive-deps - thereRepository = new CoreThereRepository(canvasRef.current as HTMLCanvasElement); - thereRepository.render(); - // thereRepository.fitSelectedObjectToScreen(thereRepository.getAllSceneNameModels()); - thereRepository.fitCameraToCenteredObject(thereRepository.getAllSceneNameModels()); + sceneMangerStore.loadGl(canvasRef.current!); + return () => { + sceneMangerStore.dispose(); + }; }); return (
+
+ {sceneMangerStore.sceneItems.map((el) => { + if (el instanceof StaticAssetItemModel) { + return StaticAssetModelView({ model: el }); + } + return <>; + })} +
); -} +}); diff --git a/ui/src/features/scene_manager/scene_manager_store.ts b/ui/src/features/scene_manager/scene_manager_store.ts new file mode 100644 index 0000000..96ae25e --- /dev/null +++ b/ui/src/features/scene_manager/scene_manager_store.ts @@ -0,0 +1,70 @@ +/* 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); + } +} diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 35b56d8..571cfd8 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -14,8 +14,9 @@ const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement) root.render( <> - + {/* - + */} + );