2024-01-03 15:23:34 +01:00
( function ( ) {
2024-03-19 10:53:22 +01:00
// a portable snackbar
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
window . SnackBar = function ( 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 ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
function _Create ( ) {
_Container = document . querySelector ( ".js-snackbar-container" )
if ( _Container ) {
_Container . remove ( )
}
_Container = null
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
if ( ! _Container ) {
// need to create a new container for notifications
_Container = document . createElement ( "div" ) ;
_Container . classList . add ( "js-snackbar-container" ) ;
2024-01-29 21:19:04 +01:00
2024-03-19 10:53:22 +01:00
document . body . appendChild ( _Container ) ;
}
_Container . opts = _Options
_Container . innerHTML = ''
_Element = document . createElement ( "div" ) ;
_Element . classList . add ( "js-snackbar__wrapper" , "xrf" ) ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
let innerSnack = document . createElement ( "div" ) ;
innerSnack . classList . add ( "js-snackbar" , "js-snackbar--show" ) ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
if ( _Options . status ) {
_Options . status = _Options . status . toLowerCase ( ) . trim ( ) ;
2024-01-29 21:19:04 +01:00
2024-03-19 10:53:22 +01:00
let status = document . createElement ( "span" ) ;
status . classList . add ( "js-snackbar__status" ) ;
2024-01-29 21:19:04 +01:00
2024-03-19 10:53:22 +01:00
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" ) ;
}
2024-01-29 21:19:04 +01:00
2024-03-19 10:53:22 +01:00
innerSnack . appendChild ( status ) ;
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
_Message = document . createElement ( "span" ) ;
_Message . classList . add ( "js-snackbar__message" ) ;
if ( typeof _Options . message == 'string' ) {
_Message . innerHTML = _Options . message ;
} else _Message . appendChild ( _Options . message )
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
innerSnack . appendChild ( _Message ) ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
if ( _Options . dismissible ) {
let closeBtn = document . createElement ( "span" ) ;
closeBtn . classList . add ( "js-snackbar__close" ) ;
closeBtn . innerText = "\u00D7" ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
closeBtn . onclick = snackbar . Close ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
innerSnack . appendChild ( closeBtn ) ;
}
2024-01-29 21:19:04 +01:00
2024-03-19 10:53:22 +01:00
_Element . style . height = "0px" ;
_Element . style . opacity = "0" ;
_Element . style . marginTop = "0px" ;
_Element . style . marginBottom = "0px" ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
_Element . appendChild ( innerSnack ) ;
_Container . appendChild ( _Element ) ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
if ( _Options . timeout !== false ) {
_Interval = setTimeout ( snackbar . Close , _Options . timeout ) ;
}
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
snackbar . Open = function ( ) {
let contentHeight = _Element . firstElementChild . scrollHeight ; // get the height of the content
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
_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 ;
} )
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
snackbar . Close = function ( ) {
if ( _Interval )
clearInterval ( _Interval ) ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
let snackbarHeight = _Element . scrollHeight ; // get the auto height as a px value
let snackbarTransitions = _Element . style . transition ;
_Element . style . transition = "" ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
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
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
requestAnimationFrame ( function ( ) {
_Element . style . height = "0px" ;
_Element . style . opacity = 0 ;
} )
} ) ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
setTimeout ( function ( ) {
try {
_Container . removeChild ( _Element ) ;
} catch ( e ) { }
} , 1000 ) ;
if ( _Options . onclose ) _Options . onclose ( )
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
} ;
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
_Options = { ... _OptionDefaults , ... userOptions }
_Create ( ) ;
if ( userOptions ) snackbar . Open ( ) ;
}
2024-01-29 21:19:04 +01:00
2024-03-19 10:53:22 +01:00
document . head . innerHTML += `
< style type = "text/css" >
2024-01-29 21:19:04 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar - container . btn ,
. js - snackbar - container input [ type = submit ] ,
. js - snackbar - container button {
margin - bottom : 15 px ;
}
. js - snackbar - container {
position : absolute ;
top : 10 px ;
left : 0 px ;
display : flex ;
align - items : center ;
2024-07-12 19:25:10 +02:00
width : 100 % ;
2024-03-19 10:53:22 +01:00
max - width : 100 % ;
2024-07-12 19:25:10 +02:00
max - height : 33 vh ;
2024-03-19 10:53:22 +01:00
padding : 10 px ;
z - index : 1001 ;
2024-07-12 19:25:10 +02:00
justify - content : center ;
2024-03-19 10:53:22 +01:00
overflow : hidden ;
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
. js - snackbar - container * {
box - sizing : border - box ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _wrapper {
-- color - c : # 555 ;
-- color - a : # FFF ;
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
. js - snackbar _ _wrapper {
transition : 1 s ;
overflow : hidden ;
height : auto ;
margin : 5 px 0 ;
transition : all ease . 5 s ;
border - radius : 15 px ;
box - shadow : 0 0 4 px 0 var ( -- xrf - box - shadow ) ;
right : 20 px ;
position : fixed ;
2024-06-04 19:00:48 +02:00
max - width : 500 px ;
2024-03-19 10:53:22 +01:00
top : 18 px ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar {
display : inline - flex ;
box - sizing : border - box ;
border - radius : 3 px ;
color : var ( -- color - c ) ;
background - color : var ( -- color - a ) ;
vertical - align : bottom ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _close ,
. js - snackbar _ _status ,
. js - snackbar _ _message {
2024-01-03 15:23:34 +01:00
position : relative ;
2024-03-19 10:53:22 +01:00
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _message {
margin : 12 px ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _status {
display : none ;
width : 15 px ;
margin - right : 5 px ;
border - radius : 3 px 0 0 3 px ;
background - color : transparent ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. 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 ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _status . js - snackbar -- success {
background - color : # 4 caf50 ;
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
. js - snackbar _ _status . js - snackbar -- warning {
background - color : # ff9800 ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _status . js - snackbar -- danger {
background - color : # ff6060 ;
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
. js - snackbar _ _status . js - snackbar -- info {
background - color : # CCC ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _close {
cursor : pointer ;
display : flex ;
align - items : top ;
padding : 8 px 13 px 0 px 0 px ;
user - select : none ;
}
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
. js - snackbar _ _close : hover {
background - color : # 4443 ;
}
< / s t y l e >
`
2024-01-03 15:23:34 +01:00
window . accessibility = ( opts ) => new Proxy ( {
opts ,
enabled : false ,
// features
2024-04-25 17:57:06 +02:00
speak _teleports : true ,
speak _keyboard : false ,
2024-01-03 15:23:34 +01:00
// audio settings
speak _rate : 1 ,
speak _pitch : 1 ,
speak _volume : 1 ,
speak _voice : - 1 ,
2024-04-25 17:57:06 +02:00
speak _voices : 0 ,
2024-01-03 15:23:34 +01:00
toggle ( ) { this . enabled = ! this . enabled } ,
settings ( ) {
this . toggle ( ) // *TODO* should show settings screen
} ,
speak ( str , opts ) {
opts = opts || { speaksigns : true }
if ( ! this . enabled || ! str ) return
if ( opts . speaksigns ) {
str = str . replace ( /\/\// , ' ' )
. replace ( /:/ , '' )
. replace ( /\// , ' slash ' )
. replace ( /\./ , ' dot ' )
. replace ( /#/ , ' hash ' )
. replace ( /&/ , ' and ' )
. replace ( /=/ , ' is ' )
}
2024-06-17 14:42:42 +02:00
if ( str == this . speak . lastStr ) return // no duplicates
this . speak . lastStr = str
2024-01-03 15:23:34 +01:00
let speech = window . speechSynthesis
let utterance = new SpeechSynthesisUtterance ( str )
2024-04-25 17:57:06 +02:00
this . speak _voices = speech . getVoices ( ) . length
if ( this . speak _voice != - 1 && this . speak _voice < this . speak _voices ) {
utterance . voice = speech . getVoices ( ) [ this . speak _voice ] ;
} else {
2024-01-03 15:23:34 +01:00
let voices = speech . getVoices ( )
for ( let i = 0 ; i < voices . length ; i ++ ) {
if ( voices [ i ] . lang == navigator . lang ) this . speak _voice = i ;
}
}
utterance . rate = this . speak _rate
utterance . pitch = this . speak _pitch
utterance . volume = this . speak _volume
if ( opts . override ) speech . cancel ( )
speech . speak ( utterance )
} ,
init ( ) {
2024-06-17 14:42:42 +02:00
this
. speakArrowKeys ( )
. setupListeners ( )
. setupPersistance ( )
. setupHrefCycling ( )
. setupSpeechKillOnEscape ( )
setTimeout ( ( ) => this . initCommands ( ) , 200 )
} ,
speakArrowKeys ( ) {
// speak arrow keys
2024-01-03 15:23:34 +01:00
window . addEventListener ( 'keydown' , ( e ) => {
if ( ! this . speak _keyboard ) return
let k = e . key
switch ( k ) {
case "ArrowUp" : k = "forward" ; break ;
case "ArrowLeft" : k = "left" ; break ;
case "ArrowRight" : k = "right" ; break ;
case "ArrowDown" : k = "backward" ; break ;
}
this . speak ( k , { override : true } )
} )
2024-06-17 14:42:42 +02:00
return this
} ,
setupSpeechKillOnEscape ( ) {
window . addEventListener ( 'keydown' , ( e ) => {
if ( e . key == "Escape" ) {
this . speak ( "stop" , { override : true } )
}
} )
} ,
setupHrefCycling ( ) {
// speak arrow keys
window . addEventListener ( 'keydown' , ( e ) => {
2024-06-17 15:53:47 +02:00
if ( e . key != "Tab" && e . key != "Enter" ) return
2024-06-17 14:42:42 +02:00
let subScene = xrf . scene . getObjectByName ( xrf . frag . pos . last )
if ( ! subScene ) subScene = xrf . scene
2024-06-17 15:53:47 +02:00
let cache = this . setupHrefCycling . cache = this . setupHrefCycling . cache || { current : - 1 }
2024-06-17 14:42:42 +02:00
let objects = [ ]
subScene . traverse ( ( n ) => ( n . userData . href || n . userData [ 'aria-description' ] ) && objects . push ( n ) )
const highlight = ( n ) => {
if ( this . helper ) {
if ( this . helper . selected == n . uuid ) return // already selected
xrf . scene . remove ( this . helper )
}
this . selected = n
this . helper = new THREE . BoxHelper ( n , 0xFF00FF )
this . helper . computeLineDistances ( )
this . helper . material . linewidth = 8
this . helper . material . color = xrf . focusLine . material . color
this . helper . material . dashSize = xrf . focusLine . material . dashSize
this . helper . material . gapSize = xrf . focusLine . material . gapSize
this . helper . selected = n . uuid
xrf . scene . add ( this . helper )
2024-06-18 12:00:14 +02:00
notify ( ` ${ n . userData [ 'aria-description' ] || '' } ` + ( n . userData . href ? ` <br><b>name:</b> ${ n . name } <br><b>href:</b> ${ n . userData [ 'href' ] } ` : '' ) )
2024-06-17 14:42:42 +02:00
}
2024-06-17 15:53:47 +02:00
if ( e . key == 'Enter' && objects [ cache . current ] . userData . href ) {
xrf . navigator . to ( objects [ cache . current ] . userData . href )
}
2024-06-17 14:42:42 +02:00
// increment to next
2024-06-17 15:53:47 +02:00
cache . current = ( cache . current + 1 ) % objects . length
if ( e . key == 'Tab' ) {
highlight ( objects [ cache . current ] )
}
2024-06-17 14:42:42 +02:00
e . preventDefault ( )
return false
} )
return this
} ,
setupListeners ( ) {
2024-01-03 15:23:34 +01:00
document . addEventListener ( '$menu:buttons:render' , ( e ) => {
let $ = e . detail
let a = [ ... $ . querySelectorAll ( 'a' ) ]
// make sure anchor buttons are accessible by tabbing to them
a . map ( ( btn ) => {
if ( ! btn . href ) btn . setAttribute ( "href" , "javascript:void(0)" ) // important!
btn . setAttribute ( "aria-label" , "button" )
} )
document . addEventListener ( 'mouseover' , ( e ) => {
if ( e . target . getAttribute ( "aria-title" ) ) {
let lines = [ ]
lines . push ( e . target . getAttribute ( "aria-title" ) )
lines . push ( e . target . getAttribute ( "aria-description" ) )
lines = lines . filter ( ( l ) => l )
this . speak ( lines . join ( "." ) , { override : true , speaksigns : false } )
}
} )
2024-04-25 17:57:06 +02:00
document . addEventListener ( '$chat.send' , ( opts ) => {
if ( opts . detail . message ) this . speak ( opts . detail . message )
} )
2024-01-03 15:23:34 +01:00
} )
document . addEventListener ( 'network.send' , ( e ) => {
let opts = e . detail
opts . message = opts . message || ''
this . speak ( opts . message )
} )
opts . xrf . addEventListener ( 'pos' , ( opts ) => {
2024-04-25 17:57:06 +02:00
if ( this . enabled && this . speak _teleports ) {
2024-01-29 21:19:04 +01:00
network . send ( { message : this . posToMessage ( opts ) , class : [ "info" , "guide" ] } )
}
if ( opts . frag . pos . string . match ( /,/ ) ) {
network . pos = opts . frag . pos . string
} else {
network . posName = opts . frag . pos . string
2024-01-03 15:23:34 +01:00
}
} )
2024-06-17 14:42:42 +02:00
return this
} ,
2024-01-03 15:23:34 +01:00
2024-06-17 14:42:42 +02:00
setupPersistance ( ) {
2024-04-25 17:57:06 +02:00
// auto-enable if previously enabled
2024-06-15 17:33:08 +02:00
if ( window . localStorage . getItem ( "accessibility" ) === 'true' || xrf . navigator . URI . XRF . accessible ) {
2024-04-25 17:57:06 +02:00
setTimeout ( ( ) => {
this . enabled = true
this . setFontSize ( )
} , 100 )
}
2024-06-17 14:42:42 +02:00
return this
2024-04-25 17:57:06 +02:00
} ,
initCommands ( ) {
document . addEventListener ( 'chat.command.help' , ( e ) => {
2024-06-17 14:42:42 +02:00
e . detail . message += ` <br><b class="badge"><Escape></b> silence TTS `
e . detail . message += ` <br><b class="badge"><Tab></b> cycle [href] buttons / silence TTS `
2024-04-25 17:57:06 +02:00
e . detail . message += ` <br><b class="badge">/fontsize <number></b> set fontsize (default=14) `
} )
document . addEventListener ( 'chat.command' , ( e ) => {
if ( e . detail . message . match ( /^fontsize/ ) ) {
try {
let fontsize = parseInt ( e . detail . message . replace ( /^fontsize / , '' ) . trim ( ) )
if ( fontsize == NaN ) throw 'not a number'
this . setFontSize ( fontsize )
$chat . send ( { message : 'fontsize set to ' + fontsize } )
} catch ( e ) {
console . error ( e )
$chat . send ( { message : 'example usage: /fontsize 20' } )
}
}
} )
} ,
setFontSize ( size ) {
if ( size ) {
window . localStorage . setItem ( "fontsize" , size )
} else size = window . localStorage . getItem ( "fontsize" )
if ( ! size ) return
document . head . innerHTML += `
< style type = "text/css" >
. accessibility # messages * {
font - size : $ { size } px ! important ;
line - height : $ { size * 2 } px ! important ;
}
< / s t y l e >
`
$messages = document . querySelector ( '#messages' )
setTimeout ( ( ) => $messages . scrollTop = $messages . scrollHeight , 1000 )
2024-01-03 15:23:34 +01:00
} ,
posToMessage ( opts ) {
let obj
let description
let msg = "teleported to "
let pos = opts . frag . pos
if ( pos . string . match ( ',' ) ) msg += ` coordinates <a href="#pos= ${ pos . string } "> ${ pos . string } </a> `
else {
msg += ` location <a href="#pos= ${ pos . string } "> ${ pos . string } </a> `
obj = opts . scene . getObjectByName ( pos . string )
if ( obj ) {
description = obj . userData [ 'aria-label' ] || ''
} else msg += ", but the teleportation was refused because it cannot be found within this world"
}
return msg
} ,
sanitizeTranscript ( ) {
return $chat . $messages . innerText
. replaceAll ( "<[^>]*>" , "" ) // strip html
. split ( '\n' )
. map ( ( l ) => String ( l + '.' ) . replace ( /(^|:|;|!|\?|\.)\.$/g , '\$1' ) ) // add dot if needed
. join ( '\n' )
}
} ,
{
// auto-trigger events on changes
get ( data , k , receiver ) { return data [ k ] } ,
set ( data , k , v ) {
data [ k ] = v
switch ( k ) {
case "enabled" : {
2024-04-25 17:57:06 +02:00
let message = "accessibility mode has been " + ( v ? "activated" : "disabled" ) + ".<br>Type /help for help."
2024-01-03 15:23:34 +01:00
$ ( '#accessibility.btn' ) . style . filter = v ? 'brightness(1.0)' : 'brightness(0.5)'
if ( v ) $chat . visible = true
2024-04-25 17:57:06 +02:00
$chat . send ( { message , class : [ 'info' ] } )
2024-01-03 15:23:34 +01:00
data . enabled = true
data . speak ( message )
data . enabled = v
2024-04-25 17:57:06 +02:00
window . localStorage . setItem ( "accessibility" , v )
2024-01-03 15:23:34 +01:00
$chat . $messages . classList [ v ? 'add' : 'remove' ] ( 'guide' )
2024-04-25 17:57:06 +02:00
document . body . classList . toggle ( [ 'accessibility' ] )
2024-01-03 15:23:34 +01:00
if ( ! data . readTranscript && ( data . readTranscript = true ) ) {
data . speak ( data . sanitizeTranscript ( ) )
}
}
}
}
} )
document . addEventListener ( '$menu:ready' , ( e ) => {
try {
accessibility = accessibility ( e . detail )
accessibility . init ( )
document . dispatchEvent ( new CustomEvent ( "accessibility:ready" , e ) )
$menu . buttons = $menu . buttons . concat ( [ ` <a class="btn" style="background:var(--xrf-dark-gray);filter: brightness(0.5);" aria-label="button" aria-description="enable all accessibility features" id="accessibility" onclick="accessibility.settings()"><i class="gg-yinyang"></i>accessibility</a><br> ` ] )
} catch ( e ) { console . error ( e ) }
} )
2024-04-25 17:57:06 +02:00
document . querySelector ( 'head' ) . innerHTML += `
< style type = "text/css" >
. accessibility # messages * {
2024-06-15 17:33:08 +02:00
font - size : 20 px ! important ;
line - height : 35 px ;
2024-04-25 17:57:06 +02:00
}
. accessibility # messages . msg . self {
background : var ( -- xrf - gray ) ;
color : # FFF ;
}
. accessibility # messages . msg . info ,
. accessibility # messages . msg . self {
line - height : unset ;
padding - top : 15 px ;
padding - bottom : 15 px ;
}
. accessibility # chatbar {
display : block ! important ;
}
. accessibility # chatsend {
display : block ! important ;
}
. accessibility # chatline {
text - indent : 25 px ;
}
< / s t y l e >
`
2024-03-19 10:53:22 +01:00
// reactive component for displaying the menu
menuComponent = ( el ) => new Proxy ( {
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
html : `
< div class = "xrf footer" >
< div class = "menu" >
< div id = "buttons" > < / d i v >
< a class = "btn" id = "more" aria - title = "menu button" > < i id = "icon" class = "gg-menu" > < / i > < / a > < b r >
< / d i v >
< / d i v >
` ,
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
collapsed : false ,
logo : './../../assets/logo.png' ,
buttons : [ ` <a class="btn" aria-label="button" aria-title="share button" aria-description="this allows embedding and sharing of this URL or make a screenshot of it" id="share" onclick="frontend.share()"><i class="gg-link"></i> share</a><br> ` ] ,
$buttons : $buttons = el . querySelector ( '#buttons' ) ,
$btnMore : $btnMore = el . querySelector ( '#more' ) ,
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
toggle ( state ) {
this . collapsed = state !== undefined ? state : ! this . collapsed
el . querySelector ( "i#icon" ) . className = this . collapsed ? 'gg-close' : 'gg-menu'
document . body . classList [ this . collapsed ? 'add' : 'remove' ] ( [ 'menu' ] )
} ,
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
init ( opts ) {
el . innerHTML = this . html
document . body . appendChild ( el ) ;
( [ 'click' ] ) . map ( ( e ) => el . addEventListener ( e , ( ev ) => this [ e ] && this [ e ] ( ev . target . id , ev ) ) )
setTimeout ( ( ) => {
document . dispatchEvent ( new CustomEvent ( "$menu:ready" , { detail : { $menu : this , xrf } } ) )
} , 100 )
return this
} ,
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
click ( id , e ) {
switch ( id ) {
case "icon" :
2024-07-12 19:25:10 +02:00
case "more" : return this . toggle ( ) ; break ;
2024-01-03 15:23:34 +01:00
}
2024-07-12 19:25:10 +02:00
this . toggle ( false )
2024-01-03 15:23:34 +01:00
}
2024-03-19 10:53:22 +01:00
} ,
{
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
get ( me , k , v ) { return me [ k ] } ,
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
set ( me , k , v ) {
me [ k ] = v
switch ( k ) {
case "buttons" : el . querySelector ( "#buttons" ) . innerHTML = this . renderButtons ( me ) ;
document . dispatchEvent ( new CustomEvent ( "$menu:buttons:render" , { detail : el . querySelector ( '.menu' ) } ) )
break ;
case "collapsed" :
el . querySelector ( "#buttons" ) . style . display = me . collapsed ? 'block' : 'none'
frontend . emit ( '$menu:collapse' , v )
break ;
}
} ,
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
renderButtons : ( data ) => ` ${ data . buttons . join ( '' ) } `
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
} )
2024-01-03 15:23:34 +01:00
2024-03-19 10:53:22 +01:00
// reactify component!
document . addEventListener ( 'frontend:ready' , ( e ) => {
window . $menu = menuComponent ( document . createElement ( 'div' ) ) . init ( e . detail )
} )
2024-01-03 15:23:34 +01:00
// 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 : `
< div id = "topbar" class = "xrf" >
< div class = "logo" > < / d i v >
< button id = "navback" onclick = "history.back()" > & # 8249 ; < / b u t t o n >
< button id = "navforward" onclick = "history.forward()" > & # 8250 ; < / b u t t o n >
< input id = "load" type = "submit" value = "load 3D file" > < / i n p u t >
2024-02-29 15:07:20 +01:00
< input type = "text" id = "uri" value = "" onchange = "AFRAME.XRF.navigator.to( $('#uri').value )" style = "display:none" / >
2024-01-03 15:23:34 +01:00
< / d i v >
` ,
el : null ,
2024-06-17 14:42:42 +02:00
notify _links : true ,
2024-01-03 15:23:34 +01:00
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 ( )
2024-01-29 21:19:04 +01:00
. setupNetworkListeners ( )
2024-01-03 15:23:34 +01:00
. hidetopbarWhenMenuCollapse ( )
2024-01-29 21:19:04 +01:00
. hideUIWhenNavigating ( )
2024-01-03 15:23:34 +01:00
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 ) )
2024-02-29 14:36:00 +01:00
setTimeout ( ( ) => {
2024-03-01 14:35:36 +01:00
let instructions = AFRAME . utils . device . isMobile ( )
? "hold 2-3 fingers to move forward/backward"
2024-06-17 14:42:42 +02:00
: "use W A S D keys and mouse-drag to move around"
2024-03-01 14:35:36 +01:00
window . notify ( instructions , { timeout : false } )
2024-04-25 17:57:06 +02:00
xrf . addEventListener ( 'pos' , ( opts ) => {
let pos = opts . frag . pos . string
window . notify ( '<b class="badge">teleporting</b> to <b>' + pos + "</b><br>use back/forward (browserbutton) to undo" )
2024-03-01 14:35:36 +01:00
} ) // close dialogs when url changes
2024-02-29 14:36:00 +01:00
} , 2000 )
xrf . addEventListener ( 'href' , ( data ) => {
2024-02-29 15:07:20 +01:00
if ( ! data . selected ) return
2024-02-29 14:36:00 +01:00
2024-07-12 19:25:10 +02:00
2024-06-17 14:42:42 +02:00
let html = this . notify _links ? ` <b class="badge"> ${ data . mesh . isSRC && ! data . mesh . portal ? 'src' : 'href' } </b> ${ data . xrf ? data . xrf . string : data . mesh . userData . src } <br> ` : ''
2024-02-29 15:07:20 +01:00
let metadata = data . mesh . userData
2024-02-29 14:36:00 +01:00
let meta = xrf . Parser . getMetaData ( )
let hasMeta = false
for ( let label in meta ) {
let fields = meta [ label ]
for ( let i = 0 ; i < fields . length ; i ++ ) {
let field = fields [ i ]
if ( metadata [ field ] ) {
hasMeta = true
html += ` <br><b style="min-width:110px;display:inline-block"> ${ label } :</b> ${ metadata [ field ] } \n `
break
}
}
}
let root = data . mesh . portal ? data . mesh . portal . stencilObject : data . mesh
2024-04-25 17:57:06 +02:00
let transcript = xrf . sceneToTranscript ( root , data . mesh )
2024-02-29 14:36:00 +01:00
if ( transcript . length ) html += ` <br><b>transcript:</b><br><div class="transcript"> ${ transcript } </div> `
2024-06-15 17:33:08 +02:00
if ( hasMeta && ! data . mesh . portal && metadata . XRF . src ) html += ` <br><br><a class="btn" style="float:right" onclick="xrf.navigator.to(' ${ data . mesh . userData . href } ')">Visit embedded scene</a> `
2024-06-17 14:42:42 +02:00
if ( ! html ) return
2024-07-12 19:25:10 +02:00
2024-02-29 14:36:00 +01:00
window . notify ( html , { timeout : 7000 * ( hasMeta ? 1.5 : 1 ) } )
} )
2024-01-03 15:23:34 +01:00
} , 100 )
return this
} ,
2024-01-29 21:19:04 +01:00
setupNetworkListeners ( ) {
document . addEventListener ( 'network.connect' , ( e ) => {
2024-02-29 15:07:20 +01:00
window . notify ( "🪐 connecting to awesomeness.." )
2024-01-29 21:19:04 +01:00
$chat . send ( { message : ` 🪐 connecting to awesomeness.. ` , class : [ 'info' ] , timeout : 5000 } )
} )
document . addEventListener ( 'network.connected' , ( e ) => {
2024-02-29 15:07:20 +01:00
window . notify ( "🪐 connected to awesomeness.." )
2024-01-29 21:19:04 +01:00
$chat . visibleChatbar = true
$chat . send ( { message : ` 🎉 ${ e . detail . plugin . profile . name || '' } connected! ` , class : [ 'info' ] , timeout : 5000 } )
} )
document . addEventListener ( 'network.disconnect' , ( ) => {
2024-02-29 15:07:20 +01:00
window . notify ( "🪐 disconnecting.." )
2024-01-29 21:19:04 +01:00
} )
document . addEventListener ( 'network.info' , ( e ) => {
window . notify ( e . detail . message )
$chat . send ( { ... e . detail , class : [ 'info' ] , timeout : 5000 } )
} )
document . addEventListener ( 'network.error' , ( e ) => {
window . notify ( e . detail . message )
$chat . send ( { ... e . detail , class : [ 'info' ] , timeout : 5000 } )
} )
return this
} ,
2024-01-03 15:23:34 +01:00
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
} ,
2024-01-29 21:19:04 +01:00
hideUIWhenNavigating ( ) {
// hide ui when user is navigating the scene using mouse/touch
let showUI = ( show ) => ( e ) => {
let isChatMsg = e . target . closest ( '.msg' )
let isChatLine = e . target . id == 'chatline'
let isChatEmptySpace = e . target . id == 'messages'
2024-03-19 10:53:22 +01:00
let isUI = e . target . closest ( '.ui' ) ||
e . target . closest ( '.btn' ) ||
e . target . closest ( 'button' ) ||
e . target . closest ( 'textarea' ) ||
e . target . closest ( 'input' ) ||
e . target . closest ( 'a' )
2024-01-29 21:19:04 +01:00
//console.dir({class: e.target.className, id: e.target.id, isChatMsg,isChatLine,isChatEmptySpace,isUI, tagName: e.target.tagName})
2024-03-19 10:53:22 +01:00
if ( isUI ) return
2024-01-29 21:19:04 +01:00
if ( show ) {
2024-06-11 19:30:32 +02:00
if ( typeof $chat != 'undefined' ) $chat . visible = true
2024-01-29 21:19:04 +01:00
} else {
2024-06-11 19:30:32 +02:00
if ( typeof $chat != 'undefined' ) $chat . visible = false
2024-01-29 21:19:04 +01:00
$menu . toggle ( false )
}
return true
}
document . addEventListener ( 'mousedown' , showUI ( false ) )
document . addEventListener ( 'mouseup' , showUI ( true ) )
document . addEventListener ( 'touchstart' , showUI ( false ) )
document . addEventListener ( 'touchend' , showUI ( true ) )
} ,
2024-01-03 15:23:34 +01:00
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 ) ;
2024-02-29 15:07:20 +01:00
let file = files . slice ? files [ 0 ] : files
2024-01-03 15:23:34 +01:00
for ( var i in contentLoaders ) {
let r = new RegExp ( '\\' + i + '$' )
2024-06-11 19:30:32 +02:00
if ( file . name . match ( r ) ) {
xrf . navigator . URI . file = '' // bypass cached file (easy refresh same file for testing)
return contentLoaders [ i ] ( file )
}
2024-01-03 15:23:34 +01:00
}
alert ( file . name + " is not supported" )
} ;
input . click ( ) ;
}
} ,
notify ( _str , opts ) {
if ( window . accessibility && window . accessibility . enabled ) return $chat . send ( { message : _str , class : [ 'info' ] } )
2024-02-29 15:07:20 +01:00
opts = opts || { status : 'info' }
2024-01-03 15:23:34 +01:00
opts = Object . assign ( { status , timeout : 4000 } , opts )
opts . message = _str
if ( typeof str == 'string' ) {
2024-02-29 15:07:20 +01:00
str = _str . replace ( /(^\w+):/ , "<div class='badge'>\$1</div>" )
if ( ! opts . status ) {
2024-01-03 15:23:34 +01:00
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 ( ) {
2024-03-19 10:53:22 +01:00
function download ( dataurl , filename ) {
2024-01-03 15:23:34 +01:00
var a = document . createElement ( "a" ) ;
2024-03-19 10:53:22 +01:00
a . href = URL . createObjectURL ( new Blob ( [ dataurl ] ) ) ;
2024-01-03 15:23:34 +01:00
a . setAttribute ( "download" , filename ) ;
a . click ( ) ;
return false ;
}
2024-03-19 10:53:22 +01:00
function exportScene ( model , ext , file ) {
document . dispatchEvent ( new CustomEvent ( 'frontend.export' , { detail : { scene : model . scene , ext } } ) )
xrf . emit ( 'export' , { scene : model . scene , ext } )
. then ( ( ) => {
// setup exporters
let defaultExporter = THREE . GLTFExporter
xrf . loaders [ 'gltf' ] . exporter = defaultExporter
xrf . loaders [ 'glb' ] . exporter = defaultExporter
const exporter = new THREE . GLTFExporter ( )
exporter . parse (
model . scene ,
function ( glb ) { download ( glb , ` ${ file } ` ) } , // ready
function ( error ) { console . error ( error ) } , // error
{
binary : true ,
onlyVisible : false ,
animations : model . animations ,
includeCustomExtensions : true ,
trs : true
}
) ;
} )
}
// load original scene and overwrite with updates
let url = document . location . search . replace ( /\?/ , '' )
2024-04-16 15:19:08 +02:00
let { urlObj , dir , file , hash , ext } = xrf . navigator . origin = xrf . URI . parse ( url )
2024-03-19 10:53:22 +01:00
const Loader = xrf . loaders [ ext ]
loader = new Loader ( ) . setPath ( dir )
notify ( 'exporting scene<br><br>please wait..' )
loader . load ( url , ( model ) => {
exportScene ( model , ext , file )
} )
2024-01-03 15:23:34 +01:00
} ,
updateHashPosition ( randomize ) {
2024-04-16 15:19:08 +02:00
const pos = xrf . frag . pos . get ( )
2024-06-26 14:42:00 +02:00
xrf . navigator . updateHash . active = false // prevent teleport
2024-04-16 15:19:08 +02:00
xrf . navigator . URI . hash . pos = ` ${ pos . x } , ${ pos . y } , ${ pos . z } `
2024-06-26 14:42:00 +02:00
document . location . hash = ` # ${ xrf . navigator . URI . fragment } `
xrf . navigator . updateHash . active = true
return document . location . href
2024-01-03 15:23:34 +01:00
} ,
copyToClipboard ( text ) {
2024-02-29 15:07:20 +01:00
// copy url to clipboard
2024-01-03 15:23:34 +01:00
var dummy = document . createElement ( 'input' )
document . body . appendChild ( dummy ) ;
dummy . value = text ;
dummy . select ( ) ;
document . execCommand ( 'copy' ) ;
2024-02-29 15:07:20 +01:00
document . body . removeChild ( dummy ) ;
2024-01-03 15:23:34 +01:00
} ,
share ( opts ) {
2024-01-29 21:19:04 +01:00
opts = opts || { notify : true , qr : true , share : true , linkonly : false }
2024-04-16 18:44:55 +02:00
if ( network . meetingLink && ! xrf . navigator . URI . hash . meet ) {
xrf . navigator . URI . hash . meet = network . meetingLink
2024-01-29 21:19:04 +01:00
}
2024-06-26 14:42:00 +02:00
let url = frontend . updateHashPosition ( )
console . log ( url )
2024-02-29 15:07:20 +01:00
if ( opts . linkonly ) return url
2024-01-03 15:23:34 +01:00
this . copyToClipboard ( url )
2024-02-29 15:07:20 +01:00
// End of *TODO*
2024-01-03 15:23:34 +01:00
if ( opts . notify ) {
2024-01-29 21:19:04 +01:00
window . notify ( ` <h2> ${ network . connected ? 'Meeting link ' : 'Link' } copied to clipboard!</h2>
Now share it with your friends ❤ ️ < br >
2024-01-03 15:23:34 +01:00
< canvas id = "qrcode" width = "121" height = "121" > < / c a n v a s > < b r >
< button onclick = "frontend.download()" > < i class = "gg-software-download" > < / i > & n b s p ; & n b s p ; & n b s p ; d o w n l o a d s c e n e f i l e < / b u t t o n > < b r >
< button onclick = "alert('this might take a while'); $('a-scene').components.screenshot.capture('equirectangular')" > < i class = "gg-image" > < / i > & n b s p ; & n b s p ; d o w n l o a d 3 6 0 s c r e e n s h o t < / b u t t o n > < b r >
< a class = "btn" target = "_blank" href = "https://github.com/coderofsalvation/xrfragment-helloworld" > < i class = "gg-serverless" > < / i > & n b s p ; & n b s p ; & n b s p ; c l o n e & s e l f h o s t t h i s e x p e r i e n c e < / a > < b r >
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}'></iframe>" id = "share" / >
< br >
2024-01-29 21:19:04 +01:00
< br >
2024-01-03 15:23:34 +01:00
` ,{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'
} )
}
2024-02-29 15:07:20 +01:00
$menu . collapse = true
2024-01-03 15:23:34 +01:00
}
} ,
2024-02-29 15:07:20 +01:00
{
// auto-trigger events on changes
2024-01-03 15:23:34 +01:00
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 ;
}
}
} )
2024-02-29 15:07:20 +01:00
2024-01-03 15:23:34 +01:00
frontend = frontend ( { xrf , document } ) . init ( )
2024-04-25 17:57:06 +02:00
// this allows surfing to a href by typing its node-name
// help screen
document . addEventListener ( 'chat.command.help' , ( e ) => {
e . detail . message += `
< br > < b class = "badge" > & lt ; destinationname & gt ; < / b > s u r f t o a d e s t i n a t i o n
`
} )
document . addEventListener ( 'chat.input' , ( e ) => {
let name = e . detail . message . trim ( )
xrf . scene . traverse ( ( n ) => {
if ( n . userData && n . userData . href && n . userData . href . match ( /pos=/ ) && n . name == name ) {
$chat . send ( { message : '<b class="badge">activating</b> ' + n . name , class : [ 'self' , 'info' ] } )
xrf . navigator . to ( n . userData . href )
}
} )
} )
// this allows a more-or-less MUD type interface
//
//
// help screen
document . addEventListener ( 'chat.command.help' , ( e ) => {
e . detail . message += `
< br > < b class = "badge" > ? < / b > h e l p s c r e e n
< br > < b class = "badge" > look < / b > v i e w s c e n e a n d d e s t i n a t i o n s
< br > < b class = "badge" > go [ left | right | forward | destination ] < / b > s u r f [ t o d e s t i n a t i o n ]
< br > < b class = "badge" > do [ action ] < / b > l i s t [ o r p e r f o r m ] a c t i o n ( s )
< br > < b class = "badge" > rotate & lt ; left | right | up | down & gt ; < / b > r o t a t e c a m e r a
< br > < b class = "badge" > back < / b > g o t o p r e v i o u s p o r t a l / l i n k
< br > < b class = "badge" > forward < / b > g o t o p r e v i o u s p o r t a l / l i n k
< br > < b class = "badge" > # ... . < / b > e x e c u t e X R F r a g m e n t s
< hr / >
`
} )
const listExits = ( scene ) => {
let message = ''
let destinations = { }
scene . traverse ( ( n ) => {
if ( n . userData && n . userData . href && n . userData . href . match ( /pos=/ ) ) {
destinations [ n . name ] = n . userData [ 'aria-label' ] || n . userData . href
}
} )
for ( let destination in destinations ) {
message += ` <br><b class="badge"> ${ destination } </b> ${ destinations [ destination ] } `
}
if ( ! message ) message += '<br>type <b class="badge">back</b> to go back'
return message
}
const listActions = ( scene ) => {
let message = ''
let destinations = { }
scene . traverse ( ( n ) => {
if ( n . userData && n . userData . href && ! n . userData . href . match ( /pos=/ ) ) {
destinations [ n . name ] = n . userData [ 'aria-description' ] || n . userData [ 'aria-label' ] || n . userData . href
}
} )
for ( let destination in destinations ) {
message += ` <br><b class="badge"> ${ destination } </b> ${ destinations [ destination ] } `
}
if ( ! message ) message += '<br>no actions found'
return message
}
document . addEventListener ( 'chat.input' , ( e ) => {
if ( e . detail . message . trim ( ) == '?' ) {
document . dispatchEvent ( new CustomEvent ( 'chat.command' , { detail : { message : "help" } } ) )
e . detail . halt = true // don't send to other peers
}
if ( e . detail . message . trim ( ) == 'look' ) {
let scene = xrf . frag . pos . last ? xrf . scene . getObjectByName ( xrf . frag . pos . last ) : xrf . scene
let message = ` <div class="transcript"> ${ xrf . sceneToTranscript ( scene ) } </div><br>possible destinations in this area: ${ listExits ( scene ) } `
e . detail . halt = true // dont print command to screen
$chat . send ( { message } )
}
if ( e . detail . message . match ( /^go($| )/ ) ) {
if ( e . detail . message . trim ( ) == 'go' ) {
$chat . send ( { message : ` all possible destinations: ${ listExits ( xrf . scene ) } ` } )
} else {
let destination = e . detail . message . replace ( /^go / , '' ) . trim ( )
if ( destination . match ( /(left|right|forward|backward)/ ) ) {
let key = ''
switch ( destination ) {
case "left" : key = 'ArrowLeft' ; break ;
case "right" : key = 'ArrowRight' ; break ;
case "forward" : key = 'ArrowUp' ; break ;
case "backward" : key = 'ArrowDown' ; break ;
}
if ( key ) {
let lookcontrols = document . querySelector ( '[look-controls]' )
if ( lookcontrols ) lookcontrols . removeAttribute ( 'look-controls' ) // workaround to unlock camera
var wasd = document . querySelector ( '[wasd-controls]' ) . components [ 'wasd-controls' ]
wasd . keys [ key ] = true
wasd . velocity = new THREE . Vector3 ( )
setTimeout ( ( ) => delete wasd . keys [ key ] , 100 )
wasd . el . object3D . matrixAutoUpdate = true ;
wasd . el . object3D . updateMatrix ( )
xrf . camera . getCam ( ) . updateMatrix ( )
}
} else {
let node
xrf . scene . traverse ( ( n ) => {
if ( n . userData && n . userData . href && n . name == destination ) node = n
} )
if ( node ) xrf . navigator . to ( node . userData . href )
else $chat . send ( { message : "type 'look' for possible destinations" } )
}
}
e . detail . halt = true // dont write input to chat
}
if ( e . detail . message . match ( /^do($| )/ ) ) {
if ( e . detail . message . trim ( ) == 'do' ) {
$chat . send ( { message : ` all possible actions: ${ listActions ( xrf . scene ) } ` } )
} else {
let action = e . detail . message . replace ( /^do / , '' ) . trim ( )
xrf . scene . traverse ( ( n ) => {
if ( n . userData && n . userData . href && n . name == action ) {
$chat . send ( { message : '<b class="badge">activating</b> ' + n . name , class : [ 'self' , 'info' ] } )
xrf . navigator . to ( n . userData . href )
}
} )
}
e . detail . halt = true // dont write input to chat
}
if ( e . detail . message . match ( /^rotate / ) ) {
let dir = e . detail . message . replace ( /^rotate / , '' ) . trim ( )
let y = 0 ;
let x = 0 ;
switch ( dir ) {
case "left" : y = 0.3 ; break ;
case "right" : y = - 0.3 ; break ;
case "up" : x = 0.3 ; break ;
case "down" : x = - 0.3 ; break ;
}
let lookcontrols = document . querySelector ( '[look-controls]' )
if ( lookcontrols ) lookcontrols . removeAttribute ( 'look-controls' ) // workaround to unlock camera
xrf . camera . rotation . y += y
xrf . camera . rotation . x += x
xrf . camera . matrixAutoUpdate = true
e . detail . halt = true // dont write input to chat
}
if ( e . detail . message . trim ( ) == 'back' ) {
window . history . back ( )
}
if ( e . detail . message . trim ( ) == 'forward' ) {
window . history . forward ( )
}
} )
// this allows surfing to a href by typing its node-name
// help screen
document . addEventListener ( 'chat.command.help' , ( e ) => {
e . detail . message += `
< br > < b class = "badge" > / s p e a k _ k e y b o a r d & l t ; t r u e | f a l s e & g t ; < / b > t u r n o n / o f f k e y b o a r d i n p u t T T S
< br > < b class = "badge" > / s p e a k _ t e l e p o r t s & l t ; t r u e | f a l s e & g t ; < / b > t u r n o n / o f f T T S f o r t e l e p o r t s
< br > < b class = "badge" > / s p e a k _ r a t e & l t ; 1 & g t ; < / b > a d j u s t T T S s p e e d
< br > < b class = "badge" > / s p e a k _ p i t c h & l t ; 1 & g t ; < / b > a d j u s t T T S p i t c h
< br > < b class = "badge" > / s p e a k _ v o l u m e & l t ; 1 & g t ; < / b > a d j u s t T T S v o l u m e
< br > < b class = "badge" > / s p e a k _ v o i c e & l t ; 0 & g t ; < / b > s e l e c t v o i c e ( m a x : $ { w i n d o w . a c c e s s i b i l i t y . s p e a k _ v o i c e s } )
`
} )
document . addEventListener ( 'chat.command' , ( e ) => {
if ( ! e . detail . message . trim ( ) . match ( / / ) ) return
let action = e . detail . message . trim ( ) . split ( " " ) [ 0 ]
let value = e . detail . message . trim ( ) . split ( " " ) [ 1 ]
if ( window . accessibility [ action ] == undefined ) return
window . accessibility [ action ] = value
window . localStorage . setItem ( action , value )
$chat . send ( { message : ` ${ action } set to ${ value } ` , class : [ 'info' ] } )
} )
2024-01-03 15:23:34 +01:00
} ) . apply ( { } )