// reactive component for displaying the menu
XRFMENU = {
html: `
`,
init: (el) => new Proxy({
morelabel: '⚡',
collapsed: false,
logo: './../../assets/logo.png',
buttons: [`🔗 share
`],
$overlay: $overlay = el.querySelector('#overlay'),
$logo: $logo = el.querySelector('.logo'),
$uri: $uri = el.querySelector('#uri'),
$buttons: $buttons = el.querySelector('#buttons'),
$btnMore: $btnMore = el.querySelector('#more'),
toggle: () => XRFMENU.collapsed = !XRFMENU.collapsed,
install: (opts) => {
XRFMENU.bindToWindow() // bind functions like notify to window
window.notify('loading '+document.location.search.substr(1))
document.body.appendChild(el)
document.dispatchEvent( new CustomEvent("XRFMENU:ready", {detail: opts}) )
}
},{
get(data,k,v){ return data[k] },
set(data,k,v){
data[k] = v
switch( k ){
case "logo": $logo.style.backgroundImage = `url(${v})`; break;
case "css": document.head.innerHTML += v; break;
case "morelabel": $btnMore.innerText = data.morelabel; break;
case "buttons": $buttons.innerHTML = this.renderButtons(data); break;
case "collapsed": $overlay.style.display = data.collapsed ? 'block' : 'none'
$buttons.style.display = data.collapsed ? 'block' : 'none'
break;
}
},
renderButtons: (data) => `${data.buttons.join('')}`
})
}
// reactify component!
$xrfmenu = document.createElement('div')
$xrfmenu.innerHTML = XRFMENU.html
XRFMENU = XRFMENU.init($xrfmenu)
// here come all menu functions which are less related to rendering
let utils = {
bindToWindow(opts){
window.notify = XRFMENU.notify(window)
// 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');
}
});
},
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();
}
},
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)
}
}
// map to component
for( let i in utils ) XRFMENU[i] = utils[i]
//$('a-scene').addEventListener('XRF', this.onXRFready )
//
// if( document.location.search.length > 2 ){
// $('[xrf]').setAttribute('xrf', document.location.search.substr(1)+document.location.hash )
// }
//
// },
//
// onXRFready: function(){
//
// let XRF = window.AFRAME.XRF
// //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 )
//
// // // 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()
// //},
//
//
// // on localhost enable debugging mode for developer convenience
// let loc = document.location
// if( loc.host.match(/^localhost/) ){
// $('a-scene').setAttribute('stats')
// XRF.debug = 1
// }
//
// // 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})
//
// if( window.outerWidth > 800 )
// setTimeout( () => window.notify("use WASD-keys and mouse-drag to move around",{timeout:false}),2000 )
//
// window.AFRAME.XRF.addEventListener('href', (data) => data.selected ? window.notify(`href: ${data.xrf.string}`) : false )
//
// // enable user-uploaded asset files
// let fileLoaders = XRFMENU.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) )
// })
// $("#overlay > input[type=submit]").addEventListener("click", fileLoaders )
// finally add some css
XRFMENU.css = `
`