// this has some overlap with $menu.js // frontend serves as a basis for shared functions (download, share e.g.) window.frontend = (opts) => new Proxy({ html: `
`, el: null, plugin: {}, xrf, // this SUPER-emit forwards custom events to all objects supporting dispatchEvent // perfect to broadcast events simultaniously to document + 3D scene emit(k,v){ v = v || {event:k} for( let i in opts ){ if( opts[i].dispatchEvent ){ if( opts.debug ) console.log(`${i}.emit(${k},{...})`) opts[i].dispatchEvent( new CustomEvent(k,{detail:v}) ) } } }, init(){ // setup element and delegate events this.el = document.createElement("div") this.el.innerHTML = this.html document.body.appendChild(this.el); (['click']).map( (e) => this.el.addEventListener(e, (ev) => this[e] && this[e](ev.target.id,ev) ) ) this .setupFileLoaders() .setupIframeUrlHandler() .setupCapture() .setupUserHints() .hidetopbarWhenMenuCollapse() window.notify = this.notify setTimeout( () => { document.dispatchEvent( new CustomEvent("frontend:ready", {detail:opts} ) ) },1) return this }, click(id,ev){ switch( id ){ case "load": this.fileLoaders() } }, setupFileLoaders(){ // enable user-uploaded asset files (activated by load button) this.fileLoaders = this.loadFile({ ".gltf": (file) => file.arrayBuffer().then( (data) => xrf.navigator.to(file.name,null, (new xrf.loaders.gltf()), data) ), ".glb": (file) => file.arrayBuffer().then( (data) => xrf.navigator.to(file.name,null, (new xrf.loaders.gltf()), data) ) }) return this }, setupIframeUrlHandler(){ // allow iframe to open url window.addEventListener('message', (event) => { if (event.data && event.data.url) { window.open(event.data.url, '_blank'); } }); return this }, setupCapture(){ // add screenshot component with camera to capture bigger size equirects // document.querySelector('a-scene').components.screenshot.capture('perspective') $('a-scene').setAttribute("screenshot",{camera: "[camera]",width: 4096*2, height:2048*2}) return this }, setupUserHints(){ // notify navigation + href mouseovers to user setTimeout( () => { window.notify('loading '+document.location.search.substr(1)) setTimeout( () => window.notify("use WASD-keys and mouse-drag to move around",{timeout:false}),2000 ) setTimeout( () => xrf.addEventListener('href', (data) => data.selected ? window.notify(`href: ${data.xrf.string}`) : false ), 5000) },100) return this }, hidetopbarWhenMenuCollapse(){ // hide topbar when menu collapse button is pressed document.addEventListener('$menu:collapse', (e) => this.el.querySelector("#topbar").style.display = e.detail === true ? 'block' : 'none') return this }, loadFile(contentLoaders, multiple){ return () => { window.notify("if you're on Meta browser, file-uploads might be disabled") let input = document.createElement('input'); input.type = 'file'; input.multiple = multiple; input.accept = Object.keys(contentLoaders).join(","); input.onchange = () => { let files = Array.from(input.files); let file = files.slice ? files[0] : files for( var i in contentLoaders ){ let r = new RegExp('\\'+i+'$') if( file.name.match(r) ) return contentLoaders[i](file) } alert(file.name+" is not supported") }; input.click(); } }, notify(_str,opts){ if( window.outerWidth < 800 ) return if( window.accessibility && window.accessibility.enabled ) return $chat.send({message:_str,class:['info']}) opts = opts || {status:'info'} opts = Object.assign({ status, timeout:4000 },opts) opts.message = _str if( typeof str == 'string' ){ str = _str.replace(/(^\w+):/,"
\$1
") if( !opts.status ){ if( str.match(/error/g) ) opts.status = "danger" if( str.match(/warning/g) ) opts.status = "warning" } opts.message = str } window.SnackBar( opts ) opts.message = typeof _str == 'string' ? _str : _str.innerText window.frontend.emit("notify",opts) }, download(){ function fetchAndDownload(dataurl, filename) { var a = document.createElement("a"); a.href = dataurl; a.setAttribute("download", filename); a.click(); return false; } let file = document.location.search.replace(/\?/,'') fetchAndDownload( file, file ) }, updateHashPosition(randomize){ // *TODO* this should be part of the XRF Threejs framework if( typeof THREE == 'undefined' ) THREE = xrf.THREE let radToDeg = THREE.MathUtils.radToDeg let toDeg = (x) => x / (Math.PI / 180) let camera = document.querySelector('[camera]').object3D.parent // *TODO* fix for threejs camera.position.x += Math.random()/10 camera.position.z += Math.random()/10 // *TODO* add camera direction let direction = new xrf.THREE.Vector3() camera.getWorldDirection(direction) const pitch = Math.asin(direction.y); const yaw = Math.atan2(direction.x, direction.z); const pitchInDegrees = pitch * 180 / Math.PI; const yawInDegrees = yaw * 180 / Math.PI; let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}` let newHash = document.location.hash.replace(/[&]?(pos|rot)=[0-9\.-]+,[0-9\.-]+,[0-9\.-]+/,'') newHash += `&${lastPos}` document.location.hash = newHash.replace(/&&/,'&') .replace(/#&/,'') this.copyToClipboard( window.location.href ); }, copyToClipboard(text){ // copy url to clipboard var dummy = document.createElement('input') document.body.appendChild(dummy); dummy.value = text; dummy.select(); document.execCommand('copy'); document.body.removeChild(dummy); }, share(opts){ opts = opts || {notify:true,qr:true,share:true} if( network.connected && !document.location.hash.match(/meet=/) ){ let p = $connections.chatnetwork.find( (p) => p.plugin.name == $connections.selectedChatnetwork ) if( p.link ) document.location.hash += `&meet=${p.link}` } let url = window.location.href this.copyToClipboard( url ) // End of *TODO* if( opts.notify ){ window.notify(`

${ network.connected ? 'Meeting link ' : 'Link'} copied to clipboard!

Now share it with your friends ❤️



   clone & selfhost this experience
To embed this experience in your blog,
copy/paste the following into your HTML:


`,{timeout:false}) } // draw QR code if( opts.qr ){ setTimeout( () => { let QR = window.QR QR.canvas = document.getElementById('qrcode') QR.draw( url, QR.canvas ) },1) } // mobile share if( opts.share && typeof navigator.share != 'undefined'){ navigator.share({ url, title: 'your meeting link' }) } $menu.collapse = true } }, { // auto-trigger events on changes get(me,k,receiver){ return me[k] }, set(me,k,v){ let from = me[k] me[k] = v switch( k ){ case "logo": $logo.style.backgroundImage = `url(${v})`; break; default: me.emit(`me.${k}.change`, {from,to:v}); break; } } }) frontend = frontend({xrf,document}).init()