wip: feat/remotestorage

This commit is contained in:
Leon van Kammen 2025-05-14 09:48:33 +02:00
parent 52f79a44c5
commit 96e11f3594
10 changed files with 147 additions and 47 deletions

View file

@ -20,7 +20,7 @@
renderer="colorManagement: false; stencil: true; antialias:true; highRefreshRate:true; foveationLevel: 0.5; toneMapping: ACESFilmic; exposure: 3.0" renderer="colorManagement: false; stencil: true; antialias:true; highRefreshRate:true; foveationLevel: 0.5; toneMapping: ACESFilmic; exposure: 3.0"
device-orientation-permission-ui xrf-gaze-always joystick device-orientation-permission-ui xrf-gaze-always joystick
light="defaultLightsEnabled: false"> light="defaultLightsEnabled: false">
<a-entity id="player" movement-controls touch-controls="axis:y" wasd-controls="fly:false" look-controls="magicWindowTrackingEnabled:true"> <a-entity id="player" movement-controls touch-controls="axis:y" wasd-controls="fly:true" look-controls="magicWindowTrackingEnabled:true">
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity> <a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
<a-entity id="left-hand" hand-tracking-grab-controls="hand:left;modelColor:#cccccc" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor"> <a-entity id="left-hand" hand-tracking-grab-controls="hand:left;modelColor:#cccccc" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
<a-entity rotation="-35 0 0" position="0 0.1 0" id="navigator"> <a-entity rotation="-35 0 0" position="0 0.1 0" id="navigator">

2
make
View file

@ -73,7 +73,7 @@ build(){
aframe(){ aframe(){
test -d src/3rd/js/aframe/build/aframe || git clone https://github.com/aframevr/aframe src/3rd/js/aframe/build/aframe --depth=1 test -d src/3rd/js/aframe/build/aframe || git clone https://github.com/aframevr/aframe src/3rd/js/aframe/build/aframe --depth=1
curdir=$(pwd) curdir=$(pwd)
cd src/3rd/js/aframe/build && cp three.module.js aframe/src/lib/. # override to add extra loaders like fbx/collada e.g. cd src/3rd/js/aframe/build && cp three*.js aframe/src/lib/. # override to add extra loaders like fbx/collada e.g.
#cd aframe && npm install && npm install troika-three-text && npm run dist #cd aframe && npm install && npm install troika-three-text && npm run dist
cd aframe && npm install && npm run dist cd aframe && npm install && npm run dist
cd "$curdir" cd "$curdir"

View file

@ -0,0 +1,36 @@
import * as SUPER_THREE from 'super-three';
import { DRACOLoader } from 'super-three/examples/jsm/loaders/DRACOLoader';
import { GLTFLoader } from 'super-three/examples/jsm/loaders/GLTFLoader';
//import { KTX2Loader } from 'super-three/examples/jsm/loaders/KTX2Loader';
import { OBB } from 'super-three/addons/math/OBB.js';
import { OBJLoader } from 'super-three/examples/jsm/loaders/OBJLoader';
import { FBXLoader } from 'super-three/examples/jsm/loaders/FBXLoader';
import { USDZLoader } from 'super-three/examples/jsm/loaders/USDZLoader';
import { ColladaLoader } from 'super-three/examples/jsm/loaders/ColladaLoader';
import { MTLLoader } from 'super-three/examples/jsm/loaders/MTLLoader';
import * as BufferGeometryUtils from 'super-three/examples/jsm/utils/BufferGeometryUtils';
import { LightProbeGenerator } from 'super-three/examples/jsm/lights/LightProbeGenerator';
import { TransformControls } from 'super-three/examples/jsm/controls/TransformControls.js';
import { GLTFExporter } from 'super-three/examples/jsm/exporters/GLTFExporter.js';
var THREE = globalThis.THREE = {...SUPER_THREE};
// TODO: Eventually include these only if they are needed by a component.
require('../../vendor/DeviceOrientationControls'); // THREE.DeviceOrientationControls
THREE.DRACOLoader = DRACOLoader;
THREE.GLTFLoader = GLTFLoader;
THREE.KTX2Loader = KTX2Loader;
THREE.OBJLoader = OBJLoader;
THREE.MTLLoader = MTLLoader;
THREE.FBXLoader = FBXLoader;
THREE.USDZLoader = USDZLoader;
THREE.ColladaLoader = ColladaLoader;
THREE.OBB = OBB;
THREE.BufferGeometryUtils = BufferGeometryUtils;
THREE.LightProbeGenerator = LightProbeGenerator;
THREE.TransformControls = TransformControls;
THREE.GLTFExporter = GLTFExporter;
THREE.Cache.enabled = true;
export default THREE;

View file

@ -29,7 +29,7 @@ THREE.OBB = OBB;
THREE.BufferGeometryUtils = BufferGeometryUtils; THREE.BufferGeometryUtils = BufferGeometryUtils;
THREE.LightProbeGenerator = LightProbeGenerator; THREE.LightProbeGenerator = LightProbeGenerator;
THREE.TransformControls = TransformControls; THREE.TransformControls = TransformControls;
THREE.GLTFExporter = GLTFExporter; THREE.GLTFExporter = GLTFExporter || console.error("GLTFExporter not found");
//THREE.Text = Text //THREE.Text = Text
export default THREE; export default THREE;

View file

@ -4,19 +4,23 @@ filesComponent = (el) => new Proxy({
html: (data) => ` html: (data) => `
<style type="text/css"> <style type="text/css">
#messages .msg.ui, #messages .msg.ui #files div { #messages .msg.ui #files div {
border:none; border:none;
padding:0; padding:0;
border-radius:0;
margin:0; margin:0;
box-shadow:none; box-shadow:none;
} }
.msg.ui #files{
min-width:415px;
}
</style> </style>
<div class="ui envelope"> <div class="ui envelope">
<div class="msg ui"> <div class="msg ui">
<div> <div>
<div id="files"> <div id="files">
<i class="gg-close-o" id="close" onclick="$files.visible = false"></i> <i class="gg-close-o" id="close" onclick="$files.remove()"></i>
<br> <br>
<div class="tab-frame"> <div class="tab-frame">
${data.tabs.map( (t) => ${data.tabs.map( (t) =>
@ -25,11 +29,12 @@ filesComponent = (el) => new Proxy({
` `
).join('') ).join('')
} }
<br><br> <br><br><br>
<div class="tab"> <div class="tab">
<div id="localFilesTab"> <div id="localFilesTab">
<button id="uploadFile" ><i class="gg-software-upload"></i> upload</button> <button id="localOpen" onclick="$files.fileLoaders()" ><i class="gg-software-upload"></i> open experience</button>
<button id="downloadfile"><i class="gg-software-download"></i> download</button> <br>
<button id="localSave" onclick="frontend.download()"><i class="gg-software-download"></i> save current</button>
</div> </div>
</div> </div>
</div> </div>
@ -40,7 +45,7 @@ filesComponent = (el) => new Proxy({
`, `,
tabs: [ tabs: [
{name: "local files", id: "localFiles"} {name: "offline", id: "localFiles"}
], ],
show: false, show: false,
@ -50,6 +55,10 @@ filesComponent = (el) => new Proxy({
this.show = state !== undefined ? state : !this.show this.show = state !== undefined ? state : !this.show
}, },
remove(){
el.parent.remove(el)
},
init(opts){ init(opts){
this.decorateFileButton() this.decorateFileButton()
// create HTML element // create HTML element
@ -72,7 +81,7 @@ filesComponent = (el) => new Proxy({
document.querySelector("#load").setAttribute("value","3D file") document.querySelector("#load").setAttribute("value","3D file")
// decorate fileLoaders // decorate fileLoaders
window.frontend.fileLoaders = ( (fileLoaders) => { window.frontend.fileLoaders = ( (fileLoaders) => {
this.fileLoader = fileLoaders this.fileLoaders = fileLoaders
return () => this.toggle() return () => this.toggle()
})( window.frontend.fileLoaders ) })( window.frontend.fileLoaders )
}, },

View file

@ -889,6 +889,38 @@ document.head.innerHTML += `
bottom: 3px; bottom: 3px;
} }
.gg-globe-alt,
.gg-globe-alt::after,
.gg-globe-alt::before {
display: inline-block;
box-sizing: border-box;
height: 18px;
border: 2px solid;
}
.gg-globe-alt {
position: relative;
transform: scale(var(--ggs, 1));
width: 18px;
border-radius: 22px;
}
.gg-globe-alt::after,
.gg-globe-alt::before {
content: "";
position: absolute;
width: 8px;
border-radius: 100%;
top: -2px;
left: 3px;
}
.gg-globe-alt::after {
width: 24px;
height: 20px;
border: 2px solid transparent;
border-bottom: 2px solid;
top: -11px;
left: -5px;
}
</style> </style>
` `

View file

@ -252,23 +252,26 @@ window.frontend = (opts) => new Proxy({
download(){ download(){
function download(dataurl, filename) {
var a = document.createElement("a");
a.href = URL.createObjectURL( new Blob([dataurl]) );
a.setAttribute("download", filename);
a.click();
return false;
}
function exportScene(model,ext,file){ function exportScene(model,ext,file){
document.dispatchEvent( new CustomEvent('frontend.export',{detail:{ scene: model.scene,ext}}) ) document.dispatchEvent( new CustomEvent('frontend.export',{detail:{ scene: model.scene,ext}}) )
xrf.emit('export', {scene: model.scene, ext}) xrf.emit('export', {scene: model.scene, ext})
.then( () => { .then( () => {
function download(dataurl, filename) {
var a = document.createElement("a");
a.href = URL.createObjectURL( new Blob([dataurl]) );
a.setAttribute("download", filename);
a.click();
return false;
}
// setup exporters // setup exporters
let defaultExporter = THREE.GLTFExporter let defaultExporter = THREE.GLTFExporter
if( !xrf.loaders['gltf'].exporter ) xrf.loaders['gltf'].exporter = defaultExporter if( !xrf.loaders['gltf'].exporter ) xrf.loaders['gltf'].exporter = defaultExporter
if( !xrf.loaders['glb'].exporter ) xrf.loaders['glb'].exporter = defaultExporter if( !xrf.loaders['glb'].exporter ) xrf.loaders['glb'].exporter = defaultExporter
const exporter = new xrf.loaders[ext]() const exporter = new xrf.loaders[ext].exporter()
debugger
exporter.parse( exporter.parse(
model.scene, model.scene,
function ( glb ) { download(glb, `${file}`) }, // ready function ( glb ) { download(glb, `${file}`) }, // ready
@ -287,13 +290,12 @@ window.frontend = (opts) => new Proxy({
// load original scene and overwrite with updates // load original scene and overwrite with updates
let url = document.location.search.replace(/\?/,'') let url = document.location.search.replace(/\?/,'')
let {urlObj,dir,file,hash,fileExt} = xrf.navigator.origin = xrf.URI.parse(url) let {urlObj,dir,file,hash,fileExt} = xrf.navigator.origin = xrf.URI.parse(url)
debugger
const Loader = xrf.loaders[fileExt] const Loader = xrf.loaders[fileExt]
loader = new Loader().setPath( dir ) loader = new Loader().setPath( dir )
notify('exporting scene<br><br>please wait..') notify('exporting scene<br><br>please wait..')
loader.load(url, (model) => { loader.load(url, (model) => {
exportScene(model,fileExt,file) exportScene(model,fileExt,file)
}) }, console.error )
}, },
updateHashPosition(randomize){ updateHashPosition(randomize){

View file

@ -0,0 +1,7 @@
const WebXRF = { name: 'webxr', builder: function(privateClient, publicClient) {
return {
exports: {
addScene: function() {}
}
}
}};

View file

@ -3,33 +3,39 @@
// reactive component for displaying the menu // reactive component for displaying the menu
remoteStorageComponent = (el) => new Proxy({ remoteStorageComponent = (el) => new Proxy({
html: ` html: (data) => (`
<style type="text/css"> <style type="text/css">
body div#remotestorage-widget .rs-button{ body #files .rs-button-big{
background: #AAA; background: #FFF;
box-shadow: none;
border: 1px solid #CCC;
padding: 10px 0px 49px 10px;
}
#files .rs-button{
background:#CCC;
} }
</style> </style>
<div id="remoteFilesTab"> <div id="remoteFilesTab">
<div id="rswidget"></div> <div id="rswidget"></div>
<br><br> <br>
<button id="uploadFile" ><i class="gg-software-upload"></i> upload</button> <div id="buttons" style="display:none">
<button id="downloadfile"><i class="gg-software-download"></i> download</button> <button id="remoteOpen" ><i class="gg-software-upload"></i> open experience</button>
<br>
<button id="remoteSave"><i class="gg-software-download"></i> save private experience</button>
<br>
<button id="remoteSave"><i class="gg-globe-alt"></i> save public experience</button>
</div>
</div> </div>
`, `),
show: false, connected: false,
toggle(state){
this.show = state !== undefined ? state : !this.show
},
init(opts){ init(opts){
// create HTML element // create HTML element
$files.tabs = $files.tabs.concat({id:"remoteFiles", name: "remote files"}) $files.tabs = $files.tabs.concat({id:"remoteFiles", name: "online"})
el.innerHTML = this.html el.innerHTML = this.html(this)
el.className = "tab" el.className = "tab"
this.toggle(this.show) // trigger visibility
document.querySelector("#files .tab-frame").appendChild(el); document.querySelector("#files .tab-frame").appendChild(el);
// setup input listeners // setup input listeners
@ -61,32 +67,36 @@ remoteStorageComponent = (el) => new Proxy({
let apis = { let apis = {
dropbox: "4jc8nx1lbarp472" dropbox: "4jc8nx1lbarp472"
} }
window.remoteStorage = new RemoteStorage({logging: true }) const modules = []
if( typeof WebXRF != undefined ){
modules.push(WebXRF) // defined in remotestorage-module-webXRF.js
}
window.remoteStorage = new RemoteStorage({logging: true, modules })
if( Object.keys(apis).length ) remoteStorage.setApiKeys(apis) if( Object.keys(apis).length ) remoteStorage.setApiKeys(apis)
remoteStorage.on('connected', (e) => { console.log("connected") } ) remoteStorage.on('connected', (e) => { this.connected = true })
//remoteStorage.on('network-offline', (e) => this.el.sceneEl.emit('remoteStorage.network-offline',e) ) //remoteStorage.on('network-offline', (e) => this.el.sceneEl.emit('remoteStorage.network-offline',e) )
//remoteStorage.on('network-online', (e) => this.el.sceneEl.emit('remoteStorage.network-online',e) ) //remoteStorage.on('network-online', (e) => this.el.sceneEl.emit('remoteStorage.network-online',e) )
//remoteStorage.on('error', (e) => this.el.sceneEl.emit('remoteStorage.error',e) ) //remoteStorage.on('error', (e) => this.el.sceneEl.emit('remoteStorage.error',e) )
//remoteStorage.on('ready', (e) => { } ) remoteStorage.on('ready', (e) => { } )
remoteStorage.access.claim( `webxr`, 'rw'); // our data dir remoteStorage.access.claim( `webxr`, 'rw'); // our data dir
remoteStorage.caching.enable( `/webxr/` ) // local-first, remotestorage-second remoteStorage.caching.enable( `/webxr/` ) // local-first, remotestorage-second
remoteStorage.caching.enable( `/public/webxr/` ) // local-first, remotestorage-second
// create widget // create widget
let opts = {} let opts = {}
opts.modalBackdrop = false opts.modalBackdrop = false
opts.leaveOpen = true
widget = new window.Widget(window.remoteStorage, opts) widget = new window.Widget(window.remoteStorage, opts)
widget.attach( "rswidget" ); widget.attach( "rswidget" );
}, },
click(id,e){ click(id,e){
switch(id){ //switch(id){
case "icon": // case "more": return this.toggle(); break;
case "more": return this.toggle(); break; //}
}
this.toggle(false)
} }
}, },
{ {
@ -95,6 +105,9 @@ remoteStorageComponent = (el) => new Proxy({
set(me,k,v){ set(me,k,v){
me[k] = v me[k] = v
switch( k ){
case 'connected': el.querySelector("#buttons").style.display = v ? 'block' : 'none'; break;
}
}, },
}) })

View file

@ -63,7 +63,6 @@ xrf.navigator.to = (url,flags,loader,data) => {
evalFragment() evalFragment()
return resolve(xrf.model) // eval non-positional fragments (no loader needed) return resolve(xrf.model) // eval non-positional fragments (no loader needed)
} }
xrf xrf
.emit('navigateLoading', {url,loader,data}) .emit('navigateLoading', {url,loader,data})
.then( () => { .then( () => {
@ -109,7 +108,7 @@ xrf.navigator.init = () => {
xrf.navigator.URI = xrfragment.URI.parse(document.location.href) xrf.navigator.URI = xrfragment.URI.parse(document.location.href)
window.addEventListener('popstate', function (event){ window.addEventListener('popstate', function (event){
if( xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion) if( xrf.navigator.updateHash.active && document.location.hash.length > 1 ){ // ignore programmatic hash updates (causes infinite recursion)
xrf.navigator.to( xrf.navigator.URI.last ) xrf.navigator.to( xrf.navigator.URI.last )
} }
}) })
@ -119,7 +118,9 @@ xrf.navigator.init = () => {
}) })
// allow other libraries to trigger popstate event without triggering the navigate-fallbacks during pageload // allow other libraries to trigger popstate event without triggering the navigate-fallbacks during pageload
setTimeout( xrf.navigator.setupNavigateFallbacks(), 1500 ) setTimeout( () => {
xrf.navigator.setupNavigateFallbacks()
}, 2500 )
// this allows selectionlines to be updated according to the camera (renderloop) // this allows selectionlines to be updated according to the camera (renderloop)
xrf.focusLine = new xrf.THREE.Group() xrf.focusLine = new xrf.THREE.Group()