Added Image upload component.

This commit is contained in:
Michael Mainguy 2024-04-29 13:39:05 -05:00
parent eb4281ac30
commit d82df88296
8 changed files with 171 additions and 66 deletions

View File

@ -156,7 +156,7 @@ h1 {
#tutorial { #tutorial {
z-index: 15; z-index: 15;
left: 100px; left: 100px;
top: 560px; top: 750px;
width: 160px; width: 160px;
height: 210px; height: 210px;
} }

View File

@ -1,5 +1,5 @@
import {AbstractMesh, ActionManager, InstancedMesh, Mesh, Observable, Scene, TransformNode} from "@babylonjs/core"; 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 log from "loglevel";
import {Controllers} from "../controllers/controllers"; import {Controllers} from "../controllers/controllers";
import {AppConfig} from "../util/appConfig"; import {AppConfig} from "../util/appConfig";
@ -43,6 +43,27 @@ export class DiagramManager {
this.cleanupOrphanConnections(mesh) 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 { public get diagramMenuManager(): DiagramMenuManager {

View File

@ -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;
}

View File

@ -1,12 +1,21 @@
import {DiagramEntity, DiagramEntityType} from "../types/diagramEntity"; import {DiagramEntity, DiagramEntityType, DiagramTemplates} from "../types/diagramEntity";
import {AbstractMesh, InstancedMesh, Mesh, Quaternion, Scene, Vector3} from "@babylonjs/core"; import {
AbstractMesh,
InstancedMesh,
Mesh,
MeshBuilder,
Quaternion,
Scene,
StandardMaterial,
Texture,
Vector3
} from "@babylonjs/core";
import {DiagramConnection} from "../diagramConnection"; import {DiagramConnection} from "../diagramConnection";
import {updateTextNode} from "../../util/functions/updateTextNode"; import {updateTextNode} from "../../util/functions/updateTextNode";
import log from "loglevel"; import log from "loglevel";
import {v4 as uuidv4} from 'uuid'; import {v4 as uuidv4} from 'uuid';
import {buildStandardMaterial} from "../../materials/functions/buildStandardMaterial"; import {buildStandardMaterial} from "../../materials/functions/buildStandardMaterial";
export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh { export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh {
const logger = log.getLogger('buildMeshFromDiagramEntity'); const logger = log.getLogger('buildMeshFromDiagramEntity');
if (!entity) { if (!entity) {
@ -33,25 +42,58 @@ function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): Abst
logger.debug(`mesh ${oldMesh.id} already exists`); logger.debug(`mesh ${oldMesh.id} already exists`);
newMesh = oldMesh; newMesh = oldMesh;
} else { } else {
if (entity.template == "#connection-template") { switch (entity.template) {
const connection: DiagramConnection = new DiagramConnection(entity.from, entity.to, entity.id, scene); case DiagramTemplates.CONNECTION:
const connection: DiagramConnection = new DiagramConnection(entity.from, entity.to, entity.id, scene);
logger.debug(`connection.mesh = ${connection.mesh.id}`); logger.debug(`connection.mesh = ${connection.mesh.id}`);
newMesh = connection.mesh; newMesh = connection.mesh;
} else { break;
const toolMesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color); case DiagramTemplates.USER:
if (toolMesh && !oldMesh) { break;
newMesh = new InstancedMesh(entity.id, (toolMesh as Mesh)); case DiagramTemplates.IMAGE:
newMesh.metadata = {template: entity.template, exportable: true, tool: false}; newMesh = buildImage(entity, scene);
} else { 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); logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color);
break;
}
} }
} }
return newMesh; 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) { function generateId(entity: DiagramEntity) {
if (!entity.id) { if (!entity.id) {
entity.id = "id" + uuidv4(); entity.id = "id" + uuidv4();
@ -104,6 +146,7 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene)
} }
return newMesh; return newMesh;
} }
function xyztovec(xyz: { x, y, z }): Vector3 { function xyztovec(xyz: { x, y, z }): Vector3 {
return new Vector3(xyz.x, xyz.y, xyz.z); return new Vector3(xyz.x, xyz.y, xyz.z);
} }

View File

@ -21,6 +21,16 @@ export enum DiagramEventMask {
REMOTE = 2, 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 = { export type DiagramEvent = {
type: DiagramEventType; type: DiagramEventType;
@ -34,6 +44,7 @@ export type DiagramEntity = {
id?: string; id?: string;
from?: string; from?: string;
to?: string; to?: string;
image?: string;
last_seen?: Date; last_seen?: Date;
position?: { x: number, y: number, z: number }; position?: { x: number, y: number, z: number };
rotation?: { x: number, y: number, z: number }; rotation?: { x: number, y: number, z: number };

View File

@ -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};

View File

@ -1,4 +1,5 @@
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {uploadImage} from "./functions/uploadImage";
function MainMenu({onClick}) { function MainMenu({onClick}) {
return ( return (
@ -7,6 +8,7 @@ function MainMenu({onClick}) {
<div id="enterXR" className="inactive"><a href="#" id="enterVRLink">Enter VR</a></div> <div id="enterXR" className="inactive"><a href="#" id="enterVRLink">Enter VR</a></div>
<QuestLink/> <QuestLink/>
<div id="diagrams"><a href="#" id="diagramsLink" onClick={onClick}>Diagrams</a></div> <div id="diagrams"><a href="#" id="diagramsLink" onClick={onClick}>Diagrams</a></div>
<div id="imageUpload"><a href="#" id="imageUploadLink" onClick={onClick}>Upload Image</a></div>
<div id="download"><a href="#" id="downloadLink">Download Model</a></div> <div id="download"><a href="#" id="downloadLink">Download Model</a></div>
</div> </div>
) )
@ -113,7 +115,19 @@ function Menu() {
function handleDiagramListClick(evt: React.MouseEvent<HTMLAnchorElement>) { function handleDiagramListClick(evt: React.MouseEvent<HTMLAnchorElement>) {
evt.preventDefault(); 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 ( return (
@ -134,49 +148,3 @@ export default function WebApp() {
</div> </div>
) )
} }
/*
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');
}
});
}
*/

View File

@ -22,7 +22,11 @@ export default defineConfig({
'^/create-db': { '^/create-db': {
target: 'https://www.deepdiagram.com/', target: 'https://www.deepdiagram.com/',
changeOrigin: true, changeOrigin: true,
} },
'^/api/images': {
target: 'https://www.deepdiagram.com/',
changeOrigin: true,
},
} }
}, },
base: "/" base: "/"