new build
This commit is contained in:
		
							parent
							
								
									0d73a822e8
								
							
						
					
					
						commit
						543dd6bf71
					
				
					 10 changed files with 437434 additions and 148 deletions
				
			
		
							
								
								
									
										1
									
								
								dist/aframe-hand-tracking-controls-extras.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								dist/aframe-hand-tracking-controls-extras.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										18
									
								
								dist/aframe-troika-text.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								dist/aframe-troika-text.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										80
									
								
								dist/xrfragment.aframe.all.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										80
									
								
								dist/xrfragment.aframe.all.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * v0.5.1 generated at Tue Apr 16 04:44:21 PM UTC 2024
 | 
			
		||||
 * v0.5.1 generated at Thu Apr 25 03:56:52 PM UTC 2024
 | 
			
		||||
 * https://xrfragment.org
 | 
			
		||||
 * SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1945,6 +1945,7 @@ xrf.parseModel = function(model,url){
 | 
			
		|||
  let file               = xrf.getFile(url)
 | 
			
		||||
  model.file             = file
 | 
			
		||||
  model.isXRF            = true
 | 
			
		||||
  model.scene.isXRFRoot  = true
 | 
			
		||||
  model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
 | 
			
		||||
 | 
			
		||||
  xrf.emit('parseModel',{model,url,file})
 | 
			
		||||
| 
						 | 
				
			
			@ -2017,10 +2018,12 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
 | 
			
		||||
  let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
 | 
			
		||||
  URI.hash          = xrf.navigator.reactifyHash(URI.hash)
 | 
			
		||||
  let fileChange    = URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  let external      = URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  let hasPos        = URI.hash.pos 
 | 
			
		||||
  let hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  // decorate with extra state
 | 
			
		||||
  URI.fileChange    = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  URI.external      = URI.file && URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  URI.hasPos        = URI.hash.pos ? true : false
 | 
			
		||||
  URI.duplicatePos  = URI.source == xrf.navigator.URI.source && URI.hasPos
 | 
			
		||||
  URI.hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  let hashbus       = xrf.hashbus
 | 
			
		||||
  xrf.navigator.URI = URI
 | 
			
		||||
  let {directory,file,fragment,fileExt} = URI;
 | 
			
		||||
| 
						 | 
				
			
			@ -2044,22 +2047,25 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        loader = loader || new Loader().setPath( URI.URN )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( !URI.fragment && !URI.file && !URI.fileExt ) return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !fileChange && hashChange && !hasPos  ){
 | 
			
		||||
      if( URI.duplicatePos || (!URI.fragment && !URI.file && !URI.fileExt) ){ 
 | 
			
		||||
        return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !URI.fileChange && URI.hashChange && !URI.hasPos  ){
 | 
			
		||||
        evalFragment()
 | 
			
		||||
        return resolve(xrf.model)                         // positional navigation
 | 
			
		||||
        return resolve(xrf.model)                         // eval non-positional fragments (no loader needed)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      xrf
 | 
			
		||||
      .emit('navigateLoading', {url,loader,data})
 | 
			
		||||
      .then( () => {
 | 
			
		||||
        if( (!fileChange || !file) && hashChange && hasPos ){                 // we're already loaded
 | 
			
		||||
        if( (!URI.fileChange || !file) && URI.hashChange && URI.hasPos ){                 // we're already loaded
 | 
			
		||||
          evalFragment()
 | 
			
		||||
          xrf.emit('navigateLoaded',{url})
 | 
			
		||||
          return resolve(xrf.model) 
 | 
			
		||||
        }
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
        // clear xrf objects from scene
 | 
			
		||||
        if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false
 | 
			
		||||
        xrf.reset() 
 | 
			
		||||
| 
						 | 
				
			
			@ -2071,11 +2077,6 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        const onLoad = (model) => {
 | 
			
		||||
 | 
			
		||||
          model.file = URI.file
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          if( xrf.model ){
 | 
			
		||||
            xrf.navigator.pushState( external ? URI.URN + URI.file : URI.file, fragment )
 | 
			
		||||
          }
 | 
			
		||||
          //if( xrf.model ) xrf.navigator.pushState( `${ document.location.pathname != URI.directory ? URI.directory: ''}${URI.file}`, fragment )
 | 
			
		||||
          xrf.model = model 
 | 
			
		||||
 | 
			
		||||
          if( !model.isXRF ) xrf.parseModel(model,url.replace(directory,"")) // this marks the model as an XRF model
 | 
			
		||||
| 
						 | 
				
			
			@ -2090,12 +2091,17 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
            model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) )
 | 
			
		||||
          }
 | 
			
		||||
          // spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          const defaultFragment = xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          // spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments 
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
          xrf.add( model.scene )
 | 
			
		||||
          if( fragment ) xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          fragment = fragment || defaultFragment || ''
 | 
			
		||||
          xrf.navigator.pushState( URI.external ? URI.URN + URI.file : URI.file, fragment.replace(/^#/,'') )
 | 
			
		||||
          //if( fragment )  xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          xrf.emit('navigateLoaded',{url,model})
 | 
			
		||||
          resolve(model)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2122,7 +2128,7 @@ xrf.navigator.init = () => {
 | 
			
		|||
 | 
			
		||||
  window.addEventListener('popstate', function (event){
 | 
			
		||||
    if( !xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/\?/,'') )
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/.*\?/,'') )
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  
 | 
			
		||||
| 
						 | 
				
			
			@ -2358,13 +2364,16 @@ xrf.addEventListener('audioInited', function(opts){
 | 
			
		|||
 | 
			
		||||
xrf.frag.defaultPredefinedViews = (opts) => {
 | 
			
		||||
  let {scene,model} = opts;
 | 
			
		||||
  let defaultFragment;
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    if( n.userData && n.userData['#'] ){
 | 
			
		||||
      if( !n.parent && !document.location.hash ){
 | 
			
		||||
        xrf.navigator.to( n.userData['#'] )
 | 
			
		||||
      }else xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
      if( n.isXRFRoot ){ 
 | 
			
		||||
        defaultFragment = n.userData['#']
 | 
			
		||||
      }
 | 
			
		||||
      xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return defaultFragment
 | 
			
		||||
}
 | 
			
		||||
xrf.frag.loop = function(v, opts){
 | 
			
		||||
  let { frag, mesh, model, camera, scene, renderer, THREE} = opts
 | 
			
		||||
| 
						 | 
				
			
			@ -2404,7 +2413,7 @@ xrf.frag.pos = function(v, opts){
 | 
			
		|||
 | 
			
		||||
  if( xrf.debug ) console.log(`#pos.js: setting camera to position ${pos.x},${pos.y},${pos.z}`)
 | 
			
		||||
 | 
			
		||||
  xrf.frag.pos.last = pos // remember
 | 
			
		||||
  xrf.frag.pos.last = v.string // remember
 | 
			
		||||
 | 
			
		||||
  camera.updateMatrixWorld()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3148,6 +3157,17 @@ xrf.optimize.removeDuplicateLights = () => {
 | 
			
		|||
xrf.addEventListener('parseModel', (opts) => {
 | 
			
		||||
  xrf.optimize(opts)
 | 
			
		||||
})
 | 
			
		||||
xrf.sceneToTranscript = (scene, ignoreMesh ) => {
 | 
			
		||||
  let transcript = ''
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    let isSRC = false
 | 
			
		||||
    n.traverseAncestors( (m) => m.userData.src ? isSRC = true : false )
 | 
			
		||||
    if( !isSRC && n.userData['aria-description'] && (!ignoreMesh || n.uuid != ignoreMesh.uuid) ){
 | 
			
		||||
      transcript += `<b>#${n.name}</b> ${n.userData['aria-description']}. `
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return transcript
 | 
			
		||||
}
 | 
			
		||||
// switch camera when multiple cameras for url #mycameraname
 | 
			
		||||
 | 
			
		||||
xrf.addEventListener('dynamicKey', (opts) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -4098,10 +4118,16 @@ window.AFRAME.registerComponent('xrf', {
 | 
			
		|||
        VRbutton = document.querySelector('.a-enter-vr-button')
 | 
			
		||||
        if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#-VR' ) )
 | 
			
		||||
        if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
 | 
			
		||||
        //if( AFRAME.utils.device.checkARSupport() && VRbutton ){
 | 
			
		||||
        //  VRbutton.style.display = 'none'
 | 
			
		||||
        //  ARbutton.parentNode.style.right = '20px'
 | 
			
		||||
        //}
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      // (de)active look-controls because of 'rot=' XR Fragment
 | 
			
		||||
      aScene.addEventListener('loaded', () => {
 | 
			
		||||
        // this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model 
 | 
			
		||||
        aScene.canvas.addEventListener('mousedown', () => xrf.camera.el.setAttribute("look-controls","") )
 | 
			
		||||
      })
 | 
			
		||||
      XRF.addEventListener('rot',(e) => {
 | 
			
		||||
       let lookcontrols = document.querySelector('[look-controls]')
 | 
			
		||||
       if( lookcontrols ) lookcontrols.removeAttribute("look-controls")
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      let repositionUser = (scale) => () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										80
									
								
								dist/xrfragment.aframe.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										80
									
								
								dist/xrfragment.aframe.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * v0.5.1 generated at Tue Apr 16 04:44:20 PM UTC 2024
 | 
			
		||||
 * v0.5.1 generated at Thu Apr 25 03:56:52 PM UTC 2024
 | 
			
		||||
 * https://xrfragment.org
 | 
			
		||||
 * SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1943,6 +1943,7 @@ xrf.parseModel = function(model,url){
 | 
			
		|||
  let file               = xrf.getFile(url)
 | 
			
		||||
  model.file             = file
 | 
			
		||||
  model.isXRF            = true
 | 
			
		||||
  model.scene.isXRFRoot  = true
 | 
			
		||||
  model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
 | 
			
		||||
 | 
			
		||||
  xrf.emit('parseModel',{model,url,file})
 | 
			
		||||
| 
						 | 
				
			
			@ -2015,10 +2016,12 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
 | 
			
		||||
  let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
 | 
			
		||||
  URI.hash          = xrf.navigator.reactifyHash(URI.hash)
 | 
			
		||||
  let fileChange    = URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  let external      = URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  let hasPos        = URI.hash.pos 
 | 
			
		||||
  let hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  // decorate with extra state
 | 
			
		||||
  URI.fileChange    = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  URI.external      = URI.file && URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  URI.hasPos        = URI.hash.pos ? true : false
 | 
			
		||||
  URI.duplicatePos  = URI.source == xrf.navigator.URI.source && URI.hasPos
 | 
			
		||||
  URI.hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  let hashbus       = xrf.hashbus
 | 
			
		||||
  xrf.navigator.URI = URI
 | 
			
		||||
  let {directory,file,fragment,fileExt} = URI;
 | 
			
		||||
| 
						 | 
				
			
			@ -2042,22 +2045,25 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        loader = loader || new Loader().setPath( URI.URN )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( !URI.fragment && !URI.file && !URI.fileExt ) return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !fileChange && hashChange && !hasPos  ){
 | 
			
		||||
      if( URI.duplicatePos || (!URI.fragment && !URI.file && !URI.fileExt) ){ 
 | 
			
		||||
        return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !URI.fileChange && URI.hashChange && !URI.hasPos  ){
 | 
			
		||||
        evalFragment()
 | 
			
		||||
        return resolve(xrf.model)                         // positional navigation
 | 
			
		||||
        return resolve(xrf.model)                         // eval non-positional fragments (no loader needed)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      xrf
 | 
			
		||||
      .emit('navigateLoading', {url,loader,data})
 | 
			
		||||
      .then( () => {
 | 
			
		||||
        if( (!fileChange || !file) && hashChange && hasPos ){                 // we're already loaded
 | 
			
		||||
        if( (!URI.fileChange || !file) && URI.hashChange && URI.hasPos ){                 // we're already loaded
 | 
			
		||||
          evalFragment()
 | 
			
		||||
          xrf.emit('navigateLoaded',{url})
 | 
			
		||||
          return resolve(xrf.model) 
 | 
			
		||||
        }
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
        // clear xrf objects from scene
 | 
			
		||||
        if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false
 | 
			
		||||
        xrf.reset() 
 | 
			
		||||
| 
						 | 
				
			
			@ -2069,11 +2075,6 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        const onLoad = (model) => {
 | 
			
		||||
 | 
			
		||||
          model.file = URI.file
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          if( xrf.model ){
 | 
			
		||||
            xrf.navigator.pushState( external ? URI.URN + URI.file : URI.file, fragment )
 | 
			
		||||
          }
 | 
			
		||||
          //if( xrf.model ) xrf.navigator.pushState( `${ document.location.pathname != URI.directory ? URI.directory: ''}${URI.file}`, fragment )
 | 
			
		||||
          xrf.model = model 
 | 
			
		||||
 | 
			
		||||
          if( !model.isXRF ) xrf.parseModel(model,url.replace(directory,"")) // this marks the model as an XRF model
 | 
			
		||||
| 
						 | 
				
			
			@ -2088,12 +2089,17 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
            model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) )
 | 
			
		||||
          }
 | 
			
		||||
          // spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          const defaultFragment = xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          // spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments 
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
          xrf.add( model.scene )
 | 
			
		||||
          if( fragment ) xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          fragment = fragment || defaultFragment || ''
 | 
			
		||||
          xrf.navigator.pushState( URI.external ? URI.URN + URI.file : URI.file, fragment.replace(/^#/,'') )
 | 
			
		||||
          //if( fragment )  xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          xrf.emit('navigateLoaded',{url,model})
 | 
			
		||||
          resolve(model)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2120,7 +2126,7 @@ xrf.navigator.init = () => {
 | 
			
		|||
 | 
			
		||||
  window.addEventListener('popstate', function (event){
 | 
			
		||||
    if( !xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/\?/,'') )
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/.*\?/,'') )
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  
 | 
			
		||||
| 
						 | 
				
			
			@ -2356,13 +2362,16 @@ xrf.addEventListener('audioInited', function(opts){
 | 
			
		|||
 | 
			
		||||
xrf.frag.defaultPredefinedViews = (opts) => {
 | 
			
		||||
  let {scene,model} = opts;
 | 
			
		||||
  let defaultFragment;
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    if( n.userData && n.userData['#'] ){
 | 
			
		||||
      if( !n.parent && !document.location.hash ){
 | 
			
		||||
        xrf.navigator.to( n.userData['#'] )
 | 
			
		||||
      }else xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
      if( n.isXRFRoot ){ 
 | 
			
		||||
        defaultFragment = n.userData['#']
 | 
			
		||||
      }
 | 
			
		||||
      xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return defaultFragment
 | 
			
		||||
}
 | 
			
		||||
xrf.frag.loop = function(v, opts){
 | 
			
		||||
  let { frag, mesh, model, camera, scene, renderer, THREE} = opts
 | 
			
		||||
| 
						 | 
				
			
			@ -2402,7 +2411,7 @@ xrf.frag.pos = function(v, opts){
 | 
			
		|||
 | 
			
		||||
  if( xrf.debug ) console.log(`#pos.js: setting camera to position ${pos.x},${pos.y},${pos.z}`)
 | 
			
		||||
 | 
			
		||||
  xrf.frag.pos.last = pos // remember
 | 
			
		||||
  xrf.frag.pos.last = v.string // remember
 | 
			
		||||
 | 
			
		||||
  camera.updateMatrixWorld()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3146,6 +3155,17 @@ xrf.optimize.removeDuplicateLights = () => {
 | 
			
		|||
xrf.addEventListener('parseModel', (opts) => {
 | 
			
		||||
  xrf.optimize(opts)
 | 
			
		||||
})
 | 
			
		||||
xrf.sceneToTranscript = (scene, ignoreMesh ) => {
 | 
			
		||||
  let transcript = ''
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    let isSRC = false
 | 
			
		||||
    n.traverseAncestors( (m) => m.userData.src ? isSRC = true : false )
 | 
			
		||||
    if( !isSRC && n.userData['aria-description'] && (!ignoreMesh || n.uuid != ignoreMesh.uuid) ){
 | 
			
		||||
      transcript += `<b>#${n.name}</b> ${n.userData['aria-description']}. `
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return transcript
 | 
			
		||||
}
 | 
			
		||||
// switch camera when multiple cameras for url #mycameraname
 | 
			
		||||
 | 
			
		||||
xrf.addEventListener('dynamicKey', (opts) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -4096,10 +4116,16 @@ window.AFRAME.registerComponent('xrf', {
 | 
			
		|||
        VRbutton = document.querySelector('.a-enter-vr-button')
 | 
			
		||||
        if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#-VR' ) )
 | 
			
		||||
        if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
 | 
			
		||||
        //if( AFRAME.utils.device.checkARSupport() && VRbutton ){
 | 
			
		||||
        //  VRbutton.style.display = 'none'
 | 
			
		||||
        //  ARbutton.parentNode.style.right = '20px'
 | 
			
		||||
        //}
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      // (de)active look-controls because of 'rot=' XR Fragment
 | 
			
		||||
      aScene.addEventListener('loaded', () => {
 | 
			
		||||
        // this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model 
 | 
			
		||||
        aScene.canvas.addEventListener('mousedown', () => xrf.camera.el.setAttribute("look-controls","") )
 | 
			
		||||
      })
 | 
			
		||||
      XRF.addEventListener('rot',(e) => {
 | 
			
		||||
       let lookcontrols = document.querySelector('[look-controls]')
 | 
			
		||||
       if( lookcontrols ) lookcontrols.removeAttribute("look-controls")
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      let repositionUser = (scale) => () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										436829
									
								
								dist/xrfragment.module.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										436829
									
								
								dist/xrfragment.module.js
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										11
									
								
								dist/xrfragment.plugin.frontend.css.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								dist/xrfragment.plugin.frontend.css.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -289,7 +289,7 @@ document.head.innerHTML += `
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    .badge,
 | 
			
		||||
    #messages .msg.ui div.badge{
 | 
			
		||||
    #messages .msg .badge{
 | 
			
		||||
      box-sizing:border-box;
 | 
			
		||||
      display:inline-block;
 | 
			
		||||
      color: var(--xrf-white);
 | 
			
		||||
| 
						 | 
				
			
			@ -304,6 +304,11 @@ document.head.innerHTML += `
 | 
			
		|||
    #messages .msg.ui div.badge a{
 | 
			
		||||
      color:#FFF;
 | 
			
		||||
    }
 | 
			
		||||
    #messages .msg .badge{
 | 
			
		||||
      display:inline;
 | 
			
		||||
      color: var(--xrf-primary-fg);
 | 
			
		||||
      background: var(--xrf-dark-gray);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .ruler{
 | 
			
		||||
      width:97%; 
 | 
			
		||||
| 
						 | 
				
			
			@ -364,8 +369,8 @@ document.head.innerHTML += `
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  .transcript{
 | 
			
		||||
    max-height:105px;
 | 
			
		||||
    max-width:405px;
 | 
			
		||||
    max-height:132px;
 | 
			
		||||
    width:100%;
 | 
			
		||||
    overflow-y:auto;
 | 
			
		||||
    border: 1px solid var(--xrf-gray);
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										303
									
								
								dist/xrfragment.plugin.frontend.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										303
									
								
								dist/xrfragment.plugin.frontend.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -255,14 +255,15 @@ window.accessibility = (opts) => new Proxy({
 | 
			
		|||
  enabled: false,
 | 
			
		||||
 | 
			
		||||
  // features
 | 
			
		||||
  speak_movements: true,
 | 
			
		||||
  speak_keyboard: true,
 | 
			
		||||
  speak_teleports: true,
 | 
			
		||||
  speak_keyboard: false,
 | 
			
		||||
 | 
			
		||||
  // audio settings
 | 
			
		||||
  speak_rate: 1,
 | 
			
		||||
  speak_pitch: 1,
 | 
			
		||||
  speak_volume: 1,
 | 
			
		||||
  speak_voice: -1,
 | 
			
		||||
  speak_voices: 0,
 | 
			
		||||
 | 
			
		||||
  toggle(){ this.enabled = !this.enabled },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -284,8 +285,10 @@ window.accessibility = (opts) => new Proxy({
 | 
			
		|||
    }
 | 
			
		||||
    let speech = window.speechSynthesis
 | 
			
		||||
    let utterance = new SpeechSynthesisUtterance( str )
 | 
			
		||||
    if( this.speak_voice != -1) utterance.voice  = speech.getVoices()[ this.speak_voice ];
 | 
			
		||||
    else{
 | 
			
		||||
    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{
 | 
			
		||||
      let voices = speech.getVoices()
 | 
			
		||||
      for(let i = 0; i < voices.length; i++ ){
 | 
			
		||||
        if( voices[i].lang == navigator.lang ) this.speak_voice = i;
 | 
			
		||||
| 
						 | 
				
			
			@ -329,6 +332,9 @@ window.accessibility = (opts) => new Proxy({
 | 
			
		|||
          this.speak( lines.join("."), {override:true,speaksigns:false} )
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      document.addEventListener('$chat.send', (opts) => {
 | 
			
		||||
        if( opts.detail.message ) this.speak( opts.detail.message)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('network.send', (e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -338,8 +344,7 @@ window.accessibility = (opts) => new Proxy({
 | 
			
		|||
    })
 | 
			
		||||
 | 
			
		||||
    opts.xrf.addEventListener('pos', (opts) => {
 | 
			
		||||
      if( this.enabled ){
 | 
			
		||||
        $chat.send({message: this.posToMessage(opts) })
 | 
			
		||||
      if( this.enabled && this.speak_teleports ){
 | 
			
		||||
        network.send({message: this.posToMessage(opts), class:["info","guide"]})
 | 
			
		||||
      }
 | 
			
		||||
      if( opts.frag.pos.string.match(/,/) ){
 | 
			
		||||
| 
						 | 
				
			
			@ -349,6 +354,53 @@ window.accessibility = (opts) => new Proxy({
 | 
			
		|||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    setTimeout( () => this.initCommands(), 200 )
 | 
			
		||||
    // auto-enable if previously enabled
 | 
			
		||||
    if( window.localStorage.getItem("accessibility") === 'true' ){
 | 
			
		||||
      setTimeout( () => {
 | 
			
		||||
        this.enabled = true
 | 
			
		||||
        this.setFontSize()
 | 
			
		||||
      }, 100 )
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  initCommands(){
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('chat.command.help', (e) => {
 | 
			
		||||
      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;
 | 
			
		||||
        }
 | 
			
		||||
      </style>
 | 
			
		||||
    `
 | 
			
		||||
    $messages = document.querySelector('#messages')
 | 
			
		||||
    setTimeout( () => $messages.scrollTop = $messages.scrollHeight, 1000 )
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  posToMessage(opts){
 | 
			
		||||
| 
						 | 
				
			
			@ -383,14 +435,17 @@ window.accessibility = (opts) => new Proxy({
 | 
			
		|||
    data[k] = v 
 | 
			
		||||
    switch( k ){
 | 
			
		||||
      case "enabled": {
 | 
			
		||||
                        let message = "accessibility has been"+(v?"boosted":"lowered")
 | 
			
		||||
                        let message = "accessibility mode has been "+(v?"activated":"disabled")+".<br>Type /help for help."
 | 
			
		||||
                        if( v ) message = "<img src='https://i.imgur.com/wedtUSs.png' style='width:100%;border-radius:6px'/><br>" + message
 | 
			
		||||
                        $('#accessibility.btn').style.filter= v ? 'brightness(1.0)' : 'brightness(0.5)'
 | 
			
		||||
                        if( v ) $chat.visible = true
 | 
			
		||||
                        $chat.send({message,class:['info','guide']})
 | 
			
		||||
                        $chat.send({message,class:['info']})
 | 
			
		||||
                        data.enabled = true
 | 
			
		||||
                        data.speak(message)
 | 
			
		||||
                        data.enabled = v
 | 
			
		||||
                        window.localStorage.setItem("accessibility", v)
 | 
			
		||||
                        $chat.$messages.classList[ v ? 'add' : 'remove' ]('guide')
 | 
			
		||||
                        document.body.classList.toggle(['accessibility'])
 | 
			
		||||
                        if( !data.readTranscript && (data.readTranscript = true) ){
 | 
			
		||||
                          data.speak( data.sanitizeTranscript() )
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			@ -400,7 +455,6 @@ window.accessibility = (opts) => new Proxy({
 | 
			
		|||
})
 | 
			
		||||
 | 
			
		||||
document.addEventListener('$menu:ready', (e) => {
 | 
			
		||||
  return
 | 
			
		||||
  try{
 | 
			
		||||
    accessibility = accessibility(e.detail) 
 | 
			
		||||
    accessibility.init()
 | 
			
		||||
| 
						 | 
				
			
			@ -408,6 +462,35 @@ document.addEventListener('$menu: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)}
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
document.querySelector('head').innerHTML += `
 | 
			
		||||
  <style type="text/css"> 
 | 
			
		||||
    .accessibility #messages * {
 | 
			
		||||
      font-size:24px !important;
 | 
			
		||||
      line-height:40px;
 | 
			
		||||
    }
 | 
			
		||||
    .accessibility #messages .msg.self {
 | 
			
		||||
      background:var(--xrf-gray);
 | 
			
		||||
      color:#FFF;
 | 
			
		||||
    }
 | 
			
		||||
    .accessibility #messages .msg.info,
 | 
			
		||||
    .accessibility #messages .msg.self {
 | 
			
		||||
      line-height:unset;
 | 
			
		||||
      padding-top:15px;
 | 
			
		||||
      padding-bottom:15px;
 | 
			
		||||
    }
 | 
			
		||||
    .accessibility #chatbar{
 | 
			
		||||
      display: block !important;
 | 
			
		||||
    }
 | 
			
		||||
    .accessibility #chatsend{
 | 
			
		||||
      display: block !important;
 | 
			
		||||
    }
 | 
			
		||||
    .accessibility #chatline {
 | 
			
		||||
      text-indent:25px;
 | 
			
		||||
    }
 | 
			
		||||
  </style>
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
// reactive component for displaying the menu 
 | 
			
		||||
menuComponent = (el) => new Proxy({
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -570,8 +653,9 @@ window.frontend = (opts) => new Proxy({
 | 
			
		|||
                           ? "hold 2-3 fingers to move forward/backward" 
 | 
			
		||||
                           :  "use WASD-keys and mouse-drag to move around"
 | 
			
		||||
        window.notify(instructions,{timeout:false})
 | 
			
		||||
        xrf.addEventListener('navigate', (opts) => {
 | 
			
		||||
          window.notify('<b class="badge">teleporting</b> to <b>'+opts.url+"</b><br><br>use back/forward browserbutton to undo")
 | 
			
		||||
        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")
 | 
			
		||||
        }) // close dialogs when url changes
 | 
			
		||||
      },2000 )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -594,13 +678,8 @@ window.frontend = (opts) => new Proxy({
 | 
			
		|||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        let transcript = ''
 | 
			
		||||
        let root = data.mesh.portal ? data.mesh.portal.stencilObject : data.mesh
 | 
			
		||||
        root.traverse( (n) => {
 | 
			
		||||
          if( n.userData['aria-description'] && n.uuid != data.mesh.uuid ){
 | 
			
		||||
            transcript += `<b>#${n.name}</b> ${n.userData['aria-description']}. `
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        let transcript = xrf.sceneToTranscript(root,data.mesh)
 | 
			
		||||
        if( transcript.length ) html += `<br><b>transcript:</b><br><div class="transcript">${transcript}</div>`
 | 
			
		||||
 | 
			
		||||
        if (hasMeta && !data.mesh.portal ) html += `<br><br><a class="btn" style="float:right" onclick="xrf.navigator.to('${data.mesh.userData.href}')">Visit embedded scene</a>`
 | 
			
		||||
| 
						 | 
				
			
			@ -834,4 +913,194 @@ window.frontend = (opts) => new Proxy({
 | 
			
		|||
})
 | 
			
		||||
 | 
			
		||||
frontend = frontend({xrf,document}).init()
 | 
			
		||||
// 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"><destinationname></b> surf to a destination
 | 
			
		||||
  ` 
 | 
			
		||||
})
 | 
			
		||||
        
 | 
			
		||||
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> help screen
 | 
			
		||||
    <br><b class="badge">look</b> view scene and destinations 
 | 
			
		||||
    <br><b class="badge">go [left|right|forward|destination]</b> surf [to destination]
 | 
			
		||||
    <br><b class="badge">do [action]</b> list [or perform] action(s)
 | 
			
		||||
    <br><b class="badge">rotate <left|right|up|down></b> rotate camera
 | 
			
		||||
    <br><b class="badge">back</b> go to previous portal/link 
 | 
			
		||||
    <br><b class="badge">forward</b> go to previous portal/link 
 | 
			
		||||
    <br><b class="badge">#....</b> execute XR Fragments 
 | 
			
		||||
    <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">/speak_keyboard <true|false></b> turn on/off keyboard input TTS
 | 
			
		||||
    <br><b class="badge">/speak_teleports <true|false></b> turn on/off TTS for teleports
 | 
			
		||||
    <br><b class="badge">/speak_rate <1></b> adjust TTS speed
 | 
			
		||||
    <br><b class="badge">/speak_pitch <1></b> adjust TTS pitch
 | 
			
		||||
    <br><b class="badge">/speak_volume <1></b> adjust TTS volume
 | 
			
		||||
    <br><b class="badge">/speak_voice <0></b> select voice (max: ${window.accessibility.speak_voices})
 | 
			
		||||
  ` 
 | 
			
		||||
})
 | 
			
		||||
        
 | 
			
		||||
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']})
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
}).apply({})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										128
									
								
								dist/xrfragment.plugin.network.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										128
									
								
								dist/xrfragment.plugin.network.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -329,7 +329,9 @@ connectionsComponent = {
 | 
			
		|||
    set(data,k,v){ 
 | 
			
		||||
      data[k] = v 
 | 
			
		||||
      switch( k ){
 | 
			
		||||
        case "visible":             el.style.display = v ? '' : 'none'; break;
 | 
			
		||||
        case "visible":             el.style.display = v ? '' : 'none'; 
 | 
			
		||||
                                    if( !v && el.parentNode && el.parentNode.parentNode ) el.parentNode.parentNode.remove() 
 | 
			
		||||
                                    break;
 | 
			
		||||
        case "webcam":              $webcam.innerHTML       = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
 | 
			
		||||
        case "chatnetwork":         $chatnetwork.innerHTML  = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
 | 
			
		||||
        case "scene":               $scene.innerHTML        = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
 | 
			
		||||
| 
						 | 
				
			
			@ -386,12 +388,14 @@ chatComponent = {
 | 
			
		|||
  html: `
 | 
			
		||||
    <div id="chat">
 | 
			
		||||
     <div id="videos" style="pointer-events:none"></div>
 | 
			
		||||
     <div id="messages" aria-live="assertive" aria-relevant></div>
 | 
			
		||||
     <div id="messages" aria-live="assertive" role="log" aria-relevant="additions"></div>
 | 
			
		||||
     <div id="chatfooter">
 | 
			
		||||
       <div id="chatbar">
 | 
			
		||||
           <input id="chatline" type="text" placeholder="chat here"></input>
 | 
			
		||||
       </div>
 | 
			
		||||
       <button id="showchat" class="btn">show chat</button>
 | 
			
		||||
       <button id="chatsend" class="btn" aria-label="send message">
 | 
			
		||||
          <i class="gg-chevron-right-o"></i>
 | 
			
		||||
       </button>
 | 
			
		||||
     </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  `,
 | 
			
		||||
| 
						 | 
				
			
			@ -409,6 +413,7 @@ chatComponent = {
 | 
			
		|||
    $messages:       el.querySelector("#messages"),
 | 
			
		||||
    $chatline:       el.querySelector("#chatline"),
 | 
			
		||||
    $chatbar:        el.querySelector("#chatbar"),
 | 
			
		||||
    $chatsend:       el.querySelector("#chatsend"),
 | 
			
		||||
    
 | 
			
		||||
    install(opts){
 | 
			
		||||
      this.opts  = opts
 | 
			
		||||
| 
						 | 
				
			
			@ -421,6 +426,19 @@ chatComponent = {
 | 
			
		|||
      this.send({message:`Welcome to <b>${document.location.search.substr(1)}</b>, a 3D scene(file) which simply links to other ones.<br>You can start a solo offline exploration in XR right away.<br>Type /help below, or use the arrow- or WASD-keys on your keyboard, and mouse-drag to rotate.<br>`, class: ["info","guide","multiline"] })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    sendInput(value){
 | 
			
		||||
      if( value[0] == '#' ) return xrf.navigator.to(value)
 | 
			
		||||
      let event   = value.match(/^[!\/]/) ? "chat.command" : "network.send"
 | 
			
		||||
      let message = value.replace(/^[!\/]/,'')
 | 
			
		||||
      let raw     = {detail:{message:value, halt:false}}
 | 
			
		||||
      document.dispatchEvent( new CustomEvent( event, {detail: {message}} ) )
 | 
			
		||||
      document.dispatchEvent( new CustomEvent( "chat.input", raw ) )
 | 
			
		||||
      if( event == "network.send" && !raw.detail.halt ) this.send({message: value })
 | 
			
		||||
      this.$chatline.lastValue = value
 | 
			
		||||
      this.$chatline.value = ''
 | 
			
		||||
      if( window.innerHeight < 600 ) this.$chatline.blur()
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initListeners(){
 | 
			
		||||
      let {$chatline} = this
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -428,12 +446,10 @@ chatComponent = {
 | 
			
		|||
 | 
			
		||||
      $chatline.addEventListener('keydown', (e) => {
 | 
			
		||||
        if (e.key == 'Enter' ){
 | 
			
		||||
          if( $chatline.value[0] != '/' ){
 | 
			
		||||
            document.dispatchEvent( new CustomEvent("network.send", {detail: {message:$chatline.value}} ) )
 | 
			
		||||
          }
 | 
			
		||||
          this.send({message: $chatline.value })
 | 
			
		||||
          $chatline.value = ''
 | 
			
		||||
          if( window.innerHeight < 600 ) $chatline.blur()
 | 
			
		||||
          this.sendInput($chatline.value)
 | 
			
		||||
        }
 | 
			
		||||
        if (e.key == 'ArrowUp' ){
 | 
			
		||||
          $chatline.value = $chatline.lastValue || ''
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -446,11 +462,26 @@ chatComponent = {
 | 
			
		|||
        if( e.detail.username ) this.username = e.detail.username
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      document.addEventListener('chat.command', (e) => {
 | 
			
		||||
        if( String(e.detail.message).trim() == 'help' ){
 | 
			
		||||
          let detail = {message:`The following commands are available
 | 
			
		||||
          <br><br>
 | 
			
		||||
          <b class="badge">/help</b> shows this help screen
 | 
			
		||||
          `}
 | 
			
		||||
          document.dispatchEvent( new CustomEvent( 'chat.command.help', {detail}))
 | 
			
		||||
          this.send({message: detail.message})
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      this.$chatsend.addEventListener('click', (e) => {
 | 
			
		||||
        this.sendInput($chatline.value)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    inform(){
 | 
			
		||||
      if( !this.inform.informed && (this.inform.informed = true) ){
 | 
			
		||||
        window.notify("Connected via P2P. You can now type message which will be visible to others.")
 | 
			
		||||
        window.notify("You can now type messages in the textfield below.")
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -488,7 +519,7 @@ chatComponent = {
 | 
			
		|||
        br.classList.add.apply(br.classList, opts.class)
 | 
			
		||||
        div.classList.add.apply(div.classList, opts.class.concat(["envelope"]))
 | 
			
		||||
      }
 | 
			
		||||
      if( msg.className.match(/(info|guide|ui)/) || !opts.from ){
 | 
			
		||||
      if( !msg.className.match(/(info|guide|ui)/) && !opts.from ){
 | 
			
		||||
        let frag = xrf.URI.parse(document.location.hash).XRF
 | 
			
		||||
        opts.from = 'you'
 | 
			
		||||
        if( frag.pos ) opts.pos = frag.pos.string
 | 
			
		||||
| 
						 | 
				
			
			@ -589,7 +620,7 @@ chatComponent.css = `
 | 
			
		|||
     }
 | 
			
		||||
 | 
			
		||||
     #chatbar,
 | 
			
		||||
     button#showchat{
 | 
			
		||||
     button#chatsend{
 | 
			
		||||
       z-index: 1500;
 | 
			
		||||
       position: fixed;
 | 
			
		||||
       bottom: 24px;
 | 
			
		||||
| 
						 | 
				
			
			@ -603,14 +634,19 @@ chatComponent.css = `
 | 
			
		|||
       box-sizing: border-box;
 | 
			
		||||
       box-shadow: 0px 0px 5px 5px #0002;
 | 
			
		||||
     }
 | 
			
		||||
     button#showchat{
 | 
			
		||||
       z-index:1550;
 | 
			
		||||
       color:white;
 | 
			
		||||
       border:0;
 | 
			
		||||
       display:none;
 | 
			
		||||
       height: 44px;
 | 
			
		||||
       background:#07F;
 | 
			
		||||
       font-weight:bold;
 | 
			
		||||
     button#chatsend{
 | 
			
		||||
      line-height:0px;
 | 
			
		||||
      display:none;
 | 
			
		||||
      z-index: 1550;
 | 
			
		||||
      color: white;
 | 
			
		||||
      border: 0;
 | 
			
		||||
      height: 35px;
 | 
			
		||||
      background: var(--xrf-dark-gray);
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
      width: 20px;
 | 
			
		||||
      max-width: 20px;
 | 
			
		||||
      border-radius: 20px 0px 0px 20px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
     }
 | 
			
		||||
     #chatbar input{
 | 
			
		||||
       border:none;
 | 
			
		||||
| 
						 | 
				
			
			@ -628,6 +664,7 @@ chatComponent.css = `
 | 
			
		|||
       max-width: 500px;
 | 
			
		||||
       */
 | 
			
		||||
       width:100%;
 | 
			
		||||
       box-sizing:border-box;
 | 
			
		||||
       align-items: flex-start;
 | 
			
		||||
       position: absolute;
 | 
			
		||||
       transition:1s;
 | 
			
		||||
| 
						 | 
				
			
			@ -636,7 +673,7 @@ chatComponent.css = `
 | 
			
		|||
       bottom: 49px;
 | 
			
		||||
       padding: 20px;
 | 
			
		||||
       overflow:hidden;
 | 
			
		||||
       overflow-y: scroll;
 | 
			
		||||
       overflow-y: auto;
 | 
			
		||||
       pointer-events:none;
 | 
			
		||||
       transition:1s;
 | 
			
		||||
       z-index: 100;
 | 
			
		||||
| 
						 | 
				
			
			@ -648,11 +685,14 @@ chatComponent.css = `
 | 
			
		|||
       pointer-events:all;
 | 
			
		||||
     }
 | 
			
		||||
     #messages *{
 | 
			
		||||
       box-sizing:border-box;
 | 
			
		||||
/*
 | 
			
		||||
       pointer-events:none;
 | 
			
		||||
       -webkit-user-select:none;
 | 
			
		||||
       -moz-user-select:-moz-none;
 | 
			
		||||
       -ms-user-select:none;
 | 
			
		||||
       user-select:none;
 | 
			
		||||
*/
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg{
 | 
			
		||||
       transition:all 1s ease;
 | 
			
		||||
| 
						 | 
				
			
			@ -667,7 +707,8 @@ chatComponent.css = `
 | 
			
		|||
       cursor:grabbing;
 | 
			
		||||
       border: 1px solid #0002;
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg *{
 | 
			
		||||
     #messages .msg *,
 | 
			
		||||
     #messages .user *{
 | 
			
		||||
       pointer-events:all;
 | 
			
		||||
       -webkit-user-select:text;
 | 
			
		||||
       -moz-user-select:-moz-text;
 | 
			
		||||
| 
						 | 
				
			
			@ -677,17 +718,17 @@ chatComponent.css = `
 | 
			
		|||
 | 
			
		||||
     #messages .msg.self{
 | 
			
		||||
       border-radius: 20px;
 | 
			
		||||
       background:var(--xrf-box-shadow);
 | 
			
		||||
       background:var(--xrf-dark-gray);
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg.self,
 | 
			
		||||
     #messages .msg.self div{
 | 
			
		||||
       color:#FFF;
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg.info{
 | 
			
		||||
       background: #473f7f;
 | 
			
		||||
       background: var(--xrf-white);
 | 
			
		||||
       border-radius: 20px;
 | 
			
		||||
       color: #FFF;
 | 
			
		||||
       text-align: right;
 | 
			
		||||
       color: var(--xrf-dark-gray);
 | 
			
		||||
       text-align: left;
 | 
			
		||||
       line-height: 19px;
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg.info,
 | 
			
		||||
| 
						 | 
				
			
			@ -696,7 +737,7 @@ chatComponent.css = `
 | 
			
		|||
     }
 | 
			
		||||
     #messages .msg a {
 | 
			
		||||
       text-decoration:underline;
 | 
			
		||||
       color: var(--xrf-primary);
 | 
			
		||||
       color: var(--xrf-light-xrf-secondary);
 | 
			
		||||
       font-weight:bold;
 | 
			
		||||
       transition:0.3s;
 | 
			
		||||
     }
 | 
			
		||||
| 
						 | 
				
			
			@ -773,9 +814,14 @@ chatComponent.css = `
 | 
			
		|||
      margin:0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .envelope{
 | 
			
		||||
      margin-right:15px;
 | 
			
		||||
      width:50%;
 | 
			
		||||
      max-width:700px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .envelope,
 | 
			
		||||
    .envelope * {
 | 
			
		||||
      overflow:hidden;
 | 
			
		||||
      transition:1s;
 | 
			
		||||
      pointer-events:none;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -796,5 +842,31 @@ chatComponent.css = `
 | 
			
		|||
    .user, .user *{ 
 | 
			
		||||
      font-size: var(--xrf-font-size-0);
 | 
			
		||||
    }
 | 
			
		||||
    .gg-chevron-right-o {
 | 
			
		||||
      color:#FFF;
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      position: relative;
 | 
			
		||||
      display: block;
 | 
			
		||||
      transform: scale(var(--ggs,1));
 | 
			
		||||
      width: 22px;
 | 
			
		||||
      height: 22px;
 | 
			
		||||
      border: 2px solid;
 | 
			
		||||
      border-radius: 100px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .gg-chevron-right-o::after {
 | 
			
		||||
      color:#FFF;
 | 
			
		||||
      content: "";
 | 
			
		||||
      display: block;
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      width: 6px;
 | 
			
		||||
      height: 6px;
 | 
			
		||||
      border-bottom: 2px solid;
 | 
			
		||||
      border-right: 2px solid;
 | 
			
		||||
      transform: rotate(-45deg);
 | 
			
		||||
      left: 5px;
 | 
			
		||||
      top: 6px
 | 
			
		||||
    }
 | 
			
		||||
   </style>`
 | 
			
		||||
}).apply({})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										66
									
								
								dist/xrfragment.three.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										66
									
								
								dist/xrfragment.three.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * v0.5.1 generated at Tue Apr 16 04:44:20 PM UTC 2024
 | 
			
		||||
 * v0.5.1 generated at Thu Apr 25 03:56:52 PM UTC 2024
 | 
			
		||||
 * https://xrfragment.org
 | 
			
		||||
 * SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1943,6 +1943,7 @@ xrf.parseModel = function(model,url){
 | 
			
		|||
  let file               = xrf.getFile(url)
 | 
			
		||||
  model.file             = file
 | 
			
		||||
  model.isXRF            = true
 | 
			
		||||
  model.scene.isXRFRoot  = true
 | 
			
		||||
  model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
 | 
			
		||||
 | 
			
		||||
  xrf.emit('parseModel',{model,url,file})
 | 
			
		||||
| 
						 | 
				
			
			@ -2015,10 +2016,12 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
 | 
			
		||||
  let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
 | 
			
		||||
  URI.hash          = xrf.navigator.reactifyHash(URI.hash)
 | 
			
		||||
  let fileChange    = URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  let external      = URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  let hasPos        = URI.hash.pos 
 | 
			
		||||
  let hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  // decorate with extra state
 | 
			
		||||
  URI.fileChange    = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  URI.external      = URI.file && URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  URI.hasPos        = URI.hash.pos ? true : false
 | 
			
		||||
  URI.duplicatePos  = URI.source == xrf.navigator.URI.source && URI.hasPos
 | 
			
		||||
  URI.hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  let hashbus       = xrf.hashbus
 | 
			
		||||
  xrf.navigator.URI = URI
 | 
			
		||||
  let {directory,file,fragment,fileExt} = URI;
 | 
			
		||||
| 
						 | 
				
			
			@ -2042,22 +2045,25 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        loader = loader || new Loader().setPath( URI.URN )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( !URI.fragment && !URI.file && !URI.fileExt ) return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !fileChange && hashChange && !hasPos  ){
 | 
			
		||||
      if( URI.duplicatePos || (!URI.fragment && !URI.file && !URI.fileExt) ){ 
 | 
			
		||||
        return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !URI.fileChange && URI.hashChange && !URI.hasPos  ){
 | 
			
		||||
        evalFragment()
 | 
			
		||||
        return resolve(xrf.model)                         // positional navigation
 | 
			
		||||
        return resolve(xrf.model)                         // eval non-positional fragments (no loader needed)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      xrf
 | 
			
		||||
      .emit('navigateLoading', {url,loader,data})
 | 
			
		||||
      .then( () => {
 | 
			
		||||
        if( (!fileChange || !file) && hashChange && hasPos ){                 // we're already loaded
 | 
			
		||||
        if( (!URI.fileChange || !file) && URI.hashChange && URI.hasPos ){                 // we're already loaded
 | 
			
		||||
          evalFragment()
 | 
			
		||||
          xrf.emit('navigateLoaded',{url})
 | 
			
		||||
          return resolve(xrf.model) 
 | 
			
		||||
        }
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
        // clear xrf objects from scene
 | 
			
		||||
        if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false
 | 
			
		||||
        xrf.reset() 
 | 
			
		||||
| 
						 | 
				
			
			@ -2069,11 +2075,6 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        const onLoad = (model) => {
 | 
			
		||||
 | 
			
		||||
          model.file = URI.file
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          if( xrf.model ){
 | 
			
		||||
            xrf.navigator.pushState( external ? URI.URN + URI.file : URI.file, fragment )
 | 
			
		||||
          }
 | 
			
		||||
          //if( xrf.model ) xrf.navigator.pushState( `${ document.location.pathname != URI.directory ? URI.directory: ''}${URI.file}`, fragment )
 | 
			
		||||
          xrf.model = model 
 | 
			
		||||
 | 
			
		||||
          if( !model.isXRF ) xrf.parseModel(model,url.replace(directory,"")) // this marks the model as an XRF model
 | 
			
		||||
| 
						 | 
				
			
			@ -2088,12 +2089,17 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
            model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) )
 | 
			
		||||
          }
 | 
			
		||||
          // spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          const defaultFragment = xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          // spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments 
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
          xrf.add( model.scene )
 | 
			
		||||
          if( fragment ) xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          fragment = fragment || defaultFragment || ''
 | 
			
		||||
          xrf.navigator.pushState( URI.external ? URI.URN + URI.file : URI.file, fragment.replace(/^#/,'') )
 | 
			
		||||
          //if( fragment )  xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          xrf.emit('navigateLoaded',{url,model})
 | 
			
		||||
          resolve(model)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2120,7 +2126,7 @@ xrf.navigator.init = () => {
 | 
			
		|||
 | 
			
		||||
  window.addEventListener('popstate', function (event){
 | 
			
		||||
    if( !xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/\?/,'') )
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/.*\?/,'') )
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  
 | 
			
		||||
| 
						 | 
				
			
			@ -2356,13 +2362,16 @@ xrf.addEventListener('audioInited', function(opts){
 | 
			
		|||
 | 
			
		||||
xrf.frag.defaultPredefinedViews = (opts) => {
 | 
			
		||||
  let {scene,model} = opts;
 | 
			
		||||
  let defaultFragment;
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    if( n.userData && n.userData['#'] ){
 | 
			
		||||
      if( !n.parent && !document.location.hash ){
 | 
			
		||||
        xrf.navigator.to( n.userData['#'] )
 | 
			
		||||
      }else xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
      if( n.isXRFRoot ){ 
 | 
			
		||||
        defaultFragment = n.userData['#']
 | 
			
		||||
      }
 | 
			
		||||
      xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return defaultFragment
 | 
			
		||||
}
 | 
			
		||||
xrf.frag.loop = function(v, opts){
 | 
			
		||||
  let { frag, mesh, model, camera, scene, renderer, THREE} = opts
 | 
			
		||||
| 
						 | 
				
			
			@ -2402,7 +2411,7 @@ xrf.frag.pos = function(v, opts){
 | 
			
		|||
 | 
			
		||||
  if( xrf.debug ) console.log(`#pos.js: setting camera to position ${pos.x},${pos.y},${pos.z}`)
 | 
			
		||||
 | 
			
		||||
  xrf.frag.pos.last = pos // remember
 | 
			
		||||
  xrf.frag.pos.last = v.string // remember
 | 
			
		||||
 | 
			
		||||
  camera.updateMatrixWorld()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3146,6 +3155,17 @@ xrf.optimize.removeDuplicateLights = () => {
 | 
			
		|||
xrf.addEventListener('parseModel', (opts) => {
 | 
			
		||||
  xrf.optimize(opts)
 | 
			
		||||
})
 | 
			
		||||
xrf.sceneToTranscript = (scene, ignoreMesh ) => {
 | 
			
		||||
  let transcript = ''
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    let isSRC = false
 | 
			
		||||
    n.traverseAncestors( (m) => m.userData.src ? isSRC = true : false )
 | 
			
		||||
    if( !isSRC && n.userData['aria-description'] && (!ignoreMesh || n.uuid != ignoreMesh.uuid) ){
 | 
			
		||||
      transcript += `<b>#${n.name}</b> ${n.userData['aria-description']}. `
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return transcript
 | 
			
		||||
}
 | 
			
		||||
// switch camera when multiple cameras for url #mycameraname
 | 
			
		||||
 | 
			
		||||
xrf.addEventListener('dynamicKey', (opts) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										66
									
								
								dist/xrfragment.three.module.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										66
									
								
								dist/xrfragment.three.module.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * v0.5.1 generated at Tue Apr 16 04:44:20 PM UTC 2024
 | 
			
		||||
 * v0.5.1 generated at Thu Apr 25 03:56:52 PM UTC 2024
 | 
			
		||||
 * https://xrfragment.org
 | 
			
		||||
 * SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1943,6 +1943,7 @@ xrf.parseModel = function(model,url){
 | 
			
		|||
  let file               = xrf.getFile(url)
 | 
			
		||||
  model.file             = file
 | 
			
		||||
  model.isXRF            = true
 | 
			
		||||
  model.scene.isXRFRoot  = true
 | 
			
		||||
  model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
 | 
			
		||||
 | 
			
		||||
  xrf.emit('parseModel',{model,url,file})
 | 
			
		||||
| 
						 | 
				
			
			@ -2015,10 +2016,12 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
 | 
			
		||||
  let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
 | 
			
		||||
  URI.hash          = xrf.navigator.reactifyHash(URI.hash)
 | 
			
		||||
  let fileChange    = URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  let external      = URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  let hasPos        = URI.hash.pos 
 | 
			
		||||
  let hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  // decorate with extra state
 | 
			
		||||
  URI.fileChange    = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file 
 | 
			
		||||
  URI.external      = URI.file && URI.URN != document.location.origin + document.location.pathname 
 | 
			
		||||
  URI.hasPos        = URI.hash.pos ? true : false
 | 
			
		||||
  URI.duplicatePos  = URI.source == xrf.navigator.URI.source && URI.hasPos
 | 
			
		||||
  URI.hashChange    = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
 | 
			
		||||
  let hashbus       = xrf.hashbus
 | 
			
		||||
  xrf.navigator.URI = URI
 | 
			
		||||
  let {directory,file,fragment,fileExt} = URI;
 | 
			
		||||
| 
						 | 
				
			
			@ -2042,22 +2045,25 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        loader = loader || new Loader().setPath( URI.URN )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( !URI.fragment && !URI.file && !URI.fileExt ) return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !fileChange && hashChange && !hasPos  ){
 | 
			
		||||
      if( URI.duplicatePos || (!URI.fragment && !URI.file && !URI.fileExt) ){ 
 | 
			
		||||
        return resolve(xrf.model) // nothing we can do here
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if( xrf.model && !URI.fileChange && URI.hashChange && !URI.hasPos  ){
 | 
			
		||||
        evalFragment()
 | 
			
		||||
        return resolve(xrf.model)                         // positional navigation
 | 
			
		||||
        return resolve(xrf.model)                         // eval non-positional fragments (no loader needed)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      xrf
 | 
			
		||||
      .emit('navigateLoading', {url,loader,data})
 | 
			
		||||
      .then( () => {
 | 
			
		||||
        if( (!fileChange || !file) && hashChange && hasPos ){                 // we're already loaded
 | 
			
		||||
        if( (!URI.fileChange || !file) && URI.hashChange && URI.hasPos ){                 // we're already loaded
 | 
			
		||||
          evalFragment()
 | 
			
		||||
          xrf.emit('navigateLoaded',{url})
 | 
			
		||||
          return resolve(xrf.model) 
 | 
			
		||||
        }
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
        // clear xrf objects from scene
 | 
			
		||||
        if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false
 | 
			
		||||
        xrf.reset() 
 | 
			
		||||
| 
						 | 
				
			
			@ -2069,11 +2075,6 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
        const onLoad = (model) => {
 | 
			
		||||
 | 
			
		||||
          model.file = URI.file
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          if( xrf.model ){
 | 
			
		||||
            xrf.navigator.pushState( external ? URI.URN + URI.file : URI.file, fragment )
 | 
			
		||||
          }
 | 
			
		||||
          //if( xrf.model ) xrf.navigator.pushState( `${ document.location.pathname != URI.directory ? URI.directory: ''}${URI.file}`, fragment )
 | 
			
		||||
          xrf.model = model 
 | 
			
		||||
 | 
			
		||||
          if( !model.isXRF ) xrf.parseModel(model,url.replace(directory,"")) // this marks the model as an XRF model
 | 
			
		||||
| 
						 | 
				
			
			@ -2088,12 +2089,17 @@ xrf.navigator.to = (url,flags,loader,data) => {
 | 
			
		|||
            model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) )
 | 
			
		||||
          }
 | 
			
		||||
          // spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          const defaultFragment = xrf.frag.defaultPredefinedViews({model,scene:model.scene})
 | 
			
		||||
          // spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view)
 | 
			
		||||
          let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments 
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
          xrf.add( model.scene )
 | 
			
		||||
          if( fragment ) xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          // only change url when loading *another* file
 | 
			
		||||
          fragment = fragment || defaultFragment || ''
 | 
			
		||||
          xrf.navigator.pushState( URI.external ? URI.URN + URI.file : URI.file, fragment.replace(/^#/,'') )
 | 
			
		||||
          //if( fragment )  xrf.navigator.updateHash(fragment)
 | 
			
		||||
 | 
			
		||||
          xrf.emit('navigateLoaded',{url,model})
 | 
			
		||||
          resolve(model)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2120,7 +2126,7 @@ xrf.navigator.init = () => {
 | 
			
		|||
 | 
			
		||||
  window.addEventListener('popstate', function (event){
 | 
			
		||||
    if( !xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/\?/,'') )
 | 
			
		||||
      xrf.navigator.to( document.location.href.replace(/.*\?/,'') )
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  
 | 
			
		||||
| 
						 | 
				
			
			@ -2356,13 +2362,16 @@ xrf.addEventListener('audioInited', function(opts){
 | 
			
		|||
 | 
			
		||||
xrf.frag.defaultPredefinedViews = (opts) => {
 | 
			
		||||
  let {scene,model} = opts;
 | 
			
		||||
  let defaultFragment;
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    if( n.userData && n.userData['#'] ){
 | 
			
		||||
      if( !n.parent && !document.location.hash ){
 | 
			
		||||
        xrf.navigator.to( n.userData['#'] )
 | 
			
		||||
      }else xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
      if( n.isXRFRoot ){ 
 | 
			
		||||
        defaultFragment = n.userData['#']
 | 
			
		||||
      }
 | 
			
		||||
      xrf.hashbus.pub( n.userData['#'], n )   // evaluate default XR fragments without affecting URL
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return defaultFragment
 | 
			
		||||
}
 | 
			
		||||
xrf.frag.loop = function(v, opts){
 | 
			
		||||
  let { frag, mesh, model, camera, scene, renderer, THREE} = opts
 | 
			
		||||
| 
						 | 
				
			
			@ -2402,7 +2411,7 @@ xrf.frag.pos = function(v, opts){
 | 
			
		|||
 | 
			
		||||
  if( xrf.debug ) console.log(`#pos.js: setting camera to position ${pos.x},${pos.y},${pos.z}`)
 | 
			
		||||
 | 
			
		||||
  xrf.frag.pos.last = pos // remember
 | 
			
		||||
  xrf.frag.pos.last = v.string // remember
 | 
			
		||||
 | 
			
		||||
  camera.updateMatrixWorld()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3146,6 +3155,17 @@ xrf.optimize.removeDuplicateLights = () => {
 | 
			
		|||
xrf.addEventListener('parseModel', (opts) => {
 | 
			
		||||
  xrf.optimize(opts)
 | 
			
		||||
})
 | 
			
		||||
xrf.sceneToTranscript = (scene, ignoreMesh ) => {
 | 
			
		||||
  let transcript = ''
 | 
			
		||||
  scene.traverse( (n) => {
 | 
			
		||||
    let isSRC = false
 | 
			
		||||
    n.traverseAncestors( (m) => m.userData.src ? isSRC = true : false )
 | 
			
		||||
    if( !isSRC && n.userData['aria-description'] && (!ignoreMesh || n.uuid != ignoreMesh.uuid) ){
 | 
			
		||||
      transcript += `<b>#${n.name}</b> ${n.userData['aria-description']}. `
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return transcript
 | 
			
		||||
}
 | 
			
		||||
// switch camera when multiple cameras for url #mycameraname
 | 
			
		||||
 | 
			
		||||
xrf.addEventListener('dynamicKey', (opts) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue