From d82df882968b46bf87ad6de241a106bfc446e3ea Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Mon, 29 Apr 2024 13:39:05 -0500 Subject: [PATCH] Added Image upload component. --- public/styles.css | 2 +- src/diagram/diagramManager.ts | 23 +++++- src/diagram/functions/buildImage.ts | 15 ++++ .../functions/buildMeshFromDiagramEntity.ts | 73 +++++++++++++++---- src/diagram/types/diagramEntity.ts | 11 +++ src/react/functions/uploadImage.ts | 43 +++++++++++ src/react/webApp.tsx | 64 ++++------------ vite.config.ts | 6 +- 8 files changed, 171 insertions(+), 66 deletions(-) create mode 100644 src/diagram/functions/buildImage.ts create mode 100644 src/react/functions/uploadImage.ts diff --git a/public/styles.css b/public/styles.css index c67a8ff..d7a8c8a 100644 --- a/public/styles.css +++ b/public/styles.css @@ -156,7 +156,7 @@ h1 { #tutorial { z-index: 15; left: 100px; - top: 560px; + top: 750px; width: 160px; height: 210px; } diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index 943a006..3f9fd32 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -1,5 +1,5 @@ import {AbstractMesh, ActionManager, InstancedMesh, Mesh, Observable, Scene, TransformNode} from "@babylonjs/core"; -import {DiagramEvent, DiagramEventType} from "./types/diagramEntity"; +import {DiagramEntity, DiagramEvent, DiagramEventType} from "./types/diagramEntity"; import log from "loglevel"; import {Controllers} from "../controllers/controllers"; import {AppConfig} from "../util/appConfig"; @@ -43,6 +43,27 @@ export class DiagramManager { this.cleanupOrphanConnections(mesh) } }); + document.addEventListener('uploadImage', (event: CustomEvent) => { + const diagramEntity: DiagramEntity = { + template: '#image-template', + image: event.detail.data, + text: event.detail.name, + position: {x: 0, y: 1.6, z: 0}, + rotation: {x: 0, y: Math.PI, z: 0}, + scale: {x: 1, y: 1, z: 1}, + } + + console.log(diagramEntity); + //const newMesh = buildMeshFromDiagramEntity(diagramEntity, this._scene); + if (this.onDiagramEventObservable) { + this.onDiagramEventObservable.notifyObservers({ + type: DiagramEventType.ADD, + entity: diagramEntity + }, DiagramEventObserverMask.ALL); + + //this.onDiagramEventObservable.notifyObservers({type: DiagramEventType.ADD, entity: diagramEntity}, DiagramEventObserverMask.FROM_DB); + } + }) } public get diagramMenuManager(): DiagramMenuManager { diff --git a/src/diagram/functions/buildImage.ts b/src/diagram/functions/buildImage.ts new file mode 100644 index 0000000..fb7ec5c --- /dev/null +++ b/src/diagram/functions/buildImage.ts @@ -0,0 +1,15 @@ +import log from "loglevel"; +import {AbstractMesh, MeshBuilder, StandardMaterial, Texture} from "@babylonjs/core"; +import {DefaultScene} from "../../defaultScene"; +import {DiagramEntity} from "../types/diagramEntity"; + +export function buildImage(entity: DiagramEntity): AbstractMesh { + const logger = log.getLogger('buildImage'); + logger.debug("buildImage: entity is image"); + const scene = DefaultScene.Scene; + const plane = MeshBuilder.CreatePlane("plane", {size: 1}, scene); + const material = new StandardMaterial("planeMaterial", scene); + const texture = new Texture(entity.image, scene); + material.emissiveTexture = texture; + return plane; +} \ No newline at end of file diff --git a/src/diagram/functions/buildMeshFromDiagramEntity.ts b/src/diagram/functions/buildMeshFromDiagramEntity.ts index 8958835..7a5a983 100644 --- a/src/diagram/functions/buildMeshFromDiagramEntity.ts +++ b/src/diagram/functions/buildMeshFromDiagramEntity.ts @@ -1,12 +1,21 @@ -import {DiagramEntity, DiagramEntityType} from "../types/diagramEntity"; -import {AbstractMesh, InstancedMesh, Mesh, Quaternion, Scene, Vector3} from "@babylonjs/core"; +import {DiagramEntity, DiagramEntityType, DiagramTemplates} from "../types/diagramEntity"; +import { + AbstractMesh, + InstancedMesh, + Mesh, + MeshBuilder, + Quaternion, + Scene, + StandardMaterial, + Texture, + Vector3 +} from "@babylonjs/core"; import {DiagramConnection} from "../diagramConnection"; import {updateTextNode} from "../../util/functions/updateTextNode"; import log from "loglevel"; import {v4 as uuidv4} from 'uuid'; import {buildStandardMaterial} from "../../materials/functions/buildStandardMaterial"; - export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh { const logger = log.getLogger('buildMeshFromDiagramEntity'); if (!entity) { @@ -33,25 +42,58 @@ function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): Abst logger.debug(`mesh ${oldMesh.id} already exists`); newMesh = oldMesh; } else { - if (entity.template == "#connection-template") { - const connection: DiagramConnection = new DiagramConnection(entity.from, entity.to, entity.id, scene); - - logger.debug(`connection.mesh = ${connection.mesh.id}`); - newMesh = connection.mesh; - } else { - const toolMesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color); - if (toolMesh && !oldMesh) { - newMesh = new InstancedMesh(entity.id, (toolMesh as Mesh)); - newMesh.metadata = {template: entity.template, exportable: true, tool: false}; - } else { + switch (entity.template) { + case DiagramTemplates.CONNECTION: + const connection: DiagramConnection = new DiagramConnection(entity.from, entity.to, entity.id, scene); + logger.debug(`connection.mesh = ${connection.mesh.id}`); + newMesh = connection.mesh; + break; + case DiagramTemplates.USER: + break; + case DiagramTemplates.IMAGE: + newMesh = buildImage(entity, scene); + newMesh.metadata = {template: entity.template, exportable: true, tool: false} + break; + case DiagramTemplates.BOX: + case DiagramTemplates.SPHERE: + case DiagramTemplates.CYLINDER: + case DiagramTemplates.CONE: + case DiagramTemplates.PLANE: + const toolMesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color); + if (toolMesh && !oldMesh) { + newMesh = new InstancedMesh(entity.id, (toolMesh as Mesh)); + newMesh.metadata = {template: entity.template, exportable: true, tool: false}; + } else { + logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color); + } + break; + default: logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color); + break; - } } } return newMesh; } +function buildImage(entity: DiagramEntity, scene: Scene): AbstractMesh { + const logger = log.getLogger('buildImage'); + logger.debug("buildImage: entity is image"); + const plane = MeshBuilder.CreatePlane(entity.id, {size: 1}, scene); + const material = new StandardMaterial("planeMaterial", scene); + const image = new Image(); + image.src = entity.image; + const texture = new Texture(entity.image, scene); + material.emissiveTexture = texture; + material.backFaceCulling = false; + material.disableLighting = true; + plane.material = material; + image.decode().then(() => { + plane.scaling.x = image.width / image.height; + }); + return plane; +} + function generateId(entity: DiagramEntity) { if (!entity.id) { entity.id = "id" + uuidv4(); @@ -104,6 +146,7 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene) } return newMesh; } + function xyztovec(xyz: { x, y, z }): Vector3 { return new Vector3(xyz.x, xyz.y, xyz.z); } \ No newline at end of file diff --git a/src/diagram/types/diagramEntity.ts b/src/diagram/types/diagramEntity.ts index 5fc4e10..e521949 100644 --- a/src/diagram/types/diagramEntity.ts +++ b/src/diagram/types/diagramEntity.ts @@ -21,6 +21,16 @@ export enum DiagramEventMask { REMOTE = 2, } +export enum DiagramTemplates { + CONNECTION = "#connection-template", + USER = "#user-template", + BOX = "#box-template", + SPHERE = "#sphere-template", + CYLINDER = "#cylinder-template", + CONE = "#cone-template", + IMAGE = "#image-template", + PLANE = "#plane-template", +} export type DiagramEvent = { type: DiagramEventType; @@ -34,6 +44,7 @@ export type DiagramEntity = { id?: string; from?: string; to?: string; + image?: string; last_seen?: Date; position?: { x: number, y: number, z: number }; rotation?: { x: number, y: number, z: number }; diff --git a/src/react/functions/uploadImage.ts b/src/react/functions/uploadImage.ts new file mode 100644 index 0000000..bc87f01 --- /dev/null +++ b/src/react/functions/uploadImage.ts @@ -0,0 +1,43 @@ +const uploadImage = async (evt) => { + + const file = (evt.target as HTMLInputElement).files[0]; + const formData = new FormData(); + formData.append('file', file); + //formData.append('requireSignedURLs', 'true'); + const formInitData = new FormData(); + formInitData.append('requireSignedURLs', 'true'); + + const initialUpload = await fetch('/api/images', { + method: 'POST', + body: formInitData + + + }); + try { + const initialData = await initialUpload.json(); + if (initialData.success == true) { + const upload = await fetch(initialData.result.uploadURL, { + method: 'POST', mode: 'cors', body: + formData + }); + const uploadData = await upload.json(); + console.log(uploadData) + for (let variant of uploadData.result.variants) { + if (variant.indexOf('fullhd') > -1) { + const uploadEvent = new CustomEvent('uploadImage', { + detail: { + name: file.name, + data: variant + } + }); + document.dispatchEvent(uploadEvent); + evt.target.remove(); + console.log(variant); + } + } + } + } catch (err) { + console.error(err); + } +} +export {uploadImage}; \ No newline at end of file diff --git a/src/react/webApp.tsx b/src/react/webApp.tsx index 57efbab..838dba5 100644 --- a/src/react/webApp.tsx +++ b/src/react/webApp.tsx @@ -1,4 +1,5 @@ import {useEffect, useState} from "react"; +import {uploadImage} from "./functions/uploadImage"; function MainMenu({onClick}) { return ( @@ -7,6 +8,7 @@ function MainMenu({onClick}) {
Enter VR
+ ) @@ -113,7 +115,19 @@ function Menu() { function handleDiagramListClick(evt: React.MouseEvent) { evt.preventDefault(); - setDiagramListState(diagramListState == 'none' ? 'block' : 'none'); + switch (evt.target.id) { + case 'imageUploadLink': + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.onchange = uploadImage; + + document.body.appendChild(input); + input.click(); + break; + default: + setDiagramListState(diagramListState == 'none' ? 'block' : 'none'); + } } return ( @@ -133,50 +147,4 @@ export default function WebApp() { ) -} -/* -const create = document.querySelector('#startCreateLink'); - if (create) { - create.addEventListener('click', function (evt) { - evt.preventDefault(); - document.querySelector('#main').style.display = 'none'; - document.querySelector('#create').style.display = 'block'; - }); - } - const cancel = document.querySelector('#cancelCreateLink'); - if (cancel) { - cancel.addEventListener('click', function (evt) { - evt.preventDefault(); - document.querySelector('#main').style.display = 'block'; - document.querySelector('#create').style.display = 'none'; - }); - } - const close = document.querySelector('#closekey a'); - if (close) { - close.addEventListener('click', function (evt) { - evt.preventDefault(); - document.querySelector('#keyboardHelp').style.display = 'none'; - }); - } - const desktopTutorial = document.querySelector('#desktopLink'); - if (desktopTutorial) { - desktopTutorial.addEventListener('click', function (evt) { - evt.preventDefault(); - // document.querySelector('#tutorial').style.display = 'none'; - document.querySelector('#keyboardHelp').style.display = 'block'; - }); - } - const createAction = document.querySelector('#createActionLink'); - if (createAction) { - createAction.addEventListener('click', function (evt) { - evt.preventDefault(); - const value = document.querySelector('#createName').value; - if (value && value.length > 4) { - document.location.href = '/db/' + value; - } else { - window.alert('Name must be longer than 4 characters'); - } - }); - } - - */ \ No newline at end of file +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index f2ed5b6..e8f1654 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,7 +22,11 @@ export default defineConfig({ '^/create-db': { target: 'https://www.deepdiagram.com/', changeOrigin: true, - } + }, + '^/api/images': { + target: 'https://www.deepdiagram.com/', + changeOrigin: true, + }, } }, base: "/"