wip + build
This commit is contained in:
parent
ec8d4fd497
commit
3c8841f181
8 changed files with 232731 additions and 894 deletions
1268
dist/xrfragment.aframe.all.js
vendored
1268
dist/xrfragment.aframe.all.js
vendored
File diff suppressed because it is too large
Load diff
1268
dist/xrfragment.aframe.js
vendored
1268
dist/xrfragment.aframe.js
vendored
File diff suppressed because it is too large
Load diff
229356
dist/xrfragment.module.js
vendored
229356
dist/xrfragment.module.js
vendored
File diff suppressed because it is too large
Load diff
702
dist/xrfragment.three.js
vendored
702
dist/xrfragment.three.js
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Wed Dec 13 07:23:32 PM CET 2023
|
||||
* v0.5.1 generated at Fri Dec 15 04:23:00 PM CET 2023
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
|
@ -602,6 +602,706 @@ xrf.hasTag = (tag,tags) => String(tags).match( new RegExp(`(^| )${tag}( |$)`,`g`
|
|||
|
||||
// map library functions to xrf
|
||||
for ( let i in xrfragment ) xrf[i] = xrfragment[i]
|
||||
// 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: [
|
||||
`<a class="btn" aria-label="button" aria-description="start text/audio/video chat" id="meeting" target="_blank">🧑🤝🧑 meeting</a><br>`,
|
||||
`<a class="btn" aria-label="button" aria-description="share URL/screenshot/embed" id="share" target="_blank" onclick="window.share()">🔗 share</a><br>`
|
||||
],
|
||||
|
||||
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 = '📍 new meeting location'
|
||||
}
|
||||
$('a#meeting').addEventListener('click', () => {
|
||||
if( aScene.getAttribute('meeting') ){ // meeting already, start breakout room
|
||||
let parentRoom = document.location.href
|
||||
XRFMENU.updateHashPosition(true)
|
||||
let visitorname = aScene.getAttribute("meeting").visitorname
|
||||
aScene.removeAttribute('meeting')
|
||||
// breakoutroom
|
||||
aScene.setAttribute('meeting', `id: xrfragments; visitorname: ${visitorname}; parentRoom: ${parentRoom}`)
|
||||
}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(/#&/,'')
|
||||
// copy url to clipboard
|
||||
var dummy = document.createElement('input'),
|
||||
text = window.location.href;
|
||||
document.body.appendChild(dummy);
|
||||
dummy.value = text;
|
||||
dummy.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(dummy);
|
||||
},
|
||||
|
||||
share(){
|
||||
XRFMENU.updateHashPosition()
|
||||
// End of *TODO*
|
||||
window.notify(`<h2>Link copied to clipboard!</h2> <br>Now share it with your friends ❤️<br>
|
||||
<canvas id="qrcode" width="121" height="121"></canvas><br>
|
||||
<button onclick="window.download()">💾 download scene file</button> <br>
|
||||
<button onclick="alert('this might take a while'); $('a-scene').components.screenshot.capture('equirectangular')">📷 download 360 screenshot</button> <br>
|
||||
<a class="btn" target="_blank" href="https://github.com/coderofsalvation/xrfragment-helloworld">🖥 clone & selfhost this experience</a><br>
|
||||
<br>
|
||||
To embed this experience in your blog,<br>
|
||||
copy/paste the following into your HTML:<br><input type="text" value="<iframe src='${document.location.href}'><br></iframe>" id="share"/>
|
||||
<br>
|
||||
`,{timeout:2000000})
|
||||
// draw QR code
|
||||
setTimeout( () => {
|
||||
let QR = window.QR
|
||||
QR.canvas = document.getElementById('qrcode')
|
||||
QR.draw( document.location.href, QR.canvas )
|
||||
},0)
|
||||
}
|
||||
}
|
||||
|
||||
window.XRFMENU.addHTML = () => {
|
||||
|
||||
let el = document.createElement("div")
|
||||
el.innerHTML += `<style type="text/css">
|
||||
:root {
|
||||
--xrf-primary: #6839dc;
|
||||
--xrf-primary-fg: #FFF;
|
||||
--xrf-light-primary: #ea23cf;
|
||||
--xrf-secondary: #872eff;
|
||||
--xrf-light-xrf-secondary: #ce7df2;
|
||||
--xrf-overlay-bg: #fffb;
|
||||
--xrf-box-shadow: #0005;
|
||||
--xrf-red: red;
|
||||
--xrf-black: #424280;
|
||||
--xrf-white: #fdfdfd;
|
||||
--xrf-dark-gray: #343334;
|
||||
--xrf-gray: #ecf7ff47;
|
||||
--xrf-light-gray: #efefef;
|
||||
--xrf-lighter-gray: #e4e2fb96;
|
||||
--xrf-font-sans-serif: system-ui, -apple-system, segoe ui, roboto, ubuntu, helvetica, cantarell, noto sans, sans-serif;
|
||||
--xrf-font-monospace: menlo, monaco, lucida console, liberation mono, dejavu sans mono, bitstream vera sans mono, courier new, monospace, serif;
|
||||
--xrf-font-size-1: 14px;
|
||||
--xrf-font-size-2: 17px;
|
||||
--xrf-font-size-3: 21px;
|
||||
}
|
||||
|
||||
.xrf table tr td{
|
||||
vertical-align:top;
|
||||
}
|
||||
.xrf table tr td:nth-child(1){
|
||||
padding-right:35px;
|
||||
}
|
||||
|
||||
.xrf button,
|
||||
.xrf input[type="submit"],
|
||||
.xrf .btn {
|
||||
text-decoration:none;
|
||||
background: var(--xrf-primary);
|
||||
border: 0;
|
||||
border-radius: 25px;
|
||||
padding: 11px 15px;
|
||||
font-weight: bold;
|
||||
transition: 0.3s;
|
||||
height: 32px;
|
||||
font-size: var(--xrf-font-size-1);
|
||||
color: var(--xrf-primary-fg);
|
||||
line-height: var(--xrf-font-size-1);
|
||||
cursor:pointer;
|
||||
white-space:pre;
|
||||
min-width: 45px;
|
||||
box-shadow: 0px 0px 10px var(--xrf-box-shadow);
|
||||
}
|
||||
|
||||
.xrf button:hover,
|
||||
.xrf input[type="submit"]:hover,
|
||||
.xrf .btn:hover {
|
||||
background: var(--xrf-secondary);
|
||||
}
|
||||
|
||||
.xrf, .xrf *{
|
||||
font-family: var(--xrf-font-sans-serif);
|
||||
font-size: var(--xrf-font-size-1);
|
||||
line-height:27px;
|
||||
}
|
||||
|
||||
textarea, select, input[type="text"] {
|
||||
background: transparent; /* linear-gradient( var(--xrf-lighter-gray), var(--xrf-gray) ) !important; */
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
color: var(--xrf-light-gray);
|
||||
}
|
||||
|
||||
input[type=text]{
|
||||
padding:7px 15px;
|
||||
}
|
||||
input{
|
||||
border-radius:7px;
|
||||
margin:5px 0px;
|
||||
}
|
||||
|
||||
.title {
|
||||
border-bottom: 2px solid var(--xrf-secondary);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#overlay{
|
||||
background: var(--xrf-overlay-bg);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
box-shadow: 0px 0px 10px var(--xrf-box-shadow);
|
||||
opacity: 0.9;
|
||||
z-index:2000;
|
||||
}
|
||||
|
||||
#overlay .logo{
|
||||
width: 92px;
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 93px;
|
||||
height: 30px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#overlay > input[type="submit"] {
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#overlay > button#navback,
|
||||
#overlay > button#navforward {
|
||||
height: 32px;
|
||||
font-size: var(--xrf-font-size-1);
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
padding: 2px 13px;
|
||||
border-radius:6px;
|
||||
top: 8px;
|
||||
color: var(--xrf-light-gray);
|
||||
width: 36px;
|
||||
min-width: unset;
|
||||
}
|
||||
#overlay > button#navforward {
|
||||
left:49px;
|
||||
}
|
||||
|
||||
#overlay > #uri {
|
||||
height: 18px;
|
||||
font-size: var(--xrf-font-size-3);
|
||||
position: absolute;
|
||||
left: 200px;
|
||||
top: 9px;
|
||||
max-width: 550px;
|
||||
padding: 5px 0px 5px 5px;
|
||||
width: calc( 63% - 200px);
|
||||
background: #f0f0f0;
|
||||
border-color: #Ccc;
|
||||
border: 2px solid #CCC;
|
||||
border-radius: 7px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.menu .btn{
|
||||
background: var(--xrf-primary);
|
||||
border-radius: 25px;
|
||||
border: 0;
|
||||
padding: 5px 19px;
|
||||
font-weight: 1000;
|
||||
font-family: sans-serif;
|
||||
font-size: var(--xrf-font-size-2);
|
||||
color:var(--xrf-primary-fg);
|
||||
height:33px;
|
||||
z-index:2000;
|
||||
cursor:pointer;
|
||||
min-width:107px;
|
||||
text-decoration:none;
|
||||
display:none;
|
||||
margin-top: 15px;
|
||||
line-height:36px;
|
||||
margin-right:10px;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.xrf a.btn#more{
|
||||
width: 19px;
|
||||
min-width: 19px;
|
||||
font-size:16px;
|
||||
text-align: center;
|
||||
background:white;
|
||||
}
|
||||
|
||||
html{
|
||||
max-width:unset;
|
||||
}
|
||||
|
||||
.render {
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.lil-gui.autoPlace{
|
||||
right:0px !important;
|
||||
top:48px !important;
|
||||
height:33vh;
|
||||
}
|
||||
|
||||
#VRButton {
|
||||
margin-bottom:20vh;
|
||||
}
|
||||
|
||||
@media (max-width: 450px) {
|
||||
#uri{ display:none; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.lil-gui.root{
|
||||
top:auto !important;
|
||||
left:auto !important;
|
||||
}
|
||||
.js-snackbar__message{
|
||||
overflow-y:auto;
|
||||
max-height:600px;
|
||||
}
|
||||
.js-snackbar__message h1,h2,h3{
|
||||
font-size:22px;
|
||||
}
|
||||
.xrf table tr td {
|
||||
|
||||
}
|
||||
:root{
|
||||
--xrf-font-size-1: 13px;
|
||||
--xrf-font-size-2: 17px;
|
||||
--xrf-font-size-3: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* notifications */
|
||||
|
||||
.js-snackbar-container .btn,
|
||||
.js-snackbar-container input[type=submit],
|
||||
.js-snackbar-container button{
|
||||
margin-bottom:15px;
|
||||
}
|
||||
.js-snackbar-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width:100%;
|
||||
max-width: 100%;
|
||||
padding: 10px;
|
||||
z-index:1001;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.js-snackbar-container * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.js-snackbar__wrapper {
|
||||
--color-c: #555;
|
||||
--color-a: #FFF;
|
||||
}
|
||||
|
||||
|
||||
.js-snackbar__wrapper {
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
margin: 5px 0;
|
||||
transition: all ease .5s;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 4px 0 var(--xrf-box-shadow);
|
||||
right: 20px;
|
||||
position: fixed;
|
||||
top: 55px;
|
||||
}
|
||||
|
||||
.js-snackbar {
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
color: var(--color-c);
|
||||
background-color: var(--color-a);
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.js-snackbar__close,
|
||||
.js-snackbar__status,
|
||||
.js-snackbar__message {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.js-snackbar__message {
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.js-snackbar__status {
|
||||
display: none;
|
||||
width: 15px;
|
||||
margin-right: 5px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--success,
|
||||
.js-snackbar__status.js-snackbar--warning,
|
||||
.js-snackbar__status.js-snackbar--danger,
|
||||
.js-snackbar__status.js-snackbar--info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--success {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--warning {
|
||||
background-color: #ff9800;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--danger {
|
||||
background-color: #ff6060;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--info {
|
||||
background-color: #CCC;
|
||||
}
|
||||
|
||||
.js-snackbar__close {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.js-snackbar__close:hover {
|
||||
background-color: #4443;
|
||||
}
|
||||
|
||||
.a-enter-vr-button, .a-enter-ar-button{
|
||||
height:41px;
|
||||
}
|
||||
|
||||
#qrcode{
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
height: 121px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input#share{
|
||||
font-size: var(--xrf-font-size-1);
|
||||
font-family: var(--xrf-font-monospace);
|
||||
border:2px solid #AAA;
|
||||
width:50vw;
|
||||
max-width:400px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column-reverse; /* This reverses the stacking order of the flex container */
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 71px;
|
||||
right: 11px;
|
||||
bottom: 0;
|
||||
padding-bottom:149px;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.footer .menu{
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="overlay" class="xrf" style="display:none">
|
||||
<div class="logo"></div>
|
||||
<button id="navback" onclick="history.back()"><</button>
|
||||
<button id="navforward" onclick="history.forward()">></button>
|
||||
<input type="submit" value="load 3D file"></input>
|
||||
<input type="text" id="uri" value="" onchange="AFRAME.XRF.navigator.to( $('#uri').value )" style="display:none"/>
|
||||
</div>
|
||||
<!-- open AFRAME inspector: $('a-scene').components.inspector.openInspector() -->
|
||||
<div class="xrf footer">
|
||||
<div id="buttons" class="menu">
|
||||
${window.XRFMENU.html.map( (html) => typeof html == "function" ? html() : html ).join('\n')}
|
||||
<a class="btn" id="more" style="display:inline-block">${window.XRFMENU.morelabel}</a>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
/*
|
||||
* (promise-able) EVENTS (optionally continue after listeners are finished using .then)
|
||||
*
|
||||
|
|
|
|||
702
dist/xrfragment.three.module.js
vendored
702
dist/xrfragment.three.module.js
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Wed Dec 13 07:23:32 PM CET 2023
|
||||
* v0.5.1 generated at Fri Dec 15 04:23:00 PM CET 2023
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
|
@ -602,6 +602,706 @@ xrf.hasTag = (tag,tags) => String(tags).match( new RegExp(`(^| )${tag}( |$)`,`g`
|
|||
|
||||
// map library functions to xrf
|
||||
for ( let i in xrfragment ) xrf[i] = xrfragment[i]
|
||||
// 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: [
|
||||
`<a class="btn" aria-label="button" aria-description="start text/audio/video chat" id="meeting" target="_blank">🧑🤝🧑 meeting</a><br>`,
|
||||
`<a class="btn" aria-label="button" aria-description="share URL/screenshot/embed" id="share" target="_blank" onclick="window.share()">🔗 share</a><br>`
|
||||
],
|
||||
|
||||
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 = '📍 new meeting location'
|
||||
}
|
||||
$('a#meeting').addEventListener('click', () => {
|
||||
if( aScene.getAttribute('meeting') ){ // meeting already, start breakout room
|
||||
let parentRoom = document.location.href
|
||||
XRFMENU.updateHashPosition(true)
|
||||
let visitorname = aScene.getAttribute("meeting").visitorname
|
||||
aScene.removeAttribute('meeting')
|
||||
// breakoutroom
|
||||
aScene.setAttribute('meeting', `id: xrfragments; visitorname: ${visitorname}; parentRoom: ${parentRoom}`)
|
||||
}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(/#&/,'')
|
||||
// copy url to clipboard
|
||||
var dummy = document.createElement('input'),
|
||||
text = window.location.href;
|
||||
document.body.appendChild(dummy);
|
||||
dummy.value = text;
|
||||
dummy.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(dummy);
|
||||
},
|
||||
|
||||
share(){
|
||||
XRFMENU.updateHashPosition()
|
||||
// End of *TODO*
|
||||
window.notify(`<h2>Link copied to clipboard!</h2> <br>Now share it with your friends ❤️<br>
|
||||
<canvas id="qrcode" width="121" height="121"></canvas><br>
|
||||
<button onclick="window.download()">💾 download scene file</button> <br>
|
||||
<button onclick="alert('this might take a while'); $('a-scene').components.screenshot.capture('equirectangular')">📷 download 360 screenshot</button> <br>
|
||||
<a class="btn" target="_blank" href="https://github.com/coderofsalvation/xrfragment-helloworld">🖥 clone & selfhost this experience</a><br>
|
||||
<br>
|
||||
To embed this experience in your blog,<br>
|
||||
copy/paste the following into your HTML:<br><input type="text" value="<iframe src='${document.location.href}'><br></iframe>" id="share"/>
|
||||
<br>
|
||||
`,{timeout:2000000})
|
||||
// draw QR code
|
||||
setTimeout( () => {
|
||||
let QR = window.QR
|
||||
QR.canvas = document.getElementById('qrcode')
|
||||
QR.draw( document.location.href, QR.canvas )
|
||||
},0)
|
||||
}
|
||||
}
|
||||
|
||||
window.XRFMENU.addHTML = () => {
|
||||
|
||||
let el = document.createElement("div")
|
||||
el.innerHTML += `<style type="text/css">
|
||||
:root {
|
||||
--xrf-primary: #6839dc;
|
||||
--xrf-primary-fg: #FFF;
|
||||
--xrf-light-primary: #ea23cf;
|
||||
--xrf-secondary: #872eff;
|
||||
--xrf-light-xrf-secondary: #ce7df2;
|
||||
--xrf-overlay-bg: #fffb;
|
||||
--xrf-box-shadow: #0005;
|
||||
--xrf-red: red;
|
||||
--xrf-black: #424280;
|
||||
--xrf-white: #fdfdfd;
|
||||
--xrf-dark-gray: #343334;
|
||||
--xrf-gray: #ecf7ff47;
|
||||
--xrf-light-gray: #efefef;
|
||||
--xrf-lighter-gray: #e4e2fb96;
|
||||
--xrf-font-sans-serif: system-ui, -apple-system, segoe ui, roboto, ubuntu, helvetica, cantarell, noto sans, sans-serif;
|
||||
--xrf-font-monospace: menlo, monaco, lucida console, liberation mono, dejavu sans mono, bitstream vera sans mono, courier new, monospace, serif;
|
||||
--xrf-font-size-1: 14px;
|
||||
--xrf-font-size-2: 17px;
|
||||
--xrf-font-size-3: 21px;
|
||||
}
|
||||
|
||||
.xrf table tr td{
|
||||
vertical-align:top;
|
||||
}
|
||||
.xrf table tr td:nth-child(1){
|
||||
padding-right:35px;
|
||||
}
|
||||
|
||||
.xrf button,
|
||||
.xrf input[type="submit"],
|
||||
.xrf .btn {
|
||||
text-decoration:none;
|
||||
background: var(--xrf-primary);
|
||||
border: 0;
|
||||
border-radius: 25px;
|
||||
padding: 11px 15px;
|
||||
font-weight: bold;
|
||||
transition: 0.3s;
|
||||
height: 32px;
|
||||
font-size: var(--xrf-font-size-1);
|
||||
color: var(--xrf-primary-fg);
|
||||
line-height: var(--xrf-font-size-1);
|
||||
cursor:pointer;
|
||||
white-space:pre;
|
||||
min-width: 45px;
|
||||
box-shadow: 0px 0px 10px var(--xrf-box-shadow);
|
||||
}
|
||||
|
||||
.xrf button:hover,
|
||||
.xrf input[type="submit"]:hover,
|
||||
.xrf .btn:hover {
|
||||
background: var(--xrf-secondary);
|
||||
}
|
||||
|
||||
.xrf, .xrf *{
|
||||
font-family: var(--xrf-font-sans-serif);
|
||||
font-size: var(--xrf-font-size-1);
|
||||
line-height:27px;
|
||||
}
|
||||
|
||||
textarea, select, input[type="text"] {
|
||||
background: transparent; /* linear-gradient( var(--xrf-lighter-gray), var(--xrf-gray) ) !important; */
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
color: var(--xrf-light-gray);
|
||||
}
|
||||
|
||||
input[type=text]{
|
||||
padding:7px 15px;
|
||||
}
|
||||
input{
|
||||
border-radius:7px;
|
||||
margin:5px 0px;
|
||||
}
|
||||
|
||||
.title {
|
||||
border-bottom: 2px solid var(--xrf-secondary);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#overlay{
|
||||
background: var(--xrf-overlay-bg);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
box-shadow: 0px 0px 10px var(--xrf-box-shadow);
|
||||
opacity: 0.9;
|
||||
z-index:2000;
|
||||
}
|
||||
|
||||
#overlay .logo{
|
||||
width: 92px;
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 93px;
|
||||
height: 30px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#overlay > input[type="submit"] {
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#overlay > button#navback,
|
||||
#overlay > button#navforward {
|
||||
height: 32px;
|
||||
font-size: var(--xrf-font-size-1);
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
padding: 2px 13px;
|
||||
border-radius:6px;
|
||||
top: 8px;
|
||||
color: var(--xrf-light-gray);
|
||||
width: 36px;
|
||||
min-width: unset;
|
||||
}
|
||||
#overlay > button#navforward {
|
||||
left:49px;
|
||||
}
|
||||
|
||||
#overlay > #uri {
|
||||
height: 18px;
|
||||
font-size: var(--xrf-font-size-3);
|
||||
position: absolute;
|
||||
left: 200px;
|
||||
top: 9px;
|
||||
max-width: 550px;
|
||||
padding: 5px 0px 5px 5px;
|
||||
width: calc( 63% - 200px);
|
||||
background: #f0f0f0;
|
||||
border-color: #Ccc;
|
||||
border: 2px solid #CCC;
|
||||
border-radius: 7px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.menu .btn{
|
||||
background: var(--xrf-primary);
|
||||
border-radius: 25px;
|
||||
border: 0;
|
||||
padding: 5px 19px;
|
||||
font-weight: 1000;
|
||||
font-family: sans-serif;
|
||||
font-size: var(--xrf-font-size-2);
|
||||
color:var(--xrf-primary-fg);
|
||||
height:33px;
|
||||
z-index:2000;
|
||||
cursor:pointer;
|
||||
min-width:107px;
|
||||
text-decoration:none;
|
||||
display:none;
|
||||
margin-top: 15px;
|
||||
line-height:36px;
|
||||
margin-right:10px;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.xrf a.btn#more{
|
||||
width: 19px;
|
||||
min-width: 19px;
|
||||
font-size:16px;
|
||||
text-align: center;
|
||||
background:white;
|
||||
}
|
||||
|
||||
html{
|
||||
max-width:unset;
|
||||
}
|
||||
|
||||
.render {
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.lil-gui.autoPlace{
|
||||
right:0px !important;
|
||||
top:48px !important;
|
||||
height:33vh;
|
||||
}
|
||||
|
||||
#VRButton {
|
||||
margin-bottom:20vh;
|
||||
}
|
||||
|
||||
@media (max-width: 450px) {
|
||||
#uri{ display:none; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.lil-gui.root{
|
||||
top:auto !important;
|
||||
left:auto !important;
|
||||
}
|
||||
.js-snackbar__message{
|
||||
overflow-y:auto;
|
||||
max-height:600px;
|
||||
}
|
||||
.js-snackbar__message h1,h2,h3{
|
||||
font-size:22px;
|
||||
}
|
||||
.xrf table tr td {
|
||||
|
||||
}
|
||||
:root{
|
||||
--xrf-font-size-1: 13px;
|
||||
--xrf-font-size-2: 17px;
|
||||
--xrf-font-size-3: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* notifications */
|
||||
|
||||
.js-snackbar-container .btn,
|
||||
.js-snackbar-container input[type=submit],
|
||||
.js-snackbar-container button{
|
||||
margin-bottom:15px;
|
||||
}
|
||||
.js-snackbar-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width:100%;
|
||||
max-width: 100%;
|
||||
padding: 10px;
|
||||
z-index:1001;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.js-snackbar-container * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.js-snackbar__wrapper {
|
||||
--color-c: #555;
|
||||
--color-a: #FFF;
|
||||
}
|
||||
|
||||
|
||||
.js-snackbar__wrapper {
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
margin: 5px 0;
|
||||
transition: all ease .5s;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 4px 0 var(--xrf-box-shadow);
|
||||
right: 20px;
|
||||
position: fixed;
|
||||
top: 55px;
|
||||
}
|
||||
|
||||
.js-snackbar {
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
color: var(--color-c);
|
||||
background-color: var(--color-a);
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.js-snackbar__close,
|
||||
.js-snackbar__status,
|
||||
.js-snackbar__message {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.js-snackbar__message {
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.js-snackbar__status {
|
||||
display: none;
|
||||
width: 15px;
|
||||
margin-right: 5px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--success,
|
||||
.js-snackbar__status.js-snackbar--warning,
|
||||
.js-snackbar__status.js-snackbar--danger,
|
||||
.js-snackbar__status.js-snackbar--info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--success {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--warning {
|
||||
background-color: #ff9800;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--danger {
|
||||
background-color: #ff6060;
|
||||
}
|
||||
|
||||
.js-snackbar__status.js-snackbar--info {
|
||||
background-color: #CCC;
|
||||
}
|
||||
|
||||
.js-snackbar__close {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.js-snackbar__close:hover {
|
||||
background-color: #4443;
|
||||
}
|
||||
|
||||
.a-enter-vr-button, .a-enter-ar-button{
|
||||
height:41px;
|
||||
}
|
||||
|
||||
#qrcode{
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
height: 121px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input#share{
|
||||
font-size: var(--xrf-font-size-1);
|
||||
font-family: var(--xrf-font-monospace);
|
||||
border:2px solid #AAA;
|
||||
width:50vw;
|
||||
max-width:400px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column-reverse; /* This reverses the stacking order of the flex container */
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 71px;
|
||||
right: 11px;
|
||||
bottom: 0;
|
||||
padding-bottom:149px;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.footer .menu{
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="overlay" class="xrf" style="display:none">
|
||||
<div class="logo"></div>
|
||||
<button id="navback" onclick="history.back()"><</button>
|
||||
<button id="navforward" onclick="history.forward()">></button>
|
||||
<input type="submit" value="load 3D file"></input>
|
||||
<input type="text" id="uri" value="" onchange="AFRAME.XRF.navigator.to( $('#uri').value )" style="display:none"/>
|
||||
</div>
|
||||
<!-- open AFRAME inspector: $('a-scene').components.inspector.openInspector() -->
|
||||
<div class="xrf footer">
|
||||
<div id="buttons" class="menu">
|
||||
${window.XRFMENU.html.map( (html) => typeof html == "function" ? html() : html ).join('\n')}
|
||||
<a class="btn" id="more" style="display:inline-block">${window.XRFMENU.morelabel}</a>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
/*
|
||||
* (promise-able) EVENTS (optionally continue after listeners are finished using .then)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -35,21 +35,26 @@
|
|||
XRFMENU.html.push(`<a class="btn" aria-label="button" aria-description="help menu" onclick="window.showHelp()">💁 help?</a><br>`)
|
||||
showHelp = () => window.notify(`
|
||||
<h1>💁 Hi there!</h1>
|
||||
|
||||
|
||||
This XR experience works on almost any device.<br>
|
||||
Though, the best is with AR/VR headsets ♥️<br><br>
|
||||
Allowing rich audiovisual events without forcing<br>
|
||||
use of a VR/AR headset (it's awesome though ♥️)<br>
|
||||
<br>
|
||||
<table>
|
||||
${
|
||||
$$('.footer>.btn').map( (btn) => {
|
||||
$$('.menu>.btn').map( (btn) => {
|
||||
let info = btn.getAttribute("aria-description")
|
||||
return info && info != "help menu" ? `<tr><td>${btn.outerHTML}</td><td>${info}</td></tr>` : ''
|
||||
}).join('\n')
|
||||
}
|
||||
</table>
|
||||
<b>This uses only open standards:</b><br>
|
||||
📦 simply surf hyperlinked 3D model files<br>
|
||||
📦 visit 3D models using URLs<br>
|
||||
🧑🤝🧑 meet inside hyperlinked 3D models<br>
|
||||
🚫 no proprietary tech/game-engines<br>
|
||||
🔗 <a href="https://xrfragment.org" target="_blank">XR Fragments</a> for links & navigation<br>
|
||||
📷 Serverless <a href="https://webrtc.org" target="_blank">P2P WebRTC</a><br>
|
||||
🔗 <a href="https://xrfragment.org" target="_blank">XR Fragments</a> for 3D hyper-linking & navigation<br>
|
||||
📷 Serverless <a href="https://webrtc.org" target="_blank">P2P WebRTC</a> using <a href="https://github.com/dmotz/trystero" target="_blank">trystero</a><br>
|
||||
🅰 <a href="https://aframe.io" target="_blank">AFRAME</a> + <a href="https://three.org" target="_blank">Three.js</a> for <a href="https://immersiveweb.dev" target="_blank">WebXR</a><br>
|
||||
🙉 go selfhost <a href="https://github.com/coderofsalvation/xrfragment-helloworld">worlds-in-a-webpage</a><br>
|
||||
♥️ Be sustainable: go 100% <a href="https://www.forbes.com/sites/adrianbridgwater/2023/02/06/the-future-for-open-source/" target="_blank">opensource</a>
|
||||
|
|
@ -58,7 +63,7 @@
|
|||
<img src="https://nlnet.nl/image/logo_nlnet.svg" width="100"/>
|
||||
</a>
|
||||
<br><br>
|
||||
`,{timeout:40000})
|
||||
`,{timeout:false})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
AFRAME.registerComponent('meeting', {
|
||||
schema:{
|
||||
id:{ required:true, type:'string'}
|
||||
id:{ required:true, type:'string'},
|
||||
visitorname:{required:false,type:'string'},
|
||||
parentRoom:{required:false,type:'string'}
|
||||
},
|
||||
remove: function(){
|
||||
if( this.room ) this.room.leave()
|
||||
this.meeting.remove()
|
||||
},
|
||||
init: function(){
|
||||
// embed https://github.com/dmotz/trystero (trystero-torrent.min.js build)
|
||||
|
||||
// add css+html
|
||||
let el = document.createElement("div")
|
||||
let el = this.meeting = document.createElement("div")
|
||||
el.id = 'meeting'
|
||||
el.innerHTML += `<style type="text/css">
|
||||
#videos{
|
||||
display:grid-auto-columns;
|
||||
|
|
@ -58,17 +65,50 @@ AFRAME.registerComponent('meeting', {
|
|||
top: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 110px;
|
||||
bottom: 88px;
|
||||
padding: 15px;
|
||||
pointer-events: none;
|
||||
overflow-y:auto;
|
||||
}
|
||||
#chat .msg{
|
||||
background: #fffc;
|
||||
background: #fff;
|
||||
display: inline-block;
|
||||
padding: 6px 17px;
|
||||
border-radius: 20px;
|
||||
color: #000c;
|
||||
margin-bottom: 10px;
|
||||
line-height:23px;
|
||||
pointer-events:visible;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#chat .msg.info{
|
||||
background: #333;
|
||||
color: #FFF;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
#chat .msg.info a,
|
||||
#chat .msg.info a:visited{
|
||||
color: #aaf;
|
||||
text-decoration: none;
|
||||
transition:0.3s;
|
||||
}
|
||||
#chat .msg.info a:hover,
|
||||
#chat button:hover{
|
||||
filter: brightness(1.8);
|
||||
text-decoration: underline;
|
||||
}
|
||||
#chat button {
|
||||
margin: 0px 15px 10px 0px;
|
||||
background: #07F;
|
||||
color: #FFF;
|
||||
border-radius: 7px;
|
||||
padding: 11px 15px;
|
||||
border: 0;
|
||||
font-weight: bold;
|
||||
box-shadow: 0px 0px 5px 5px #0002;
|
||||
pointer-events:all;
|
||||
}
|
||||
#chat,#chatbar,#chatbar *, #chat *{
|
||||
font-family:monospace;
|
||||
|
|
@ -78,19 +118,57 @@ AFRAME.registerComponent('meeting', {
|
|||
<div id="videos" style="pointer-events:none"></div>
|
||||
<div id="chat" aria-live="assertive" aria-relevant></div>
|
||||
<div id="chatbar">
|
||||
<input id="chatline" type="text" placeholder="chat here"></input>
|
||||
<input id="chatline" type="text" placeholder="enter your name"></input>
|
||||
</div>`
|
||||
document.body.appendChild(el)
|
||||
this.trysteroInit()
|
||||
|
||||
let chatline = this.chatline = document.querySelector("#chatline")
|
||||
let chat = this.chat = document.querySelector("#chat")
|
||||
chat.log = [] // save raw chatlog to prime new visitors
|
||||
chat.append = (str,classes,buttons) => this.chatAppend(str,classes,buttons)
|
||||
|
||||
this.initChatLine()
|
||||
|
||||
if( !this.data.visitorname ) this.chat.append("💁 Hi there! Please enter your name")
|
||||
else{
|
||||
if( this.data.parentRoom ) this.chat.append(`leaving ${this.data.parentRoom}`,["info"]);
|
||||
this.trysteroInit()
|
||||
}
|
||||
},
|
||||
|
||||
chatAppend: function(str,classes,buttons){
|
||||
if( str ){
|
||||
str = str.replace('\n', "<br>")
|
||||
.replace(/^[a-zA-Z-0-9]+?[:\.]/,'<b>$&</b>')
|
||||
let el = this.createMsg(str)
|
||||
if( classes ) classes.map( (c) => el.classList.add(c) )
|
||||
this.chat.appendChild(el) // send to screen
|
||||
this.chat.innerHTML += '<br>'
|
||||
this.chat.log.push(str)
|
||||
}
|
||||
if( buttons ){
|
||||
for( let i in buttons ){
|
||||
let btn = document.createElement("button")
|
||||
btn.innerText = i
|
||||
btn.addEventListener('click', () => buttons[i]() )
|
||||
this.chat.appendChild(btn)
|
||||
}
|
||||
this.chat.innerHTML += '<br>'
|
||||
}
|
||||
this.chat.scrollTop = this.chat.scrollHeight; // scroll to bottom
|
||||
},
|
||||
|
||||
trysteroInit: async function(){
|
||||
const { joinRoom } = await import("./../../../dist/trystero-torrent.min.js");
|
||||
|
||||
const roomname = document.location.href.replace(/#.*/,'')
|
||||
if( !document.location.hash.match(/meet/) ){
|
||||
document.location.hash += document.location.hash.match(/#/) ? '&meet' : '#meet'
|
||||
}
|
||||
let roomname = this.roomname = document.location.href
|
||||
const config = this.config = {appId: this.data.id }
|
||||
const room = this.room = joinRoom(config, roomname )
|
||||
console.log("starting webrtc room: "+roomname)
|
||||
this.chat.append("joined meeting at "+roomname,["info"]);
|
||||
this.chat.append("copied meeting link to clipboard",["info"]);
|
||||
|
||||
const idsToNames = this.idsToNames = {}
|
||||
const [sendName, getName] = room.makeAction('name')
|
||||
|
|
@ -101,9 +179,8 @@ AFRAME.registerComponent('meeting', {
|
|||
this.getName = getName
|
||||
|
||||
// tell other peers currently in the room our name
|
||||
let name = this.name = prompt('enter your name:')
|
||||
idsToNames[ room.selfId ] = name.substr(0,15)
|
||||
sendName( name )
|
||||
idsToNames[ room.selfId ] = this.data.visitorname.substr(0,15)
|
||||
sendName( this.data.visitorname )
|
||||
|
||||
// listen for peers naming themselves
|
||||
getName((name, peerId) => (idsToNames[peerId] = name))
|
||||
|
|
@ -133,7 +210,6 @@ AFRAME.registerComponent('meeting', {
|
|||
|
||||
// send stream + chatlog to peers who join later
|
||||
room.onPeerJoin( (peerId) => {
|
||||
console.log(`${idsToNames[peerId] || 'a visitor'} joined`)
|
||||
room.addStream(selfStream, peerId)
|
||||
sendName( name, peerId)
|
||||
this.sendChat({prime: this.chat.log}, peerId )
|
||||
|
|
@ -192,7 +268,7 @@ AFRAME.registerComponent('meeting', {
|
|||
return video
|
||||
},
|
||||
|
||||
createElement: function(str){
|
||||
createMsg: function(str){
|
||||
let el = document.createElement("div")
|
||||
el.className = "msg"
|
||||
el.innerHTML = this.linkify(str)
|
||||
|
|
@ -200,38 +276,31 @@ AFRAME.registerComponent('meeting', {
|
|||
},
|
||||
|
||||
// central function to broadcast stuff to chat
|
||||
send: function(str){
|
||||
send: function(str,classes,buttons){
|
||||
if( !this.sendChat ) return
|
||||
this.sendChat({content:str}) // send to network
|
||||
this.chat.append(str) // send to screen
|
||||
this.sendChat({content:str}) // send to network
|
||||
this.chat.append(str,classes,buttons) // send to screen
|
||||
},
|
||||
|
||||
initChat: function(){
|
||||
|
||||
initChatLine: function(){
|
||||
let chatline = this.chatline = document.querySelector("#chatline")
|
||||
let chat = this.chat = document.querySelector("#chat")
|
||||
chat.log = [] // save raw chatlog to prime new visitors
|
||||
chat.append = (str) => {
|
||||
if( !str ) return
|
||||
str = str.replace('\n', "<br>")
|
||||
this.chat.appendChild(this.createElement(str)) // send to screen
|
||||
this.chat.innerHTML += '<br>'
|
||||
this.chat.log.push(str)
|
||||
}
|
||||
|
||||
let send = () => {
|
||||
let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}`
|
||||
this.send(str)
|
||||
}
|
||||
|
||||
chatline.addEventListener("keydown", (e) => {
|
||||
if( e.key !== "Enter" ) return
|
||||
send()
|
||||
if( !this.data.visitorname ){
|
||||
this.data.visitorname = chatline.value
|
||||
this.chat.append("btw. camera/mic access is totally optional ♥️")
|
||||
this.trysteroInit()
|
||||
}else{
|
||||
let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}`
|
||||
this.send(str)
|
||||
}
|
||||
chatline.value = ''
|
||||
event.preventDefault();
|
||||
event.target.blur()
|
||||
})
|
||||
},
|
||||
|
||||
initChat: function(){
|
||||
// listen for chatmsg
|
||||
this.getChat((data, peerId) => {
|
||||
if( data.prime ){
|
||||
|
|
@ -240,33 +309,41 @@ AFRAME.registerComponent('meeting', {
|
|||
data.prime.map( (l) => chat.append(l) ) // send log to screen
|
||||
this.chat.primed = true
|
||||
}
|
||||
chat.append(data.content) // send to screen
|
||||
chat.append(data.content,data.classes,data.buttons) // send to screen
|
||||
})
|
||||
|
||||
// notify join in chat
|
||||
this.send( this.name+": joined")
|
||||
|
||||
this.notifyTeleport()
|
||||
return this
|
||||
},
|
||||
|
||||
notifyTeleport: function(buttons){
|
||||
// send to network
|
||||
this.sendChat({
|
||||
content: `${this.data.visitorname} teleported to ${this.roomname}`,
|
||||
classes: ["info"],
|
||||
buttons
|
||||
})
|
||||
},
|
||||
|
||||
linkify: function(t){
|
||||
const isValidHttpUrl = s => {
|
||||
let u
|
||||
try {u = new URL(s)}
|
||||
catch (_) {return false}
|
||||
return u.protocol.startsWith("http")
|
||||
}
|
||||
const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g)
|
||||
const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+[?\.][a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g)
|
||||
if (!m) return t
|
||||
const a = []
|
||||
m.forEach(x => {
|
||||
const [t1, ...t2] = t.split(x)
|
||||
a.push(t1)
|
||||
t = t2.join(x)
|
||||
const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x
|
||||
if (isNaN(x) && isValidHttpUrl(y))
|
||||
a.push('<a href="' + y + '" target="_blank" style="pointer-events:all">' + y.split('/')[2] + '</a>')
|
||||
else
|
||||
let y = (!(x.match(/:\/\//)) ? 'https://' : '') + x
|
||||
if (isNaN(x) ){
|
||||
let url_human = y.split('/')[2]
|
||||
let isXRFragment = y.match("pos=")
|
||||
if( isXRFragment ){ // detect xr fragments
|
||||
url_human = y.replace(/.*[?\?]/,'') // shorten xr fragment links
|
||||
.replace(/[?\&]meet/,'')
|
||||
y = y.replace(/.*[\?]/, '?') // start from search (to prevent page-refresh)
|
||||
}
|
||||
a.push(`<a href="${y}" ${isXRFragment ? '' : `target="_blank"`} style="pointer-events:all">${url_human}</a>`)
|
||||
}else
|
||||
a.push(x)
|
||||
})
|
||||
a.push(t)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ window.XRFMENU = {
|
|||
|
||||
html: [
|
||||
`<a class="btn" aria-label="button" aria-description="start text/audio/video chat" id="meeting" target="_blank">🧑🤝🧑 meeting</a><br>`,
|
||||
`<a class="btn" aria-label="button" aria-description="share URL/screenshot/embed" id="share" target="_blank" onclick="window.embed()">🔗 share</a><br>`
|
||||
`<a class="btn" aria-label="button" aria-description="share URL/screenshot/embed" id="share" target="_blank" onclick="window.share()">🔗 share</a><br>`
|
||||
],
|
||||
|
||||
loadFile(contentLoaders, multiple){
|
||||
|
|
@ -32,9 +32,10 @@ window.XRFMENU = {
|
|||
},
|
||||
|
||||
setupMenu(XRF){
|
||||
let aScene = document.querySelector('a-scene')
|
||||
let urlbar = $('input#uri')
|
||||
let inIframe = window.location !== window.parent.location
|
||||
let els = [ ...document.querySelectorAll('.footer .btn') ]
|
||||
let els = [ ...document.querySelectorAll('.menu .btn') ]
|
||||
els = els.filter( (el) => el.id != "more" ? el : false )
|
||||
|
||||
let showMenu = (state) => {
|
||||
|
|
@ -47,7 +48,23 @@ window.XRFMENU = {
|
|||
els.map( (el) => el.addEventListener('click', () => showMenu(false) ) )
|
||||
$('a#more').addEventListener('click', () => showMenu(true) )
|
||||
$('.a-canvas').addEventListener('click', () => showMenu(false) )
|
||||
$('a#meeting').addEventListener('click', () => document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments') )
|
||||
|
||||
// enable meetings
|
||||
let startMeeting = () => {
|
||||
aScene.setAttribute('meeting', 'id: xrfragments')
|
||||
$('a#meeting').innerText = '📍 new meeting location'
|
||||
}
|
||||
$('a#meeting').addEventListener('click', () => {
|
||||
if( aScene.getAttribute('meeting') ){ // meeting already, start breakout room
|
||||
let parentRoom = document.location.href
|
||||
XRFMENU.updateHashPosition(true)
|
||||
let visitorname = aScene.getAttribute("meeting").visitorname
|
||||
aScene.removeAttribute('meeting')
|
||||
// breakoutroom
|
||||
aScene.setAttribute('meeting', `id: xrfragments; visitorname: ${visitorname}; parentRoom: ${parentRoom}`)
|
||||
}else startMeeting()
|
||||
})
|
||||
if( document.location.hash.match(/(#|&)meet/) ) startMeeting()
|
||||
|
||||
XRF.addEventListener('hash', () => reflectUrl() )
|
||||
const reflectUrl = window.reflectUrl = (url) => {
|
||||
|
|
@ -72,8 +89,10 @@ window.XRFMENU = {
|
|||
var _Options = _OptionDefaults;
|
||||
|
||||
function _Create() {
|
||||
let _Containers = [ ...document.querySelectorAll(".js-snackbar-container") ]
|
||||
_Containers.map( (c) => c.remove() )
|
||||
_Container = document.querySelector(".js-snackbar-container")
|
||||
if( _Container ){
|
||||
_Container.remove()
|
||||
}
|
||||
_Container = null
|
||||
|
||||
if (!_Container) {
|
||||
|
|
@ -83,6 +102,7 @@ window.XRFMENU = {
|
|||
|
||||
document.body.appendChild(_Container);
|
||||
}
|
||||
_Container.opts = _Options
|
||||
_Container.innerHTML = ''
|
||||
_Element = document.createElement("div");
|
||||
_Element.classList.add("js-snackbar__wrapper","xrf");
|
||||
|
|
@ -115,7 +135,9 @@ window.XRFMENU = {
|
|||
|
||||
_Message = document.createElement("span");
|
||||
_Message.classList.add("js-snackbar__message");
|
||||
_Message.innerHTML = _Options.message;
|
||||
if( typeof _Options.message == 'string' ){
|
||||
_Message.innerHTML = _Options.message;
|
||||
}else _Message.appendChild(_Options.message)
|
||||
|
||||
innerSnack.appendChild(_Message);
|
||||
|
||||
|
|
@ -142,60 +164,6 @@ window.XRFMENU = {
|
|||
}
|
||||
}
|
||||
|
||||
var _ConfigureDefaults = function() {
|
||||
// if no options given, revert to default
|
||||
if (userOptions === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userOptions.message !== undefined) {
|
||||
_Options.message = userOptions.message;
|
||||
}
|
||||
|
||||
if (userOptions.dismissible !== undefined) {
|
||||
if (typeof (userOptions.dismissible) === "string") {
|
||||
_Options.dismissible = (userOptions.dismissible === "true");
|
||||
}
|
||||
else if (typeof (userOptions.dismissible) === "boolean") {
|
||||
_Options.dismissible = userOptions.dismissible;
|
||||
}
|
||||
else {
|
||||
console.debug("Invalid option provided for 'dismissable' [" + userOptions.dismissible + "] is of type " + (typeof userOptions.dismissible));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (userOptions.timeout !== undefined) {
|
||||
if (typeof (userOptions.timeout) === "boolean" && userOptions.timeout === false) {
|
||||
_Options.timeout = false;
|
||||
}
|
||||
else if (typeof (userOptions.timeout) === "string") {
|
||||
_Options.timeout = parseInt(userOptions.timeout);
|
||||
}
|
||||
|
||||
|
||||
if (typeof (userOptions.timeout) === "number") {
|
||||
if (userOptions.timeout === Infinity) {
|
||||
_Options.timeout = false;
|
||||
}
|
||||
else if (userOptions.timeout >= 0) {
|
||||
_Options.timeout = userOptions.timeout;
|
||||
}
|
||||
else {
|
||||
console.debug("Invalid timeout entered. Must be greater than or equal to 0.");
|
||||
}
|
||||
|
||||
_Options.timeout = userOptions.timeout;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (userOptions.status !== undefined) {
|
||||
_Options.status = userOptions.status;
|
||||
}
|
||||
}
|
||||
|
||||
snackbar.Open = function() {
|
||||
let contentHeight = _Element.firstElementChild.scrollHeight; // get the height of the content
|
||||
|
||||
|
|
@ -232,25 +200,28 @@ window.XRFMENU = {
|
|||
});
|
||||
|
||||
setTimeout(function() {
|
||||
try { _Container.removeChild(_Element); } catch (e) { }
|
||||
try {
|
||||
_Container.removeChild(_Element);
|
||||
} catch (e) { }
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
_ConfigureDefaults();
|
||||
_Options = { ..._OptionDefaults, ...userOptions }
|
||||
_Create();
|
||||
snackbar.Open();
|
||||
},
|
||||
|
||||
notify(scope){
|
||||
return function notify(str,opts){
|
||||
str = String(str)
|
||||
opts = opts || {}
|
||||
if( !opts.status ){
|
||||
opts.status = "info"
|
||||
if( str.match(/error/g) ) opts.status = "danger"
|
||||
if( str.match(/warning/g) ) opts.status = "warning"
|
||||
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 = Object.assign({ message: str , status, timeout:4000 },opts)
|
||||
opts.message = str
|
||||
window.XRFMENU.SnackBar( opts )
|
||||
}
|
||||
},
|
||||
|
|
@ -267,12 +238,14 @@ window.XRFMENU = {
|
|||
fetchAndDownload( file, file )
|
||||
},
|
||||
|
||||
embed(){
|
||||
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()
|
||||
|
|
@ -295,8 +268,12 @@ window.XRFMENU = {
|
|||
dummy.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(dummy);
|
||||
},
|
||||
|
||||
share(){
|
||||
XRFMENU.updateHashPosition()
|
||||
// End of *TODO*
|
||||
window.notify(`<b>Link copied to clipboard!</b> ❤️<br><br>
|
||||
window.notify(`<h2>Link copied to clipboard!</h2> <br>Now share it with your friends ❤️<br>
|
||||
<canvas id="qrcode" width="121" height="121"></canvas><br>
|
||||
<button onclick="window.download()">💾 download scene file</button> <br>
|
||||
<button onclick="alert('this might take a while'); $('a-scene').components.screenshot.capture('equirectangular')">📷 download 360 screenshot</button> <br>
|
||||
|
|
@ -364,6 +341,8 @@ window.XRFMENU.addHTML = () => {
|
|||
line-height: var(--xrf-font-size-1);
|
||||
cursor:pointer;
|
||||
white-space:pre;
|
||||
min-width: 45px;
|
||||
box-shadow: 0px 0px 10px var(--xrf-box-shadow);
|
||||
}
|
||||
|
||||
.xrf button:hover,
|
||||
|
|
@ -438,6 +417,8 @@ window.XRFMENU.addHTML = () => {
|
|||
border-radius:6px;
|
||||
top: 8px;
|
||||
color: var(--xrf-light-gray);
|
||||
width: 36px;
|
||||
min-width: unset;
|
||||
}
|
||||
#overlay > button#navforward {
|
||||
left:49px;
|
||||
|
|
@ -461,9 +442,8 @@ window.XRFMENU.addHTML = () => {
|
|||
|
||||
|
||||
|
||||
.xrf.footer .btn{
|
||||
.menu .btn{
|
||||
background: var(--xrf-primary);
|
||||
box-shadow: 0px 0px 10px var(--xrf-box-shadow);
|
||||
border-radius: 25px;
|
||||
border: 0;
|
||||
padding: 5px 19px;
|
||||
|
|
@ -480,6 +460,7 @@ window.XRFMENU.addHTML = () => {
|
|||
margin-top: 15px;
|
||||
line-height:36px;
|
||||
margin-right:10px;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.xrf a.btn#more{
|
||||
|
|
@ -525,6 +506,9 @@ window.XRFMENU.addHTML = () => {
|
|||
overflow-y:auto;
|
||||
max-height:600px;
|
||||
}
|
||||
.js-snackbar__message h1,h2,h3{
|
||||
font-size:22px;
|
||||
}
|
||||
.xrf table tr td {
|
||||
|
||||
}
|
||||
|
|
@ -673,6 +657,9 @@ window.XRFMENU.addHTML = () => {
|
|||
padding-bottom:149px;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.footer .menu{
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="overlay" class="xrf" style="display:none">
|
||||
|
|
@ -684,7 +671,7 @@ window.XRFMENU.addHTML = () => {
|
|||
</div>
|
||||
<!-- open AFRAME inspector: $('a-scene').components.inspector.openInspector() -->
|
||||
<div class="xrf footer">
|
||||
<div>
|
||||
<div id="buttons" class="menu">
|
||||
${window.XRFMENU.html.map( (html) => typeof html == "function" ? html() : html ).join('\n')}
|
||||
<a class="btn" id="more" style="display:inline-block">${window.XRFMENU.morelabel}</a>
|
||||
</div>
|
||||
|
|
@ -695,7 +682,7 @@ window.XRFMENU.addHTML = () => {
|
|||
if( XRFMENU.logo ) $('.logo').style['background-image'] = `url(${XRFMENU.logo})`
|
||||
|
||||
window.notify = XRFMENU.notify(window)
|
||||
window.embed = XRFMENU.embed
|
||||
window.share = XRFMENU.share
|
||||
window.download = XRFMENU.download
|
||||
window.notify('loading '+document.location.search.substr(1))
|
||||
// reroute console messages to snackbar notifications
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue