// handy shortcuts if( !window.$ ) window.$ = (s) => s ? document.querySelector(s) : false if( !window.$$ ) window.$$ = (s) => s ? [ ...document.querySelectorAll(s) ] : false window.XRFMENU = { logo: './../../assets/logo.png', html: [ `🧑‍🤝‍🧑 meeting
`, `🔗 share
` ], 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(); } }, setupMenu(XRF){ let aScene = document.querySelector('a-scene') let urlbar = $('input#uri') let inIframe = window.location !== window.parent.location let els = [ ...document.querySelectorAll('.menu .btn') ] els = els.filter( (el) => el.id != "more" ? el : false ) let showMenu = (state) => { els.map( (el) => el.style.display = state ? 'inline-block' : 'none' ) $('a#more').style.display = state ? 'none' : 'inline-block' $('#overlay').style.display = state ? 'inline-block' : 'none' if( inIframe ) $('#uri').style.display = 'block' } els.map( (el) => el.addEventListener('click', () => showMenu(false) ) ) $('a#more').addEventListener('click', () => showMenu(true) ) $('.a-canvas').addEventListener('click', () => showMenu(false) ) // enable meetings let startMeeting = () => { aScene.setAttribute('meeting', 'id: xrfragments') $('a#meeting').innerText = '🧑‍🤝‍🧑 breakout meeting' $('a#meeting').setAttribute('aria-description','breakout room') } $('a#meeting').addEventListener('click', () => { if( aScene.getAttribute('meeting') ){ // meeting already, start breakout room let parentRoom = document.location.href XRFMENU.updateHashPosition(true) let meeting = $('[meeting]').components['meeting'] meeting.data.parentRoom = parentRoom meeting.update() }else startMeeting() }) if( document.location.hash.match(/(#|&)meet/) ) startMeeting() XRF.addEventListener('hash', () => reflectUrl() ) const reflectUrl = window.reflectUrl = (url) => { urlbar.value = url || document.location.search.substr(1) + document.location.hash } reflectUrl() }, SnackBar(userOptions) { var snackbar = this || (window.snackbar = {}); var _Interval; var _Message; var _Element; var _Container; var _OptionDefaults = { message: "Operation performed successfully.", dismissible: true, timeout: 7000, status: "" } var _Options = _OptionDefaults; function _Create() { _Container = document.querySelector(".js-snackbar-container") if( _Container ){ _Container.remove() } _Container = null if (!_Container) { // need to create a new container for notifications _Container = document.createElement("div"); _Container.classList.add("js-snackbar-container"); document.body.appendChild(_Container); } _Container.opts = _Options _Container.innerHTML = '' _Element = document.createElement("div"); _Element.classList.add("js-snackbar__wrapper","xrf"); let innerSnack = document.createElement("div"); innerSnack.classList.add("js-snackbar", "js-snackbar--show"); if (_Options.status) { _Options.status = _Options.status.toLowerCase().trim(); let status = document.createElement("span"); status.classList.add("js-snackbar__status"); if (_Options.status === "success" || _Options.status === "green") { status.classList.add("js-snackbar--success"); } else if (_Options.status === "warning" || _Options.status === "alert" || _Options.status === "orange") { status.classList.add("js-snackbar--warning"); } else if (_Options.status === "danger" || _Options.status === "error" || _Options.status === "red") { status.classList.add("js-snackbar--danger"); } else { status.classList.add("js-snackbar--info"); } innerSnack.appendChild(status); } _Message = document.createElement("span"); _Message.classList.add("js-snackbar__message"); if( typeof _Options.message == 'string' ){ _Message.innerHTML = _Options.message; }else _Message.appendChild(_Options.message) innerSnack.appendChild(_Message); if (_Options.dismissible) { let closeBtn = document.createElement("span"); closeBtn.classList.add("js-snackbar__close"); closeBtn.innerText = "\u00D7"; closeBtn.onclick = snackbar.Close; innerSnack.appendChild(closeBtn); } _Element.style.height = "0px"; _Element.style.opacity = "0"; _Element.style.marginTop = "0px"; _Element.style.marginBottom = "0px"; _Element.appendChild(innerSnack); _Container.appendChild(_Element); if (_Options.timeout !== false) { _Interval = setTimeout(snackbar.Close, _Options.timeout); } } snackbar.Open = function() { let contentHeight = _Element.firstElementChild.scrollHeight; // get the height of the content _Element.style.height = contentHeight + "px"; _Element.style.opacity = 1; _Element.style.marginTop = "5px"; _Element.style.marginBottom = "5px"; _Element.addEventListener("transitioned", function() { _Element.removeEventListener("transitioned", arguments.callee); _Element.style.height = null; }) } snackbar.Close = function () { if (_Interval) clearInterval(_Interval); let snackbarHeight = _Element.scrollHeight; // get the auto height as a px value let snackbarTransitions = _Element.style.transition; _Element.style.transition = ""; requestAnimationFrame(function() { _Element.style.height = snackbarHeight + "px"; // set the auto height to the px height _Element.style.opacity = 1; _Element.style.marginTop = "0px"; _Element.style.marginBottom = "0px"; _Element.style.transition = snackbarTransitions requestAnimationFrame(function() { _Element.style.height = "0px"; _Element.style.opacity = 0; }) }); setTimeout(function() { try { _Container.removeChild(_Element); } catch (e) { } }, 1000); }; _Options = { ..._OptionDefaults, ...userOptions } _Create(); snackbar.Open(); }, notify(scope){ return function notify(str,opts){ opts = opts || {status:'info'} opts = Object.assign({ status, timeout:4000 },opts) if( typeof str == 'string' ){ if( !opts.status ){ if( str.match(/error/g) ) opts.status = "danger" if( str.match(/warning/g) ) opts.status = "warning" } } opts.message = str window.XRFMENU.SnackBar( 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(/#&/,'') XRFMENU.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(){ let inMeeting = $('[meeting]') let url = window.location.href if( !inMeeting ) XRFMENU.updateHashPosition() else url = $('[meeting]').components['meeting'].data.link XRFMENU.copyToClipboard( url ) // End of *TODO* window.notify(`

${ inMeeting ? '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:2000000}) // draw QR code setTimeout( () => { let QR = window.QR QR.canvas = document.getElementById('qrcode') QR.draw( url, QR.canvas ) },0) } } window.XRFMENU.addHTML = () => { let el = document.createElement("div") el.innerHTML += ` ` document.body.appendChild(el) if( XRFMENU.logo ) $('.logo').style['background-image'] = `url(${XRFMENU.logo})` window.notify = XRFMENU.notify(window) window.share = XRFMENU.share window.download = XRFMENU.download window.notify('loading '+document.location.search.substr(1)) // reroute console messages to snackbar notifications console.log = ( (log) => function(str){ if( String(str).match(/(:.*#|note:)/) ) window.notify(str) log(str) })(console.log) // allow iframe to open url window.addEventListener('message', (event) => { if (event.data && event.data.url) { window.open(event.data.url, '_blank'); } }); }