Updated UI to use Mantine.
This commit is contained in:
parent
b9152678b8
commit
2397ddcd4c
61
index.html
61
index.html
@ -6,44 +6,23 @@
|
|||||||
<meta content="An immersive vr diagramming experience based using webxr version 0.0.8-14 (2024-07-03T13:09:05.707Z) 4fdcc9694d3614be538e425110d1ab50cd20b302"
|
<meta content="An immersive vr diagramming experience based using webxr version 0.0.8-14 (2024-07-03T13:09:05.707Z) 4fdcc9694d3614be538e425110d1ab50cd20b302"
|
||||||
name="description">
|
name="description">
|
||||||
<meta content="width=device-width, initial-scale=1, height=device-height" name="viewport">
|
<meta content="width=device-width, initial-scale=1, height=device-height" name="viewport">
|
||||||
<link href="/styles.css" rel="stylesheet">
|
<!--<link href="/styles.css" rel="stylesheet"> -->
|
||||||
<link href="/assets/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
|
<link href="/assets/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
|
||||||
<link href="/assets/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
|
<link href="/assets/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
|
||||||
<link href="/assets/favicon-96x96.png" rel="icon" sizes="96x96" type="image/png">
|
<link href="/assets/favicon-96x96.png" rel="icon" sizes="96x96" type="image/png">
|
||||||
<title>Deep Diagram</title>
|
<title>Deep Diagram</title>
|
||||||
<link as="script" href="/newRelic.js" rel="preload">
|
<!-- <link as="script" href="/newRelic.js" rel="preload">
|
||||||
<script defer src="/newRelic.js"></script>
|
<script defer src="/newRelic.js"></script> -->
|
||||||
<script defer src="/src/webApp.ts" type="module"></script>
|
|
||||||
<script defer src="/src/vrApp.ts" type="module"></script>
|
|
||||||
<link href="/manifest.webmanifest" rel="manifest"/>
|
<link href="/manifest.webmanifest" rel="manifest"/>
|
||||||
<!--<script src='/niceware.js'></script>-->
|
<!--<script src='/niceware.js'></script>-->
|
||||||
<style>
|
<style>
|
||||||
#feed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#keyboardHelp {
|
|
||||||
display: none;
|
|
||||||
width: 665px;
|
|
||||||
height: 312px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#keyboardHelp .button {
|
|
||||||
|
|
||||||
background-color: white;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#keyboardHelp div {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<img id="loadingGrid" src="/assets/grid6.jpg"/>
|
|
||||||
<script>
|
<script>
|
||||||
if (typeof navigator.serviceWorker !== 'undefined') {
|
if (typeof navigator.serviceWorker !== 'undefined') {
|
||||||
if (localStorage.getItem('serviceWorkerVersion') !== '11') {
|
if (localStorage.getItem('serviceWorkerVersion') !== '11') {
|
||||||
@ -57,38 +36,18 @@
|
|||||||
navigator.serviceWorker.register('/sw.js', {updateViaCache: 'none'});
|
navigator.serviceWorker.register('/sw.js', {updateViaCache: 'none'});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
|
||||||
/*
|
|
||||||
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition
|
|
||||||
var SpeechGrammarList = SpeechGrammarList || window.webkitSpeechGrammarList
|
|
||||||
var SpeechRecognitionEvent = SpeechRecognitionEvent || webkitSpeechRecognitionEvent
|
|
||||||
var recognition = new SpeechRecognition();
|
|
||||||
recognition.continuous = false;
|
|
||||||
recognition.lang = 'en-US';
|
|
||||||
recognition.interimResults = true;
|
|
||||||
recognition.maxAlternatives = 1;
|
|
||||||
recognition.onresult = function(event) {
|
|
||||||
console.log(event.results[0][0].transcript);
|
|
||||||
}
|
|
||||||
recognition.onend = function() {
|
|
||||||
console.log("recognition ended");
|
|
||||||
recognition.start();
|
|
||||||
}
|
|
||||||
console.log("starting recognition");
|
|
||||||
recognition.start();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<div class="webApp" id="webApp">
|
<div class="webApp" id="webApp">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<script defer src="/src/webApp.ts" type="module"></script>
|
||||||
|
|
||||||
<!--<video id="feed" controls="" autoplay="" name="media"><source src="https://listen.broadcastify.com/1drb2xhywkg8nvz.mp3?nc=49099&xan=xtf9912b41c" type="audio/mpeg"></video> -->
|
<!--<video id="feed" controls="" autoplay="" name="media"><source src="https://listen.broadcastify.com/1drb2xhywkg8nvz.mp3?nc=49099&xan=xtf9912b41c" type="audio/mpeg"></video> -->
|
||||||
|
<!--
|
||||||
<div class="scene">
|
<div class="scene">
|
||||||
<canvas id="gameCanvas"></canvas>
|
<canvas id="gameCanvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
|
<!--<script defer src="/src/vrApp.ts" type="module"></script>-->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
1620
package-lock.json
generated
1620
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "immersive",
|
"name": "immersive",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.8-16",
|
"version": "0.0.8-17",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
@ -17,6 +17,7 @@
|
|||||||
"havok": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps"
|
"havok": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@auth0/auth0-react": "^2.2.4",
|
||||||
"@babylonjs/core": "^7.21.5",
|
"@babylonjs/core": "^7.21.5",
|
||||||
"@babylonjs/gui": "^7.21.5",
|
"@babylonjs/gui": "^7.21.5",
|
||||||
"@babylonjs/havok": "1.3.4",
|
"@babylonjs/havok": "1.3.4",
|
||||||
@ -24,6 +25,10 @@
|
|||||||
"@babylonjs/loaders": "^7.21.5",
|
"@babylonjs/loaders": "^7.21.5",
|
||||||
"@babylonjs/materials": "^7.21.5",
|
"@babylonjs/materials": "^7.21.5",
|
||||||
"@babylonjs/serializers": "^7.21.5",
|
"@babylonjs/serializers": "^7.21.5",
|
||||||
|
"@emotion/react": "^11.13.0",
|
||||||
|
"@mantine/core": "7.12.0",
|
||||||
|
"@mantine/form": "7.12.0",
|
||||||
|
"@mantine/hooks": "7.12.0",
|
||||||
"@maptiler/client": "1.8.1",
|
"@maptiler/client": "1.8.1",
|
||||||
"@picovoice/cobra-web": "^2.0.3",
|
"@picovoice/cobra-web": "^2.0.3",
|
||||||
"@picovoice/eagle-web": "^1.0.0",
|
"@picovoice/eagle-web": "^1.0.0",
|
||||||
@ -33,24 +38,25 @@
|
|||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.2.22",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"canvas-hypertxt": "1.0.3",
|
"canvas-hypertxt": "1.0.3",
|
||||||
|
"events": "^3.3.0",
|
||||||
|
"hash-wasm": "4.11.0",
|
||||||
"hls.js": "^1.1.4",
|
"hls.js": "^1.1.4",
|
||||||
|
"js-crypto-aes": "1.0.6",
|
||||||
"loglevel": "^1.9.1",
|
"loglevel": "^1.9.1",
|
||||||
|
"meaningful-string": "^1.4.0",
|
||||||
"peer-lite": "2.0.2",
|
"peer-lite": "2.0.2",
|
||||||
"pouchdb": "^8.0.1",
|
"pouchdb": "^8.0.1",
|
||||||
"pouchdb-find": "^8.0.1",
|
"pouchdb-find": "^8.0.1",
|
||||||
"query-string": "^8.1.0",
|
"query-string": "^8.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-router-dom": "^6.26.1",
|
||||||
|
"@tabler/icons-react": "^3.14.0",
|
||||||
"recordrtc": "^5.6.0",
|
"recordrtc": "^5.6.0",
|
||||||
"rfc4648": "^1.5.3",
|
"rfc4648": "^1.5.3",
|
||||||
"round": "^2.0.1",
|
"round": "^2.0.1",
|
||||||
"uuid": "^9.0.1",
|
|
||||||
"js-crypto-aes": "1.0.6",
|
|
||||||
"events": "^3.3.0",
|
|
||||||
"hash-wasm": "4.11.0",
|
|
||||||
"uint8-to-b64": "^1.0.2",
|
"uint8-to-b64": "^1.0.2",
|
||||||
"meaningful-string": "^1.4.0",
|
"uuid": "^9.0.1",
|
||||||
"websocket-ts": "^2.1.5",
|
"websocket": "^1.0.34",
|
||||||
"websocket": "^1.0.34"
|
"websocket-ts": "^2.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dom-to-image": "^2.6.7",
|
"@types/dom-to-image": "^2.6.7",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.1.0/workbox-sw.js');
|
importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.1.0/workbox-sw.js');
|
||||||
const VERSION = '@@VERSION';
|
const VERSION = '0.0.8-18';
|
||||||
const CACHE = "deepdiagram";
|
const CACHE = "deepdiagram";
|
||||||
const IMAGEDELIVERY_CACHE = "deepdiagram-images";
|
const IMAGEDELIVERY_CACHE = "deepdiagram-images";
|
||||||
const MAPTILE_CACHE = 'maptiler';
|
const MAPTILE_CACHE = 'maptiler';
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import {useEffect, useState} from "react";
|
|
||||||
|
|
||||||
export function DiagramList({display, onClick}) {
|
|
||||||
const [dbList, setDbList] = useState([]);
|
|
||||||
useEffect(() => {
|
|
||||||
const listDb = async () => {
|
|
||||||
const data = await indexedDB.databases();
|
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
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 alt="keyboard help" height="240" src="/assets/textures/keyboardhelp2.jpg" width="480"/>
|
|
||||||
<img alt="mouse help" height="240" src="/assets/textures/mousehelp.jpg" width="180"/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import {viewOnly} from "../../util/functions/getPath";
|
|
||||||
import {QuestLink} from "./questLink";
|
|
||||||
|
|
||||||
export function MainMenu({onClick}) {
|
|
||||||
if (viewOnly()) {
|
|
||||||
return (
|
|
||||||
<div className="overlay mini" id="main">
|
|
||||||
<img alt="deep diagram logo" 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 alt="deep diagram logo" 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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
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>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
29
src/react/components/vrMenuItem.tsx
Normal file
29
src/react/components/vrMenuItem.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {Indicator, Menu, Tooltip} from "@mantine/core";
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
export default function VrMenuItem({availableIcon, tip, label, onClick}) {
|
||||||
|
const [processing, setProcessing] = useState(true);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
setProcessing(false);
|
||||||
|
}, 2000)
|
||||||
|
if (availableIcon) {
|
||||||
|
return (<Tooltip multiline={true} w={256}
|
||||||
|
label={tip} position="right">
|
||||||
|
<Indicator size={25} radius="lg" offset={-15} processing={processing} position="middle-end"
|
||||||
|
label={availableIcon}>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={onClick || null}>
|
||||||
|
{label}</Menu.Item>
|
||||||
|
</Indicator>
|
||||||
|
</Tooltip>)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Tooltip multiline={true} w={256}
|
||||||
|
label={tip} position="right">
|
||||||
|
<Menu.Item
|
||||||
|
onClick={onClick || null}>
|
||||||
|
{label}</Menu.Item>
|
||||||
|
</Tooltip>)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
83
src/react/marketing/about.tsx
Normal file
83
src/react/marketing/about.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import {Accordion, Card, Center} from "@mantine/core";
|
||||||
|
import PageTemplate from "../pageTemplate";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<PageTemplate>
|
||||||
|
<Card m={64}>
|
||||||
|
<Card.Section>
|
||||||
|
<Center>
|
||||||
|
<h1>VisuaLab VR: Unleash the Power of Immersive Diagramming</h1>
|
||||||
|
</Center>
|
||||||
|
</Card.Section>
|
||||||
|
<Card.Section>
|
||||||
|
<h3>Diagram complex systems effortlessly with VisuaLab VR – fast, intuitive, and 40x more
|
||||||
|
effective.</h3>
|
||||||
|
</Card.Section>
|
||||||
|
<Card.Section>
|
||||||
|
<Accordion defaultValue="1">
|
||||||
|
<Accordion.Item key="1" value="1">
|
||||||
|
<Accordion.Control>What is VisuaLab VR?</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
Step into a world where complex ideas come to life in 3D. With VisuaLab VR, you’re not
|
||||||
|
just
|
||||||
|
creating diagrams – you’re immersing yourself in them. Our cutting-edge VR technology
|
||||||
|
transforms the
|
||||||
|
way you visualize, understand, and communicate complex systems. Whether you’re mapping
|
||||||
|
out intricate
|
||||||
|
workflows, designing sophisticated models, or explaining multifaceted processes,
|
||||||
|
VisuaLab VR makes
|
||||||
|
it easy.
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item key="2" value="2">
|
||||||
|
<Accordion.Control>Why Choose VisuaLab VR?</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
Immersive Experience: Traditional 2D diagrams can’t compare to the power of full
|
||||||
|
immersion. When you
|
||||||
|
step into VisuaLab VR, you interact with your diagrams in three dimensions, making it 40
|
||||||
|
times more
|
||||||
|
effective at explaining complex systems. See every connection, understand every
|
||||||
|
relationship, and
|
||||||
|
explore every detail as if you’re inside the system itself.
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item key="3" value="3">
|
||||||
|
<Accordion.Control>Fast and Intuitive</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
No steep learning curve here. VisuaLab VR is designed for speed and simplicity.
|
||||||
|
Drag, drop, and connect elements with ease, all in a visually rich environment. Create
|
||||||
|
stunning
|
||||||
|
diagrams in minutes, not hours.
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item key="4" value="4">
|
||||||
|
<Accordion.Control>Affordable Innovation</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
High-quality VR doesn’t have to break the bank. VisuaLab VR offers premium
|
||||||
|
features at a fraction of the cost, making it accessible for everyone from solo creators
|
||||||
|
to large
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
|
||||||
|
<Accordion.Item key="5" value="5">
|
||||||
|
<Accordion.Control>The Science Behind It</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
Studies show that immersive environments increase the ability to
|
||||||
|
comprehension and retention by up to 40 times compared to traditional 2D methods. By
|
||||||
|
engaging
|
||||||
|
multiple senses and providing a spatial understanding, VisuaLab VR allows you to grasp
|
||||||
|
complex
|
||||||
|
systems faster and with greater clarity. You’re not just looking at a diagram; you’re
|
||||||
|
experiencing
|
||||||
|
it. This deep engagement leads to quicker insights, more efficient problem-solving, and
|
||||||
|
better
|
||||||
|
decision-making.
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
|
</Card.Section>
|
||||||
|
</Card>
|
||||||
|
</PageTemplate>
|
||||||
|
)
|
||||||
|
}
|
||||||
10
src/react/marketing/documentation.tsx
Normal file
10
src/react/marketing/documentation.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import PageTemplate from "../pageTemplate";
|
||||||
|
|
||||||
|
export default function Documentation() {
|
||||||
|
return (
|
||||||
|
<PageTemplate>
|
||||||
|
<h1>Documentation</h1>
|
||||||
|
<p>Some information about the documentation</p>
|
||||||
|
</PageTemplate>
|
||||||
|
)
|
||||||
|
}
|
||||||
10
src/react/marketing/examples.tsx
Normal file
10
src/react/marketing/examples.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import PageTemplate from "../pageTemplate";
|
||||||
|
|
||||||
|
export default function Examples() {
|
||||||
|
return (
|
||||||
|
<PageTemplate>
|
||||||
|
<h1>Examples</h1>
|
||||||
|
<p>Some information about the examples</p>
|
||||||
|
</PageTemplate>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
src/react/marketing/pricing.tsx
Normal file
22
src/react/marketing/pricing.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {Card, SimpleGrid} from "@mantine/core";
|
||||||
|
import PageTemplate from "../pageTemplate";
|
||||||
|
|
||||||
|
export default function Pricing() {
|
||||||
|
return (
|
||||||
|
<PageTemplate>
|
||||||
|
<h1>Pricing</h1>
|
||||||
|
<SimpleGrid cols={{base: 1, sm: 2, md: 3}} spacing="lg">
|
||||||
|
<Card>
|
||||||
|
Free
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
Basic
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
Team
|
||||||
|
</Card>
|
||||||
|
</SimpleGrid>
|
||||||
|
<p>Some information about the pricing</p>
|
||||||
|
</PageTemplate>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
src/react/pageHeader.tsx
Normal file
34
src/react/pageHeader.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {Anchor, AppShell, Button, Group, Image} from "@mantine/core";
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
|
export default function PageHeader() {
|
||||||
|
const onClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AppShell.Header p={10}>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Image w={64} src="/assets/ddd.svg"/>
|
||||||
|
<Group justify="flex-end">
|
||||||
|
<Button key="signup">Sign up for Free</Button>
|
||||||
|
<Group visibleFrom="sm">
|
||||||
|
<Anchor component={Link} key="examples" to="/examples" p={5} c="myColor" bg="none"
|
||||||
|
underline="hover">Examples</Anchor>
|
||||||
|
<Anchor component={Link} key="about" to="/" p={5} c="myColor" bg="none"
|
||||||
|
underline="hover">About</Anchor>
|
||||||
|
<Anchor component={Link} key="documentation" to="/documentation" p={5} c="myColor" bg="none"
|
||||||
|
underline="hover">Documentation</Anchor>
|
||||||
|
<Anchor component={Link} key="pricing" to="/pricing" p={5} c="myColor" bg="none"
|
||||||
|
underline="hover">Pricing</Anchor>
|
||||||
|
<Anchor component={Link} key="vrexperience" to="/db/local" p={5} c="myColor" bg="none"
|
||||||
|
underline="hover">VR Experience</Anchor>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Button>Login</Button>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</AppShell.Header>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/react/pageTemplate.tsx
Normal file
15
src/react/pageTemplate.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {AppShell} from "@mantine/core";
|
||||||
|
import PageHeader from "./pageHeader";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function PageTemplate(props: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<AppShell
|
||||||
|
header={{height: 64}}>
|
||||||
|
<PageHeader/>
|
||||||
|
<AppShell.Main>
|
||||||
|
{props.children}
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
)
|
||||||
|
}
|
||||||
42
src/react/pages/createDiagramModal.tsx
Normal file
42
src/react/pages/createDiagramModal.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import {Anchor, Button, Checkbox, Group, Modal, Pill, Stack, Textarea, TextInput} from "@mantine/core";
|
||||||
|
|
||||||
|
export default function CreateDiagramModal({createOpened, setCreateOpened}) {
|
||||||
|
|
||||||
|
const createDiagram = () => {
|
||||||
|
setCreateOpened(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal opened={createOpened} onClose={() => {
|
||||||
|
setCreateOpened(false)
|
||||||
|
}}>
|
||||||
|
<Stack>
|
||||||
|
|
||||||
|
|
||||||
|
<TextInput key="name" label="Name" placeholder="Enter diagram name" required/>
|
||||||
|
<Textarea key="description" label="Description" placeholder="Enter diagram description"/>
|
||||||
|
<Group>
|
||||||
|
<Checkbox w={250} key="private" label="Private" disabled={true}/>
|
||||||
|
<Pill>Basic</Pill>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Checkbox w={250} key="encrypted" label="Encrypted" disabled={true}/>
|
||||||
|
<Pill>Pro</Pill>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Checkbox w={250} key="invite" label="Invite Collaborators" disabled={true}/>
|
||||||
|
<Pill>Pro</Pill>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Button key="create" onClick={createDiagram}>Create</Button>
|
||||||
|
<Anchor p={5} size="sm" key="cancel" onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setCreateOpened(false)
|
||||||
|
}}>Cancel</Anchor>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
42
src/react/pages/manageDiagramsModal.tsx
Normal file
42
src/react/pages/manageDiagramsModal.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import {Button, Card, Modal, Paper, SimpleGrid, Stack} from "@mantine/core";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function ManageDiagramsModal({manageOpened, setManageOpened}) {
|
||||||
|
const diagrams = [
|
||||||
|
{name: "Diagram 1", description: "Description 1"},
|
||||||
|
{name: "Diagram 1", description: "Description 1"},
|
||||||
|
{name: "Diagram 1", description: "Description 1"},
|
||||||
|
{name: "Diagram 1", description: "Description 1"},
|
||||||
|
{name: "Diagram 1", description: "Description 1"},
|
||||||
|
{name: "Diagram 1", description: "Description 1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
const cards = diagrams.map((diagram) => {
|
||||||
|
return (<Card><h1>{diagram.name}</h1></Card>)
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildCreateButton = () => {
|
||||||
|
if (diagrams.length < 6) {
|
||||||
|
return <Button size="lg" disabled={false}>Create</Button>
|
||||||
|
} else {
|
||||||
|
return (<Stack>
|
||||||
|
<Button size="lg" disabled={true}>Create</Button>
|
||||||
|
<Paper>You've reached the max number of diagrams for this Tier.</Paper>
|
||||||
|
<Button size="xl">Upgrade To Pro</Button>
|
||||||
|
</Stack>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal opened={manageOpened} size="lg" onClose={() => {
|
||||||
|
setManageOpened(false)
|
||||||
|
}}>
|
||||||
|
<h1>Select a Diagram</h1>
|
||||||
|
<SimpleGrid cols={3}>
|
||||||
|
{cards}
|
||||||
|
</SimpleGrid>
|
||||||
|
{buildCreateButton()}
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
99
src/react/pages/vrExperience.tsx
Normal file
99
src/react/pages/vrExperience.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import VrApp from '../../vrApp';
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {Affix, Burger, Group, Menu, useMantineTheme} from "@mantine/core";
|
||||||
|
import VrTemplate from "../vrTemplate";
|
||||||
|
import {IconStar} from "@tabler/icons-react";
|
||||||
|
import VrMenuItem from "../components/vrMenuItem";
|
||||||
|
import CreateDiagramModal from "./createDiagramModal";
|
||||||
|
import ManageDiagramsModal from "./manageDiagramsModal";
|
||||||
|
|
||||||
|
|
||||||
|
export default function VrExperience() {
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const [createDiagram, setCreateDiagram] = useState(false);
|
||||||
|
const [manageDiagrams, setManageDiagrams] = useState(false);
|
||||||
|
const [immersiveDisabled, setImmersiveDisabled] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
const vrApp = new VrApp(document.querySelector('#gameCanvas')); // code to run after render goes here
|
||||||
|
}, []);
|
||||||
|
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
|
||||||
|
setImmersiveDisabled(!supported);
|
||||||
|
});
|
||||||
|
|
||||||
|
const availableInFree = () => {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const availableInBasic = () => {
|
||||||
|
return <Group w={50}>Basic</Group>
|
||||||
|
}
|
||||||
|
const availableInPro = () => {
|
||||||
|
return <Group w={50}>Pro!<IconStar size={11}/></Group>
|
||||||
|
}
|
||||||
|
|
||||||
|
const enterImmersive = (e) => {
|
||||||
|
console.log('entering immersive mode');
|
||||||
|
e.preventDefault();
|
||||||
|
const event = new CustomEvent('enterXr', {bubbles: true});
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<VrTemplate>
|
||||||
|
<Affix position={{top: 30, left: 60}}>
|
||||||
|
<Menu trigger="hover" openDelay={50} closeDelay={400}>
|
||||||
|
<Menu.Target>
|
||||||
|
<Burger size="xl"/>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<VrMenuItem
|
||||||
|
tip={immersiveDisabled ? "Browser does not support WebXR. Immersive experience best viewed with Meta Quest headset" : "Enter Immersive Mode"}
|
||||||
|
onClick={enterImmersive}
|
||||||
|
label="Enter Immersive Mode"
|
||||||
|
availableIcon={availableInFree()}/>
|
||||||
|
<VrMenuItem
|
||||||
|
tip="Open a new window and automatically send experience to your Meta Quest headset"
|
||||||
|
onClick={() => {
|
||||||
|
window.open('https://www.oculus.com/open_url/?url=' + window.location.href, 'launchQuest', 'popup')
|
||||||
|
}}
|
||||||
|
label="Launch On Meta Quest"
|
||||||
|
availableIcon={availableInFree()}/>
|
||||||
|
<Menu.Divider/>
|
||||||
|
<VrMenuItem
|
||||||
|
tip="Edit data on desktop (Best for large amounts of text or images). After adding data, you can enter immersive mode to further refine the model."
|
||||||
|
label="Edit Data"
|
||||||
|
onClick={null}
|
||||||
|
availableIcon={availableInFree()}/>
|
||||||
|
<Menu.Divider/>
|
||||||
|
<VrMenuItem
|
||||||
|
tip="Create a new diagram from scratch"
|
||||||
|
label="Create"
|
||||||
|
onClick={() => {
|
||||||
|
setCreateDiagram(!createDiagram)
|
||||||
|
}}
|
||||||
|
availableIcon={availableInFree()}/>
|
||||||
|
<VrMenuItem
|
||||||
|
tip="Create a new diagram from predefined template"
|
||||||
|
label="Create From Template"
|
||||||
|
onClick={null}
|
||||||
|
availableIcon={availableInBasic()}/>
|
||||||
|
<VrMenuItem
|
||||||
|
tip="Manage Diagrams"
|
||||||
|
label="Manage"
|
||||||
|
onClick={() => {
|
||||||
|
setManageDiagrams(!manageDiagrams)
|
||||||
|
}}
|
||||||
|
availableIcon={availableInFree()}/>
|
||||||
|
<Menu.Divider/>
|
||||||
|
<VrMenuItem
|
||||||
|
tip="Share your model with others and collaborate in real time with others. This is a paid feature."
|
||||||
|
label="Share"
|
||||||
|
onClick={null}
|
||||||
|
availableIcon={availableInPro()}/>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
</Affix>
|
||||||
|
<canvas id="gameCanvas" style={{width: '100%', height: '100vh'}}/>
|
||||||
|
<CreateDiagramModal createOpened={createDiagram} setCreateOpened={setCreateDiagram}/>
|
||||||
|
<ManageDiagramsModal manageOpened={manageDiagrams} setManageOpened={setManageDiagrams}/>
|
||||||
|
</VrTemplate>
|
||||||
|
)
|
||||||
|
}
|
||||||
32
src/react/theme.ts
Normal file
32
src/react/theme.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {createTheme, MantineColorsTuple} from "@mantine/core";
|
||||||
|
|
||||||
|
const myColor: MantineColorsTuple = [
|
||||||
|
'#fdfce5',
|
||||||
|
'#f8f6d3',
|
||||||
|
'#f0ecaa',
|
||||||
|
'#e7e17c',
|
||||||
|
'#e0d957',
|
||||||
|
'#dbd33e',
|
||||||
|
'#d9d02f',
|
||||||
|
'#c0b820',
|
||||||
|
'#aaa316',
|
||||||
|
'#938c03'
|
||||||
|
];
|
||||||
|
export const theme = createTheme({
|
||||||
|
colors: {
|
||||||
|
myColor
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
xs: '30em',
|
||||||
|
sm: '45em',
|
||||||
|
md: '64em',
|
||||||
|
lg: '74em',
|
||||||
|
xl: '90em',
|
||||||
|
},
|
||||||
|
primaryShade: 5,
|
||||||
|
autoContrast: true,
|
||||||
|
defaultRadius: 'md',
|
||||||
|
primaryColor: 'myColor',
|
||||||
|
/* Put your mantine themeM override here */
|
||||||
|
});
|
||||||
|
|
||||||
11
src/react/vrTemplate.tsx
Normal file
11
src/react/vrTemplate.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {Container} from "@mantine/core";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function VrTemplate(props: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Container fluid={true}>
|
||||||
|
{props.children}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
import {PasswordDialog} from "./components/passwordDialog";
|
|
||||||
import {Menu} from "./components/menu";
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
import '@mantine/core/styles.css';
|
||||||
|
import React from "react";
|
||||||
|
import {RouterProvider} from "react-router-dom";
|
||||||
|
import {webRouter} from "./webRouter";
|
||||||
|
import {theme} from "./theme";
|
||||||
|
import {MantineProvider} from "@mantine/core";
|
||||||
|
|
||||||
export default function WebApp() {
|
export default function WebApp() {
|
||||||
document.addEventListener('promptpassword', () => {
|
document.addEventListener('promptpassword', () => {
|
||||||
@ -9,10 +13,9 @@ export default function WebApp() {
|
|||||||
(password as HTMLInputElement).style.display = 'block';
|
(password as HTMLInputElement).style.display = 'block';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return (
|
|
||||||
<div>
|
return (<MantineProvider defaultColorScheme="dark" theme={theme}>
|
||||||
<Menu/>
|
<RouterProvider router={webRouter}/>
|
||||||
<PasswordDialog/>
|
</MantineProvider>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/react/webRouter.tsx
Normal file
30
src/react/webRouter.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {createBrowserRouter} from "react-router-dom";
|
||||||
|
import About from "./marketing/about";
|
||||||
|
import Documentation from "./marketing/documentation";
|
||||||
|
import Examples from "./marketing/examples";
|
||||||
|
import Pricing from "./marketing/pricing";
|
||||||
|
import VrExperience from "./pages/vrExperience";
|
||||||
|
|
||||||
|
export const webRouter = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: (
|
||||||
|
<About/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/documentation",
|
||||||
|
element: (<Documentation/>)
|
||||||
|
}, {
|
||||||
|
path: "/examples",
|
||||||
|
element: (<Examples/>)
|
||||||
|
}, {
|
||||||
|
path: "/Pricing",
|
||||||
|
element: (<Pricing/>)
|
||||||
|
}, {
|
||||||
|
path: "/db/:db",
|
||||||
|
element: (<VrExperience/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
])
|
||||||
@ -30,27 +30,11 @@ export async function groundMeshObserver(ground: AbstractMesh,
|
|||||||
enablePointerSelectionOnAllControllers: true
|
enablePointerSelectionOnAllControllers: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
window.addEventListener('enterXr', async (e: CustomEvent) => {
|
||||||
|
await xr.baseExperience.enterXRAsync('immersive-vr', 'local-floor');
|
||||||
|
logger.debug("Entering XR Experience");
|
||||||
|
})
|
||||||
//xr.baseExperience.featuresManager.enableFeature(WebXRFeatureName.LAYERS, "latest", { preferMultiviewOnInit: true }, true, false);
|
//xr.baseExperience.featuresManager.enableFeature(WebXRFeatureName.LAYERS, "latest", { preferMultiviewOnInit: true }, true, false);
|
||||||
const enterButton = (document.querySelector('#enterXR') as HTMLAnchorElement);
|
|
||||||
if (enterButton) {
|
|
||||||
try {
|
|
||||||
const vrSupported = await xr.baseExperience.sessionManager.isSessionSupportedAsync('immersive-vr');
|
|
||||||
if (vrSupported) {
|
|
||||||
enterButton.classList.remove('inactive');
|
|
||||||
enterButton.addEventListener('click', async (evt) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
//const voice = new VoiceRecognizer();
|
|
||||||
logger.debug('entering XR');
|
|
||||||
|
|
||||||
const enter = await xr.baseExperience.enterXRAsync('immersive-vr', 'local-floor');
|
|
||||||
logger.debug(enter);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spinner) {
|
if (spinner) {
|
||||||
spinner.hide();
|
spinner.hide();
|
||||||
|
|||||||
21
src/vrApp.ts
21
src/vrApp.ts
@ -12,16 +12,16 @@ import {exportGltf} from "./util/functions/exportGltf";
|
|||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
import {Introduction} from "./tutorial/introduction";
|
import {Introduction} from "./tutorial/introduction";
|
||||||
|
|
||||||
|
|
||||||
const webGpu = false;
|
const webGpu = false;
|
||||||
|
|
||||||
log.setLevel('error', false);
|
log.setLevel('error', false);
|
||||||
const canvas = (document.querySelector('#gameCanvas') as HTMLCanvasElement);
|
export default class VrApp {
|
||||||
export class VrApp {
|
|
||||||
//preTasks = [havokModule];
|
//preTasks = [havokModule];
|
||||||
private logger: Logger = log.getLogger('App');
|
private logger: Logger = log.getLogger('App');
|
||||||
|
private _canvas: HTMLCanvasElement
|
||||||
|
|
||||||
constructor() {
|
constructor(canvas: HTMLCanvasElement) {
|
||||||
|
this._canvas = canvas;
|
||||||
this.initializeEngine().then(() => {
|
this.initializeEngine().then(() => {
|
||||||
this.logger.info('Engine initialized');
|
this.logger.info('Engine initialized');
|
||||||
});
|
});
|
||||||
@ -58,17 +58,18 @@ export class VrApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async initializeEngine() {
|
private async initializeEngine() {
|
||||||
|
if (!this._canvas) {
|
||||||
|
console.error('Canvas not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
let engine: WebGPUEngine | Engine = null;
|
let engine: WebGPUEngine | Engine = null;
|
||||||
if (webGpu) {
|
if (webGpu) {
|
||||||
engine = new WebGPUEngine(canvas);
|
engine = new WebGPUEngine(this._canvas);
|
||||||
await (engine as WebGPUEngine).initAsync();
|
await (engine as WebGPUEngine).initAsync();
|
||||||
} else {
|
} else {
|
||||||
engine = new Engine(canvas, true);
|
engine = new Engine(this._canvas, true);
|
||||||
}
|
}
|
||||||
engine.setHardwareScalingLevel(1 / window.devicePixelRatio);
|
engine.setHardwareScalingLevel(1 / window.devicePixelRatio);
|
||||||
window.onresize = () => {
|
|
||||||
engine.resize();
|
|
||||||
}
|
|
||||||
const scene = new Scene(engine);
|
const scene = new Scene(engine);
|
||||||
DefaultScene.Scene = scene;
|
DefaultScene.Scene = scene;
|
||||||
scene.ambientColor = new Color3(.1, .1, .1);
|
scene.ambientColor = new Color3(.1, .1, .1);
|
||||||
@ -78,9 +79,7 @@ export class VrApp {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const vrApp = new VrApp();
|
|
||||||
buildQuestLink();
|
buildQuestLink();
|
||||||
|
|
||||||
function setMainCamera(scene: Scene) {
|
function setMainCamera(scene: Scene) {
|
||||||
const CAMERA_NAME = 'Main Camera';
|
const CAMERA_NAME = 'Main Camera';
|
||||||
const camera: FreeCamera = new FreeCamera(CAMERA_NAME,
|
const camera: FreeCamera = new FreeCamera(CAMERA_NAME,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {createRoot} from "react-dom/client";
|
import * as ReactDOM from "react-dom/client";
|
||||||
import WebApp from "./react/webApp";
|
import WebApp from "./react/webApp";
|
||||||
|
|
||||||
const root = createRoot(document.getElementById('webApp'));
|
ReactDOM.createRoot(document.getElementById('webApp')).render(WebApp());
|
||||||
root.render(WebApp());
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user