refactored web interface, updated image update code.
This commit is contained in:
parent
1d6c82a16a
commit
a07b53f2a7
@ -11,11 +11,11 @@ body {
|
||||
color: #4444ee;
|
||||
}
|
||||
|
||||
|
||||
.scene {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
#gameCanvas {
|
||||
@ -26,184 +26,6 @@ body {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
div.overlay {
|
||||
position: absolute;
|
||||
display: block;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 12;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
|
||||
div.overlay div {
|
||||
width: 100%;
|
||||
margin: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.overlay div a {
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
padding-top: 1em;
|
||||
padding-left: 5px;
|
||||
vertical-align: middle;
|
||||
border-color: #FFD700;
|
||||
border-style: outset;
|
||||
border-width: 2px;
|
||||
border-radius: 10px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
div.overlay div a:visited, div.overlay div a:link {
|
||||
color: white;
|
||||
background-color: #999922;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
div.overlay div a:hover {
|
||||
background-color: #FFD700;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
div.overlay div a {
|
||||
|
||||
}
|
||||
|
||||
div.overlay input {
|
||||
display: inline-block;
|
||||
margin: 10px auto;
|
||||
text-decoration: none;
|
||||
border-color: #FFD700;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
padding: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
div#create {
|
||||
left: 600px;
|
||||
top: 400px;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 14;
|
||||
width: 320px;
|
||||
height: 344px;
|
||||
border: 3px inset #FFD700;
|
||||
display: none;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
div.overlay div a.cancel {
|
||||
font-size: small;
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
background-color: #222211;
|
||||
color: #EEC755;
|
||||
}
|
||||
|
||||
#diagramListContent ul {
|
||||
list-style-type: none;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
padding-inline-start: 0;
|
||||
|
||||
}
|
||||
|
||||
#diagramList {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#diagramList > h1 {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#diagramListContent li {
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
#diagramListContent li a {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
div.overlay div a.cancel:hover {
|
||||
background-color: #EEC700;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
|
||||
#main.mini {
|
||||
left: 100px;
|
||||
top: 200px;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#main.mini img, #tutorial img {
|
||||
width: 160px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
#main.mini div a, #tutorial div a {
|
||||
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: x-large;
|
||||
font-weight: bolder;
|
||||
text-align: center;
|
||||
color: #F9F9E9;
|
||||
}
|
||||
|
||||
#tutorial {
|
||||
z-index: 15;
|
||||
left: 100px;
|
||||
top: 750px;
|
||||
width: 160px;
|
||||
height: 210px;
|
||||
}
|
||||
|
||||
#password {
|
||||
display: none;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
#diagramList {
|
||||
left: 340px;
|
||||
top: 400px;
|
||||
height: 500px;
|
||||
background-color: #000;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#create {
|
||||
left: 500px;
|
||||
top: 340px;
|
||||
}
|
||||
|
||||
#create div {
|
||||
margin: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#closekey, #closekey a:active, #closekey a:visited, #closekey a:link {
|
||||
position: relative;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#enterXR {
|
||||
|
||||
}
|
||||
|
||||
div.overlay div.inactive a {
|
||||
background-color: #222222;
|
||||
color: #555555;
|
||||
border-color: #222222;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#enterXR.inactive {
|
||||
|
||||
}
|
||||
|
||||
#loadingGrid {
|
||||
position: relative;
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import {AbstractMesh, KeyboardEventTypes, MeshBuilder, Scene} from "@babylonjs/core";
|
||||
import {AbstractMesh, KeyboardEventTypes, Scene} from "@babylonjs/core";
|
||||
import {Rigplatform} from "./rigplatform";
|
||||
import {ControllerEventType, Controllers} from "./controllers";
|
||||
import {Controllers} from "./controllers";
|
||||
import {DiagramManager} from "../diagram/diagramManager";
|
||||
import {GridMaterial} from "@babylonjs/materials";
|
||||
import {wheelHandler} from "./functions/wheelHandler";
|
||||
import log, {Logger} from "loglevel";
|
||||
import {isDiagramEntity} from "../diagram/functions/isDiagramEntity";
|
||||
|
||||
export class WebController {
|
||||
private readonly scene: Scene;
|
||||
@ -30,15 +28,15 @@ export class WebController {
|
||||
this.diagramManager = diagramManager;
|
||||
this.controllers = controllers;
|
||||
this.canvas = document.querySelector('#gameCanvas');
|
||||
this.referencePlane = MeshBuilder.CreatePlane('referencePlane', {size: 10}, this.scene);
|
||||
this.referencePlane.setEnabled(false);
|
||||
this.referencePlane.visibility = 0.5;
|
||||
const material = new GridMaterial('grid', this.scene);
|
||||
//this.referencePlane = MeshBuilder.CreatePlane('referencePlane', {size: 10}, this.scene);
|
||||
//this.referencePlane.setEnabled(false);
|
||||
//this.referencePlane.visibility = 0.5;
|
||||
/*const material = new GridMaterial('grid', this.scene);
|
||||
material.gridRatio = 1;
|
||||
material.backFaceCulling = false;
|
||||
material.antialias = true;
|
||||
this.referencePlane.material = material;
|
||||
|
||||
*/
|
||||
|
||||
this.scene.onKeyboardObservable.add((kbInfo) => {
|
||||
this.logger.debug(kbInfo);
|
||||
@ -88,13 +86,15 @@ export class WebController {
|
||||
this.speed *= .5;
|
||||
break;
|
||||
case " ":
|
||||
if (kbInfo.event.ctrlKey) {
|
||||
/*if (kbInfo.event.ctrlKey) {
|
||||
if (this.controllers) {
|
||||
this.controllers.controllerObservable.notifyObservers(
|
||||
{type: ControllerEventType.X_BUTTON, value: 1}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
|
||||
@ -109,11 +109,13 @@ export class WebController {
|
||||
if (kbInfo.type == 1) {
|
||||
//this.referencePlane.setEnabled(true);
|
||||
} else {
|
||||
this.referencePlane.setEnabled(false);
|
||||
/* this.referencePlane.setEnabled(false);
|
||||
if (this.pickedMesh) {
|
||||
this.pickedMesh.showBoundingBox = false;
|
||||
this.pickedMesh = null;
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -121,11 +123,11 @@ export class WebController {
|
||||
this.scene.onPointerUp = () => {
|
||||
this.mouseDown = false;
|
||||
this.rig.turn(0);
|
||||
if (this.pickedMesh) {
|
||||
/*if (this.pickedMesh) {
|
||||
this.referencePlane.setEnabled(false);
|
||||
this.pickedMesh.showBoundingBox = false;
|
||||
this.pickedMesh = null;
|
||||
}
|
||||
}*/
|
||||
};
|
||||
|
||||
|
||||
@ -162,7 +164,8 @@ export class WebController {
|
||||
});
|
||||
this.scene.onPointerDown = (evt, state) => {
|
||||
if (evt.pointerType == "mouse") {
|
||||
if (evt.shiftKey) {
|
||||
this.mouseDown = true;
|
||||
/*if (evt.shiftKey) {
|
||||
//setMenuPosition(this.referencePlane, this.scene, new Vector3(0, 0, 5));
|
||||
//this.referencePlane.rotation = scene.activeCamera.absoluteRotation.toEulerAngles();
|
||||
this.pickedMesh = state.pickedMesh;
|
||||
@ -173,15 +176,15 @@ export class WebController {
|
||||
this.pickedMesh.rotation = scene.activeCamera.absoluteRotation.toEulerAngles();
|
||||
this.referencePlane.setEnabled(true);
|
||||
} else {
|
||||
this.mouseDown = true;
|
||||
/*
|
||||
|
||||
|
||||
if (state.pickedMesh) {
|
||||
|
||||
new ClickMenu(state.pickedMesh, state.gripTransform, this.diagramManager.onDiagramEventObservable);
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
} */
|
||||
}
|
||||
};
|
||||
this.scene.onPointerMove = (evt) => {
|
||||
@ -191,7 +194,7 @@ export class WebController {
|
||||
if (this.mouseDown) {
|
||||
this.rig.turn(evt.movementX);
|
||||
}
|
||||
const meshPickInfo = scene.pick(this.scene.pointerX, this.scene.pointerY, (mesh) => {
|
||||
/*const meshPickInfo = scene.pick(this.scene.pointerX, this.scene.pointerY, (mesh) => {
|
||||
return isDiagramEntity(mesh);
|
||||
});
|
||||
const planePickInfo = scene.pick(this.scene.pointerX, this.scene.pointerY, (mesh) => {
|
||||
@ -208,16 +211,17 @@ export class WebController {
|
||||
}
|
||||
} else {
|
||||
if (this.mesh) {
|
||||
/*this.diagramManager.onDiagramEventObservable.notifyObservers({
|
||||
this.diagramManager.onDiagramEventObservable.notifyObservers({
|
||||
type: DiagramEventType.MODIFY,
|
||||
entity: toDiagramEntity(this.mesh)
|
||||
}, DiagramEventObserverMask.ALL); */
|
||||
}, DiagramEventObserverMask.ALL);
|
||||
}
|
||||
this.mesh = null;
|
||||
}
|
||||
if (this.pickedMesh && planePickInfo.hit) {
|
||||
this.pickedMesh.position = planePickInfo.pickedPoint;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import {ConnectionPreview} from "../menus/connectionPreview";
|
||||
import {ScaleMenu2} from "../menus/ScaleMenu2";
|
||||
import {CameraMenu} from "../menus/cameraMenu";
|
||||
import {viewOnly} from "../util/functions/getPath";
|
||||
import {GroupMenu} from "../menus/groupMenu";
|
||||
|
||||
|
||||
export class DiagramMenuManager {
|
||||
@ -21,6 +22,7 @@ export class DiagramMenuManager {
|
||||
public readonly configMenu: ConfigMenu;
|
||||
private readonly _notifier: Observable<DiagramEvent>;
|
||||
private readonly _inputTextView: InputTextView;
|
||||
private _groupMenu: GroupMenu;
|
||||
private readonly _scene: Scene;
|
||||
private _cameraMenu: CameraMenu;
|
||||
private _logger = log.getLogger('DiagramMenuManager');
|
||||
@ -28,8 +30,6 @@ export class DiagramMenuManager {
|
||||
|
||||
constructor(notifier: Observable<DiagramEvent>, controllers: Controllers, config: AppConfig, readyObservable: Observable<boolean>) {
|
||||
this._scene = DefaultScene.Scene;
|
||||
|
||||
|
||||
this._notifier = notifier;
|
||||
this._inputTextView = new InputTextView(controllers);
|
||||
this.configMenu = new ConfigMenu(config);
|
||||
@ -92,6 +92,7 @@ export class DiagramMenuManager {
|
||||
const clickMenu = new ClickMenu(mesh);
|
||||
clickMenu.onClickMenuObservable.add((evt: ActionEvent) => {
|
||||
this._logger.debug(evt);
|
||||
|
||||
switch (evt.source.id) {
|
||||
case "remove":
|
||||
this.notifyAll({type: DiagramEventType.REMOVE, entity: {id: clickMenu.mesh.id}});
|
||||
@ -105,6 +106,9 @@ export class DiagramMenuManager {
|
||||
case "size":
|
||||
this.scaleMenu.show(clickMenu.mesh);
|
||||
break;
|
||||
case "group":
|
||||
this._groupMenu = new GroupMenu(clickMenu.mesh);
|
||||
break;
|
||||
case "close":
|
||||
this.scaleMenu.hide();
|
||||
break;
|
||||
|
||||
@ -19,6 +19,7 @@ export function toDiagramEntity(mesh: AbstractMesh): DiagramEntity {
|
||||
entity.position = vectoxys(mesh.absolutePosition);
|
||||
entity.rotation = vectoxys(mesh.absoluteRotationQuaternion.toEulerAngles());
|
||||
entity.last_seen = new Date();
|
||||
entity.image = mesh?.metadata?.image;
|
||||
entity.template = mesh?.metadata?.template;
|
||||
entity.text = mesh?.metadata?.text;
|
||||
entity.from = mesh?.metadata?.from;
|
||||
|
||||
@ -131,6 +131,10 @@ export class PouchdbPersistenceManager {
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.template == '#image-template' && !entity.image) {
|
||||
this._logger.error('no image data', entity);
|
||||
return;
|
||||
}
|
||||
if (this._encKey && !this._encryption.ready) {
|
||||
await this._encryption.setPassword(this._encKey);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {AbstractMesh, ActionEvent, Observable, Scene, TransformNode, Vector3} from "@babylonjs/core";
|
||||
import {Button} from "../objects/Button";
|
||||
|
||||
const POINTER_UP = "pointerup";
|
||||
import {positionNode} from "./functions/positionNode";
|
||||
import {isUp} from "./functions/isUp";
|
||||
|
||||
export class ClickMenu {
|
||||
private readonly _mesh: AbstractMesh;
|
||||
@ -46,7 +46,7 @@ export class ClickMenu {
|
||||
}
|
||||
}, -1, false, this, false);
|
||||
|
||||
this.makeNewButton("Close", "close", scene, x += .11)
|
||||
this.makeNewButton("Group", "group", scene, x += .11)
|
||||
.onPointerObservable.add((eventData) => {
|
||||
if (isUp(eventData)) {
|
||||
this.onClickMenuObservable.notifyObservers(eventData);
|
||||
@ -54,16 +54,14 @@ export class ClickMenu {
|
||||
}
|
||||
}, -1, false, this, false);
|
||||
|
||||
|
||||
const platform = scene.getMeshByName("platform");
|
||||
const ray = scene.activeCamera.getForwardRay(1);
|
||||
ray.direction.y = 0;
|
||||
const fpos = scene.activeCamera.globalPosition.clone().add(ray.direction.scale(1));
|
||||
this._transformNode.position = fpos;
|
||||
this._transformNode.position.y -= .4;
|
||||
this._transformNode.lookAt(scene.activeCamera.globalPosition);
|
||||
this._transformNode.rotate(Vector3.Up(), Math.PI);
|
||||
this._transformNode.setParent(platform);
|
||||
this.makeNewButton("Close", "close", scene, x += .11)
|
||||
.onPointerObservable.add((eventData) => {
|
||||
if (isUp(eventData)) {
|
||||
this.onClickMenuObservable.notifyObservers(eventData);
|
||||
this.dispose();
|
||||
}
|
||||
}, -1, false, this, false);
|
||||
positionNode(this._transformNode);
|
||||
}
|
||||
|
||||
public get mesh(): AbstractMesh {
|
||||
@ -85,7 +83,3 @@ export class ClickMenu {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
function isUp(event: ActionEvent): boolean {
|
||||
return event?.sourceEvent?.type == POINTER_UP;
|
||||
}
|
||||
7
src/menus/functions/isUp.ts
Normal file
7
src/menus/functions/isUp.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import {ActionEvent} from "@babylonjs/core";
|
||||
|
||||
const POINTER_UP = "pointerup";
|
||||
|
||||
export function isUp(event: ActionEvent): boolean {
|
||||
return event?.sourceEvent?.type == POINTER_UP;
|
||||
}
|
||||
15
src/menus/functions/positionNode.ts
Normal file
15
src/menus/functions/positionNode.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import {TransformNode, Vector3} from "@babylonjs/core";
|
||||
import {DefaultScene} from "../../defaultScene";
|
||||
|
||||
export function positionNode(transformNode: TransformNode) {
|
||||
const scene = DefaultScene.Scene;
|
||||
const platform = scene.getMeshByName("platform");
|
||||
const ray = scene.activeCamera.getForwardRay(1);
|
||||
ray.direction.y = 0;
|
||||
const fpos = scene.activeCamera.globalPosition.clone().add(ray.direction.scale(1));
|
||||
transformNode.position = fpos;
|
||||
transformNode.position.y -= .4;
|
||||
transformNode.lookAt(scene.activeCamera.globalPosition);
|
||||
transformNode.rotate(Vector3.Up(), Math.PI);
|
||||
transformNode.setParent(platform);
|
||||
}
|
||||
37
src/menus/groupMenu.ts
Normal file
37
src/menus/groupMenu.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import {AbstractMesh, Scene, TransformNode, Vector3} from "@babylonjs/core";
|
||||
import {Button} from "../objects/Button";
|
||||
import {positionNode} from "./functions/positionNode";
|
||||
import {isUp} from "./functions/isUp";
|
||||
|
||||
export class GroupMenu {
|
||||
private readonly _mesh: AbstractMesh;
|
||||
private readonly _scene: Scene;
|
||||
private _transformNode: TransformNode;
|
||||
|
||||
constructor(mesh: AbstractMesh) {
|
||||
this._mesh = mesh;
|
||||
this._scene = mesh.getScene();
|
||||
this._transformNode = new TransformNode("graoupTransform", this._scene);
|
||||
positionNode(this._transformNode);
|
||||
const button = this.buildButton("Done", "groupdone", this._scene, 0);
|
||||
button.onPointerObservable.add((eventData) => {
|
||||
if (isUp(eventData)) {
|
||||
this.dispose();
|
||||
}
|
||||
}, -1, false, this, false);
|
||||
}
|
||||
|
||||
private buildButton(name: string, id: string, scene: Scene, x: number): Button {
|
||||
const button = new Button(name, id, scene)
|
||||
button.transform.scaling = new Vector3(.2, .2, .2);
|
||||
button.transform.rotate(Vector3.Up(), Math.PI);
|
||||
const transform = button.transform;
|
||||
transform.parent = this._transformNode;
|
||||
transform.position.x = x;
|
||||
return button;
|
||||
}
|
||||
|
||||
private dispose() {
|
||||
this._transformNode.dispose(false, true);
|
||||
}
|
||||
}
|
||||
69
src/react/components/createMenu.tsx
Normal file
69
src/react/components/createMenu.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import axios from "axios";
|
||||
|
||||
export function CreateMenu({display, toggleCreateMenu}) {
|
||||
const onCreateClick = (evt) => {
|
||||
evt.preventDefault();
|
||||
const name = (document.querySelector('#createName') as HTMLInputElement).value;
|
||||
let password = (document.querySelector('#createPassword') as HTMLInputElement).value;
|
||||
const password2 = (document.querySelector('#createPassword2') as HTMLInputElement).value;
|
||||
if (password !== password2) {
|
||||
window.alert('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = window.crypto.randomUUID().replace(/-/g, '_');
|
||||
if (password.length == 0) {
|
||||
password = id;
|
||||
}
|
||||
const encrypted = (password != id);
|
||||
|
||||
localStorage.setItem(id, name);
|
||||
if (name && name.length > 4) {
|
||||
axios.post(import.meta.env.VITE_CREATE_ENDPOINT,
|
||||
{
|
||||
"_id": "org.couchdb.user:" + id,
|
||||
"name": id,
|
||||
"password": password,
|
||||
"roles": ["readers"],
|
||||
"type": "user"
|
||||
}
|
||||
).then(response => {
|
||||
console.log(response);
|
||||
const evt = new CustomEvent('dbcreated', {
|
||||
detail: {
|
||||
id: id,
|
||||
name: name,
|
||||
password: password,
|
||||
encrypted: encrypted
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(evt);
|
||||
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
window.alert('Name must be longer than 4 characters');
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="overlay" id="create" style={{'display': display}}>
|
||||
<div>
|
||||
<div>
|
||||
<label htmlFor="createName">Diagram Name</label>
|
||||
<input id="createName" placeholder="Enter a name for your diagram" type="text"/></div>
|
||||
<div>
|
||||
<label htmlFor="createPassoword">Optional Password</label>
|
||||
<input id="createPassword" placeholder="(Optional) Password" type="password"/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="createPassword2">Repeat Password</label>
|
||||
<input id="createPassword2" placeholder="(Optional) Password" type="password"/></div>
|
||||
<div><a href="#" id="createActionLink" onClick={onCreateClick}>Create</a></div>
|
||||
<div><a className="cancel" onClick={toggleCreateMenu} href="#" id="cancelCreateLink">Cancel</a></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
33
src/react/components/diagramList.tsx
Normal file
33
src/react/components/diagramList.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
export function DiagramList({display, onClick}) {
|
||||
const [dbList, setDbList] = useState([]);
|
||||
useEffect(() => {
|
||||
const listDb = async () => {
|
||||
const data = await indexedDB.databases();
|
||||
let i = 0;
|
||||
setDbList(data.filter((item) => item.name.indexOf('_pouch_') > -1).map((item) => {
|
||||
const dbid = item.name.replace('_pouch_', '');
|
||||
let friendlyName = localStorage.getItem(dbid);
|
||||
if (!friendlyName) {
|
||||
friendlyName = dbid;
|
||||
}
|
||||
return {key: dbid, name: friendlyName}
|
||||
}));
|
||||
};
|
||||
listDb();
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className="overlay" id="diagramList" style={{'display': display}}>
|
||||
<h1>Diagrams</h1>
|
||||
<div id="startCreate"><a href="#" id="startCreateLink" onClick={onClick}>New</a></div>
|
||||
<div id="diagramListContent">
|
||||
<ul>
|
||||
{dbList.map((item) => <li key={item.key}><a href={`/db/${item.key}`}>{item.name}</a></li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
9
src/react/components/keyboardHelp.tsx
Normal file
9
src/react/components/keyboardHelp.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
export function KeyboardHelp({display, onClick}) {
|
||||
return (
|
||||
<div className="overlay" id="keyboardHelp" style={{'display': display}}>
|
||||
<div id="closekey"><a href="#" onClick={onClick}>X</a></div>
|
||||
<img height="240" src="/assets/textures/keyboardhelp2.jpg" width="480"/>
|
||||
<img height="240" src="/assets/textures/mousehelp.jpg" width="180"/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
27
src/react/components/mainMenu.tsx
Normal file
27
src/react/components/mainMenu.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import {viewOnly} from "../../util/functions/getPath";
|
||||
import {QuestLink} from "./questLink";
|
||||
|
||||
export function MainMenu({onClick}) {
|
||||
if (viewOnly()) {
|
||||
return (
|
||||
<div className="overlay mini" id="main">
|
||||
<img height="120" src="/assets/ddd.svg" width="320"/>
|
||||
<div id="enterXR" className="inactive"><a href="#" id="enterVRLink">Enter VR</a></div>
|
||||
<QuestLink/>
|
||||
<div id="download"><a href="#" id="downloadLink">Download Model</a></div>
|
||||
</div>)
|
||||
} else {
|
||||
return (
|
||||
<div className="overlay mini" id="main">
|
||||
<img height="120" src="/assets/ddd.svg" width="320"/>
|
||||
<div id="enterXR" className="inactive"><a href="#" id="enterVRLink">Enter VR</a></div>
|
||||
<QuestLink/>
|
||||
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
51
src/react/components/menu.tsx
Normal file
51
src/react/components/menu.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import {useState} from "react";
|
||||
import {uploadImage} from "../functions/uploadImage";
|
||||
import {MainMenu} from "./mainMenu";
|
||||
import {CreateMenu} from "./createMenu";
|
||||
import {DiagramList} from "./diagramList";
|
||||
|
||||
export function Menu() {
|
||||
|
||||
const [createState, setCreateState] = useState('none');
|
||||
const [desktopTutorialState, setDesktopTutorialState] = useState('none');
|
||||
const [diagramListState, setDiagramListState] = useState('none');
|
||||
|
||||
function handleCreateClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||
evt.preventDefault();
|
||||
setCreateState(createState == 'none' ? 'block' : 'none');
|
||||
}
|
||||
|
||||
function handleDesktopTutorialClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||
evt.preventDefault();
|
||||
setDesktopTutorialState(desktopTutorialState == 'none' ? 'block' : 'none');
|
||||
}
|
||||
|
||||
function handleDiagramListClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||
evt.preventDefault();
|
||||
if (!evt.currentTarget.id) {
|
||||
return;
|
||||
}
|
||||
switch (evt.currentTarget.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 (
|
||||
<div>
|
||||
<MainMenu onClick={handleDiagramListClick}/>
|
||||
<CreateMenu display={createState} toggleCreateMenu={handleCreateClick}/>
|
||||
<DiagramList onClick={handleCreateClick} display={diagramListState}/>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
26
src/react/components/passwordDialog.tsx
Normal file
26
src/react/components/passwordDialog.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
export function PasswordDialog() {
|
||||
const onsubmitClick = (evt) => {
|
||||
evt.preventDefault();
|
||||
const password = (document.querySelector('#passwordInput') as HTMLInputElement).value;
|
||||
if (password.length < 4) {
|
||||
window.alert('Password must be longer than 4 characters');
|
||||
} else {
|
||||
const event = new CustomEvent('passwordset', {detail: password});
|
||||
document.dispatchEvent(event);
|
||||
(document.querySelector('#password') as HTMLInputElement).style.display = 'none';
|
||||
}
|
||||
}
|
||||
const onCancelClick = (evt) => {
|
||||
evt.preventDefault();
|
||||
(document.querySelector('#password') as HTMLInputElement).style.display = 'none';
|
||||
}
|
||||
return (
|
||||
<div className="overlay" id="password">
|
||||
<div>
|
||||
<div><input autoComplete="on" id="passwordInput" placeholder="Enter password" type="password"/></div>
|
||||
<div><a href="#" id="passwordActionLink" onClick={onsubmitClick}>Enter</a></div>
|
||||
<div><a className="cancel" href="#" onClick={onCancelClick} id="cancelPasswordLink">Cancel</a></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
8
src/react/components/questLink.tsx
Normal file
8
src/react/components/questLink.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
export function QuestLink() {
|
||||
const link = "https://www.oculus.com/open_url/?url=https://www.deepdiagram.com" + document.location.pathname;
|
||||
return (
|
||||
<div id="questLaunch">
|
||||
<a href={link} target="_blank">Launch On Quest</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
8
src/react/components/tutorialMenu.tsx
Normal file
8
src/react/components/tutorialMenu.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
export function TutorialMenu({onClick}) {
|
||||
return (
|
||||
<div className="overlay" id="tutorial">
|
||||
<h1>Help</h1>
|
||||
<div id="desktopTutorial"><a href="#" id="desktopLink" onClick={onClick}>Desktop</a></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
148
src/react/styles.css
Normal file
148
src/react/styles.css
Normal file
@ -0,0 +1,148 @@
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #999922;
|
||||
width: 100%;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
border-color: #FFD700;
|
||||
border-style: outset;
|
||||
border-width: 2px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
|
||||
a.cancel:link {
|
||||
font-size: smaller;
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
border-color: #E0C000;
|
||||
color: #FFEFBB;
|
||||
background-color: #888822;
|
||||
}
|
||||
|
||||
a:visited, a:link {
|
||||
color: white;
|
||||
background-color: #999922;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #FFD700;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
li a {
|
||||
text-align: left;
|
||||
border-radius: 2px;
|
||||
background-color: #338822;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
margin: 10px auto;
|
||||
text-decoration: none;
|
||||
border-color: #FFD700;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
padding: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: x-large;
|
||||
font-weight: bolder;
|
||||
text-align: center;
|
||||
color: #F9F9E9;
|
||||
}
|
||||
|
||||
div.overlay {
|
||||
position: absolute;
|
||||
display: block;
|
||||
border: 1px solid #555555;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 12;
|
||||
height: 120px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
div#create {
|
||||
left: 390px;
|
||||
top: 400px;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 14;
|
||||
width: 320px;
|
||||
height: 344px;
|
||||
border: 1px solid #222222;
|
||||
display: none;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
|
||||
#diagramList {
|
||||
overflow: scroll;
|
||||
left: 390px;
|
||||
top: 400px;
|
||||
height: 500px;
|
||||
background-color: #000;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
|
||||
#main.mini {
|
||||
left: 110px;
|
||||
top: 50%;
|
||||
|
||||
height: fit-content;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
#main.mini img, #tutorial img {
|
||||
width: 160px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
#tutorial {
|
||||
z-index: 15;
|
||||
left: 100px;
|
||||
top: 750px;
|
||||
width: 160px;
|
||||
height: 210px;
|
||||
}
|
||||
|
||||
#password {
|
||||
display: none;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
#closekey, #closekey a:active, #closekey a:visited, #closekey a:link {
|
||||
position: relative;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.inactive a {
|
||||
background-color: #222222;
|
||||
color: #555555;
|
||||
border-color: #222222;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@ -1,232 +1,12 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {uploadImage} from "./functions/uploadImage";
|
||||
import {viewOnly} from "../util/functions/getPath";
|
||||
import axios from "axios";
|
||||
|
||||
function MainMenu({onClick}) {
|
||||
if (viewOnly()) {
|
||||
return (
|
||||
<div className="overlay mini" id="main">
|
||||
<img height="120" src="/assets/ddd.svg" width="320"/>
|
||||
<div id="enterXR" className="inactive"><a href="#" id="enterVRLink">Enter VR</a></div>
|
||||
<QuestLink/>
|
||||
<div id="download"><a href="#" id="downloadLink">Download Model</a></div>
|
||||
</div>)
|
||||
} else {
|
||||
return (
|
||||
<div className="overlay mini" id="main">
|
||||
<img height="120" src="/assets/ddd.svg" width="320"/>
|
||||
<div id="enterXR" className="inactive"><a href="#" id="enterVRLink">Enter VR</a></div>
|
||||
<QuestLink/>
|
||||
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function PasswordDialog() {
|
||||
const onsubmitClick = (evt) => {
|
||||
evt.preventDefault();
|
||||
const password = (document.querySelector('#passwordInput') as HTMLInputElement).value;
|
||||
if (password.length < 4) {
|
||||
window.alert('Password must be longer than 4 characters');
|
||||
} else {
|
||||
const event = new CustomEvent('passwordset', {detail: password});
|
||||
document.dispatchEvent(event);
|
||||
document.querySelector('#password').style.display = 'none';
|
||||
}
|
||||
}
|
||||
const onCancelClick = (evt) => {
|
||||
evt.preventDefault();
|
||||
document.querySelector('#password').style.display = 'none';
|
||||
}
|
||||
return (
|
||||
<div className="overlay" id="password">
|
||||
<div>
|
||||
<div><input autoComplete="on" id="passwordInput" placeholder="Enter password" type="password"/></div>
|
||||
<div><a href="#" id="passwordActionLink" onClick={onsubmitClick}>Enter</a></div>
|
||||
<div><a className="cancel" href="#" onClick={onCancelClick} id="cancelPasswordLink">Cancel</a></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CreateMenu({display, toggleCreateMenu}) {
|
||||
const onCreateClick = (evt) => {
|
||||
evt.preventDefault();
|
||||
const name = (document.querySelector('#createName') as HTMLInputElement).value;
|
||||
let password = (document.querySelector('#createPassword') as HTMLInputElement).value;
|
||||
const password2 = (document.querySelector('#createPassword2') as HTMLInputElement).value;
|
||||
if (password !== password2) {
|
||||
window.alert('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = window.crypto.randomUUID().replace(/-/g, '_');
|
||||
if (password.length == 0) {
|
||||
password = id;
|
||||
}
|
||||
const encrypted = (password != id);
|
||||
|
||||
localStorage.setItem(id, name);
|
||||
if (name && name.length > 4) {
|
||||
axios.post(import.meta.env.VITE_CREATE_ENDPOINT,
|
||||
{
|
||||
"_id": "org.couchdb.user:" + id,
|
||||
"name": id,
|
||||
"password": password,
|
||||
"roles": ["readers"],
|
||||
"type": "user"
|
||||
}
|
||||
).then(response => {
|
||||
console.log(response);
|
||||
const evt = new CustomEvent('dbcreated', {
|
||||
detail: {
|
||||
id: id,
|
||||
name: name,
|
||||
password: password,
|
||||
encrypted: encrypted
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(evt);
|
||||
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
window.alert('Name must be longer than 4 characters');
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="overlay" id="create" style={{'display': display}}>
|
||||
<div>
|
||||
<div><input id="createName" placeholder="Enter a name for your diagram" type="text"/></div>
|
||||
<div><input id="createPassword" placeholder="(Optional) Password" type="password"/></div>
|
||||
<div><input id="createPassword2" placeholder="(Optional) Password" type="password"/></div>
|
||||
<div><a href="#" id="createActionLink" onClick={onCreateClick}>Create</a></div>
|
||||
<div><a className="cancel" onClick={toggleCreateMenu} href="#" id="cancelCreateLink">Cancel</a></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TutorialMenu({onClick}) {
|
||||
return (
|
||||
<div className="overlay" id="tutorial">
|
||||
<h1>Help</h1>
|
||||
<div id="desktopTutorial"><a href="#" id="desktopLink" onClick={onClick}>Desktop</a></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function KeyboardHelp({display, onClick}) {
|
||||
return (
|
||||
<div className="overlay" id="keyboardHelp" style={{'display': display}}>
|
||||
<div id="closekey"><a href="#" onClick={onClick}>X</a></div>
|
||||
<img height="240" src="/assets/textures/keyboardhelp2.jpg" width="480"/>
|
||||
<img height="240" src="/assets/textures/mousehelp.jpg" width="180"/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function QuestLink() {
|
||||
const link = "https://www.oculus.com/open_url/?url=https://www.deepdiagram.com" + document.location.pathname;
|
||||
return (
|
||||
<div id="questLaunch">
|
||||
<a href={link} target="_blank">Launch On Quest</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DiagramList({display, onClick}) {
|
||||
const [dbList, setDbList] = useState([]);
|
||||
useEffect(() => {
|
||||
const listDb = async () => {
|
||||
const data = await indexedDB.databases();
|
||||
let i = 0;
|
||||
setDbList(data.filter((item) => item.name.indexOf('_pouch_') > -1).map((item) => {
|
||||
const dbid = item.name.replace('_pouch_', '');
|
||||
let friendlyName = localStorage.getItem(dbid);
|
||||
if (!friendlyName) {
|
||||
friendlyName = dbid;
|
||||
}
|
||||
return {key: dbid, name: friendlyName}
|
||||
}));
|
||||
};
|
||||
listDb();
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className="overlay" id="diagramList" style={{'display': display}}>
|
||||
<h1>Diagrams</h1>
|
||||
<div id="startCreate"><a href="#" id="startCreateLink" onClick={onClick}>New</a></div>
|
||||
<div id="diagramListContent">
|
||||
<ul>
|
||||
{dbList.map((item) => <li key={item.key}><a href={`/db/${item.key}`}>{item.name}</a></li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Menu() {
|
||||
|
||||
const [createState, setCreateState] = useState('none');
|
||||
const [desktopTutorialState, setDesktopTutorialState] = useState('none');
|
||||
const [diagramListState, setDiagramListState] = useState('none');
|
||||
|
||||
function handleCreateClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||
evt.preventDefault();
|
||||
setCreateState(createState == 'none' ? 'block' : 'none');
|
||||
}
|
||||
|
||||
function handleDesktopTutorialClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||
evt.preventDefault();
|
||||
setDesktopTutorialState(desktopTutorialState == 'none' ? 'block' : 'none');
|
||||
}
|
||||
|
||||
function handleDiagramListClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||
evt.preventDefault();
|
||||
if (!evt.currentTarget.id) {
|
||||
return;
|
||||
}
|
||||
switch (evt.currentTarget.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 (
|
||||
<div>
|
||||
<MainMenu onClick={handleDiagramListClick}/>
|
||||
<CreateMenu display={createState} toggleCreateMenu={handleCreateClick}/>
|
||||
<DiagramList onClick={handleCreateClick} display={diagramListState}/>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
import {PasswordDialog} from "./components/passwordDialog";
|
||||
import {Menu} from "./components/menu";
|
||||
import "./styles.css";
|
||||
|
||||
export default function WebApp() {
|
||||
document.addEventListener('promptpassword', (evt) => {
|
||||
const password = document.querySelector('#password');
|
||||
if (password) {
|
||||
password.style.display = 'block';
|
||||
(password as HTMLInputElement).style.display = 'block';
|
||||
}
|
||||
});
|
||||
return (
|
||||
|
||||
@ -17,7 +17,9 @@ export async function buildColor(color: Color3, scene: Scene, parent: TransformN
|
||||
const height = .1;
|
||||
const material = new StandardMaterial("material-" + color.toHexString(), scene);
|
||||
material.diffuseColor = color;
|
||||
|
||||
material.ambientColor = color;
|
||||
//material.roughness = 1;
|
||||
material.specularPower = 64;
|
||||
// material.ambientColor = color;
|
||||
//material.roughness = .1;
|
||||
//material.maxSimultaneousLights = 2;
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import {
|
||||
Color3,
|
||||
GroundMesh,
|
||||
HemisphericLight,
|
||||
Material,
|
||||
MeshBuilder,
|
||||
Observable,
|
||||
PBRMaterial,
|
||||
PhysicsAggregate,
|
||||
PhysicsShapeType,
|
||||
PointLight,
|
||||
PointsCloudSystem,
|
||||
Scene,
|
||||
Sound,
|
||||
@ -33,14 +33,15 @@ export class CustomEnvironment {
|
||||
if (loading) {
|
||||
loading.remove();
|
||||
}
|
||||
this.scene.ambientColor = new Color3(1, 1, 1);
|
||||
//const light = new HemisphericLight("light1", new Vector3(1, 2, 1), this.scene);
|
||||
//light.groundColor = new Color3(.1, .1, .1)
|
||||
//light.diffuse = new Color3(1, 1, 1);
|
||||
this.scene.ambientColor = new Color3(.1, .1, .1);
|
||||
const light = new HemisphericLight("light1", new Vector3(.5, 1, 1).normalize(), this.scene);
|
||||
light.groundColor = new Color3(0, 0, 0);
|
||||
light.diffuse = new Color3(1, 1, 1);
|
||||
light.intensity = .8;
|
||||
//light.setDirectionToTarget(new Vector3(.4, .5, .5).normalize());
|
||||
//light.intensity = .7;
|
||||
const light = new PointLight("light1", new Vector3(0, 10, 10), this.scene);
|
||||
const light2 = new PointLight("light1", new Vector3(0, 10, -10), this.scene);
|
||||
//const light = new PointLight("light1", new Vector3(0, 10, 10), this.scene);
|
||||
//const light2 = new PointLight("light1", new Vector3(0, 10, -10), this.scene);
|
||||
const physics = new CustomPhysics(this.scene, config);
|
||||
physics
|
||||
.initializeAsync()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user