work in progress [might break]
This commit is contained in:
		
							parent
							
								
									b61f6371d0
								
							
						
					
					
						commit
						e2d1d47736
					
				
					 22 changed files with 2646 additions and 256 deletions
				
			
		| 
						 | 
				
			
			@ -15,7 +15,7 @@
 | 
			
		|||
    <script src="./../../../dist/xrfragment.aframe.js"></script>
 | 
			
		||||
    <script src="./../../../dist/xrfragment.extras.js"></script>
 | 
			
		||||
 | 
			
		||||
    <a-scene stats xr-mode-ui="XRMode: xr"  renderer="colorManagement: true; highRefreshRate:true" light="defaultLightsEnabled: false">
 | 
			
		||||
    <a-scene xr-mode-ui="XRMode: xr"  renderer="colorManagement: true; highRefreshRate:true" light="defaultLightsEnabled: false">
 | 
			
		||||
      <a-entity id="player" wasd-controls look-controls>
 | 
			
		||||
        <a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
 | 
			
		||||
        <a-entity id="left-hand"  laser-controls="hand: left" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
 | 
			
		||||
| 
						 | 
				
			
			@ -33,12 +33,12 @@
 | 
			
		|||
    <script>
 | 
			
		||||
      // xrfragment.extras.js is totally optional but can be handy
 | 
			
		||||
      // to quickly add a menu, logo, buttons, serverless meeting-functionality etc
 | 
			
		||||
      XRFMENU.logo      = './../../assets/logo.png'
 | 
			
		||||
      XRFMENU.morelabel = '⚡'
 | 
			
		||||
      $menu.logo      = 'logo_file_or_url_here'
 | 
			
		||||
      $menu.morelabel = '⚡'
 | 
			
		||||
      // add your menubuttons:
 | 
			
		||||
      XRFMENU.buttons = XRFMENU.buttons.concat([`<a class="btn" aria-label="button" aria-description="about menu" onclick="XRFMENU.showAbout()">💁 about</a><br>`])
 | 
			
		||||
      XRFMENU.collapsed = false
 | 
			
		||||
      XRFMENU.showAbout = () => window.notify(`
 | 
			
		||||
      $menu.buttons = $menu.buttons.concat([`<a class="btn" aria-label="button" aria-description="about menu" onclick="$menu.showAbout()">💁 about</a><br>`])
 | 
			
		||||
      $menu.collapsed = false
 | 
			
		||||
      $menu.showAbout = () => window.notify(`
 | 
			
		||||
          <h1>💁 Hi there!</h1>
 | 
			
		||||
 | 
			
		||||
          This XR fragments experience works almost anywhere.<br>
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +62,7 @@
 | 
			
		|||
          <br><br>
 | 
			
		||||
          `,{timeout:false})
 | 
			
		||||
 | 
			
		||||
      XRFMENU.install(xrf)
 | 
			
		||||
      $menu.install(xrf)
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
  </body>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								example/matrix/dist/matrix-crtd.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								example/matrix/dist/matrix-crtd.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										37
									
								
								example/matrix/dist/matrix-crtd.js.LICENSE.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								example/matrix/dist/matrix-crtd.js.LICENSE.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
/*!
 | 
			
		||||
 * The buffer module from node.js, for the browser.
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Feross Aboukhadijeh <https://feross.org>
 | 
			
		||||
 * @license  MIT
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * content-type
 | 
			
		||||
 * Copyright(c) 2015 Douglas Christopher Wilson
 | 
			
		||||
 * MIT Licensed
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! *****************************************************************************
 | 
			
		||||
Copyright (c) Microsoft Corporation.
 | 
			
		||||
 | 
			
		||||
Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
purpose with or without fee is hereby granted.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 | 
			
		||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 | 
			
		||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 | 
			
		||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 | 
			
		||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 | 
			
		||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 | 
			
		||||
PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
***************************************************************************** */
 | 
			
		||||
 | 
			
		||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
 | 
			
		||||
 | 
			
		||||
/*! queue-microtask. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
 | 
			
		||||
 | 
			
		||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
 | 
			
		||||
 | 
			
		||||
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
 | 
			
		||||
 | 
			
		||||
/*! simple-peer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
 | 
			
		||||
							
								
								
									
										6
									
								
								example/matrix/make
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								example/matrix/make
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
#!/bin/sh
 | 
			
		||||
version=v30.3.0
 | 
			
		||||
 | 
			
		||||
test -f ${version}.zip || wget https://github.com/matrix-org/matrix-js-sdk/archive/refs/tags/${version}.zip
 | 
			
		||||
test -d node_modules   || npm install
 | 
			
		||||
cat 
 | 
			
		||||
							
								
								
									
										2
									
								
								make
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								make
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -86,7 +86,7 @@ build(){
 | 
			
		|||
        example/assets/js/qr.js  > dist/xrfragment.aframe.js
 | 
			
		||||
 | 
			
		||||
    # html extras like menu & meetings
 | 
			
		||||
    cat src/3rd/js/extra/*.js  > dist/xrfragment.extras.js 
 | 
			
		||||
    cat src/3rd/js/extra/*.js dist/matrix-crdt.js src/3rd/js/extra/network/*.js dist/trystero-torrent.min.js > dist/xrfragment.extras.js 
 | 
			
		||||
    
 | 
			
		||||
    # fat all-in-one standalone xrf release
 | 
			
		||||
    test -f dist/aframe.min.js || {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,6 @@ window.AFRAME.registerComponent('xrf', {
 | 
			
		|||
      camera.setAttribute('xrf-fade','')
 | 
			
		||||
      AFRAME.fade = camera.components['xrf-fade']
 | 
			
		||||
 | 
			
		||||
      if( document.location.host.match(/localhost/) ) document.querySelector('a-scene').setAttribute("stats",'')
 | 
			
		||||
 | 
			
		||||
      let aScene = document.querySelector('a-scene')
 | 
			
		||||
      aScene.addEventListener('loaded', () => {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										237
									
								
								src/3rd/js/extra/$chat.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								src/3rd/js/extra/$chat.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,237 @@
 | 
			
		|||
chatComponent = {
 | 
			
		||||
 | 
			
		||||
  html: `
 | 
			
		||||
     <div id="videos" style="pointer-events:none"></div>
 | 
			
		||||
     <div id="messages" aria-live="assertive" aria-relevant></div>
 | 
			
		||||
     <div id="chatfooter">
 | 
			
		||||
       <div id="chatbar">
 | 
			
		||||
           <input id="chatline" type="text" placeholder="type here"></input>
 | 
			
		||||
       </div>
 | 
			
		||||
       <button id="showchat" class="btn">show chat</button>
 | 
			
		||||
     </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  `,
 | 
			
		||||
 | 
			
		||||
  init: (el) => new Proxy({
 | 
			
		||||
 | 
			
		||||
    scene:    null,
 | 
			
		||||
    visible:  true,
 | 
			
		||||
    messages: [],
 | 
			
		||||
 | 
			
		||||
    $messages: $messages = el.querySelector("#messages"),
 | 
			
		||||
    $chatline: $chatline = el.querySelector("#chatline"),
 | 
			
		||||
    
 | 
			
		||||
    install(opts){
 | 
			
		||||
      this.opts  = opts
 | 
			
		||||
      this.scene = opts.scene 
 | 
			
		||||
      el.className = "xrf"
 | 
			
		||||
      el.style.display = 'none' // start hidden 
 | 
			
		||||
      document.body.appendChild( el )
 | 
			
		||||
      document.dispatchEvent( new CustomEvent("$chat:ready", {detail: opts}) )
 | 
			
		||||
      $chat.send({message:`Welcome to <b>${document.location.search.substr(1)}</b>, a 3D scene(file) which simply links to others.<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","multiline"] })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initListeners(){
 | 
			
		||||
      //opts.scene.addEventListener('meeting.peer.add',    () => console.log("$chat.peer.add") )
 | 
			
		||||
      //opts.scene.addEventListener('meeting.peer.remove', () => console.log("$chat.peer.remove") )
 | 
			
		||||
      $chatline.addEventListener('keydown', (e) => {
 | 
			
		||||
        if (e.key == 'Enter' ){
 | 
			
		||||
          this.send({message: $chatline.value })
 | 
			
		||||
          $chatline.value = ''
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      console.dir(this.scene)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    toggle(){
 | 
			
		||||
      this.visible = !this.visible
 | 
			
		||||
      if( this.visible && window.meeting.status == 'offline' ) window.meeting.start(this.opts)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    send(opts){
 | 
			
		||||
      opts = { linebreak:true, message:"", class:[], ...opts }
 | 
			
		||||
      let msg = document.createElement('div')
 | 
			
		||||
      let br  = document.createElement('br')
 | 
			
		||||
      msg.className = "msg"
 | 
			
		||||
      let html = `${ opts.message || ''}${ opts.html ? opts.html(opts) : ''}`
 | 
			
		||||
      if( $messages.last == html ) return
 | 
			
		||||
      msg.innerHTML = html 
 | 
			
		||||
      if( opts.el ) msg.appendChild(opts.el)
 | 
			
		||||
      opts.id       = Math.random()
 | 
			
		||||
      if( opts.class ){
 | 
			
		||||
        msg.classList.add.apply(msg.classList, opts.class)
 | 
			
		||||
        br.classList.add.apply(br.classList, opts.class)
 | 
			
		||||
      }
 | 
			
		||||
      $messages.appendChild(msg)
 | 
			
		||||
      if( opts.linebreak ) $messages.appendChild(br)
 | 
			
		||||
      $messages.scrollTop = $messages.scrollHeight // scroll down
 | 
			
		||||
      document.dispatchEvent( new CustomEvent("$chat:receive", {detail: opts}) )
 | 
			
		||||
      $messages.last = msg.innerHTML
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  },{
 | 
			
		||||
 | 
			
		||||
    get(data,k,v){ return data[k] },
 | 
			
		||||
    set(data,k,v){ 
 | 
			
		||||
      data[k] = v    
 | 
			
		||||
      switch( k ){
 | 
			
		||||
        case "visible": { 
 | 
			
		||||
                           el.style.display = data.visible ? 'block' : 'none'
 | 
			
		||||
                           if( !el.inited && (el.inited = true) ) data.initListeners()
 | 
			
		||||
                           $menu.collapsed = !data.visible
 | 
			
		||||
                           break;
 | 
			
		||||
                        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reactify component!
 | 
			
		||||
document.addEventListener('$menu:ready', (opts) => {
 | 
			
		||||
  opts = opts.detail
 | 
			
		||||
  document.head.innerHTML += chatComponent.css 
 | 
			
		||||
  $chat = document.createElement('div')
 | 
			
		||||
  $chat.innerHTML = chatComponent.html
 | 
			
		||||
  $chat = chatComponent.init($chat)
 | 
			
		||||
  $chat.install(opts)
 | 
			
		||||
  //$menu.buttons = ([`<a class="btn" aria-label="button" aria-description="toggle text" id="meeting" onclick="$chat.toggle()">📜 toggle text</a><br>`])
 | 
			
		||||
  //                  .concat($menu.buttons)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// alpine component for displaying meetings
 | 
			
		||||
 | 
			
		||||
chatComponent.css = `
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
     #videos{
 | 
			
		||||
       display:grid-auto-columns;
 | 
			
		||||
       grid-column-gap:5px;
 | 
			
		||||
       margin-bottom:15px;
 | 
			
		||||
       position: fixed;
 | 
			
		||||
       top: 0;
 | 
			
		||||
       left: 0;
 | 
			
		||||
       bottom: 0;
 | 
			
		||||
       right: 0;
 | 
			
		||||
       margin: 15px;
 | 
			
		||||
       z-index:1500;
 | 
			
		||||
     }
 | 
			
		||||
     #videos > video{
 | 
			
		||||
       border-radius:7px;
 | 
			
		||||
       display:inline-block;
 | 
			
		||||
       background:black;
 | 
			
		||||
       width:80px;
 | 
			
		||||
       height:60px;
 | 
			
		||||
       margin-right:5px;
 | 
			
		||||
       margin-bottom:5px;
 | 
			
		||||
       vertical-align:top;
 | 
			
		||||
       pointer-events:all;
 | 
			
		||||
     }
 | 
			
		||||
     #videos > video:hover{
 | 
			
		||||
       filter: brightness(1.8);
 | 
			
		||||
       cursor:pointer;
 | 
			
		||||
     }
 | 
			
		||||
 | 
			
		||||
     #chatbar,
 | 
			
		||||
     button#showchat{
 | 
			
		||||
       z-index: 1500;
 | 
			
		||||
       position: fixed;
 | 
			
		||||
       bottom: 20px;
 | 
			
		||||
       left: 20px;
 | 
			
		||||
       width: 48%;
 | 
			
		||||
       background: white;
 | 
			
		||||
       padding: 0px 0px 0px 15px; 
 | 
			
		||||
       border-radius: 30px;
 | 
			
		||||
       max-width: 500px;
 | 
			
		||||
       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;
 | 
			
		||||
     }
 | 
			
		||||
     #chatbar input{
 | 
			
		||||
       border:none;
 | 
			
		||||
       width:90%;
 | 
			
		||||
       box-sizing:border-box;
 | 
			
		||||
     }
 | 
			
		||||
     #messages{
 | 
			
		||||
       position: absolute;
 | 
			
		||||
       top: 100px;
 | 
			
		||||
       left: 0;
 | 
			
		||||
       right: 0;
 | 
			
		||||
       bottom: 88px;
 | 
			
		||||
       padding: 15px;
 | 
			
		||||
       pointer-events: none;
 | 
			
		||||
       overflow-y:auto;
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg{
 | 
			
		||||
       background: #fff;
 | 
			
		||||
       display: inline-block;
 | 
			
		||||
       padding: 6px 17px;
 | 
			
		||||
       border-radius: 20px;
 | 
			
		||||
       color: #000c;
 | 
			
		||||
       margin-bottom: 10px;
 | 
			
		||||
       line-height:23px;
 | 
			
		||||
       pointer-events:visible;
 | 
			
		||||
       border: 1px solid #ccc8;
 | 
			
		||||
       line-height:33px;
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg.info{
 | 
			
		||||
       border: 4px dotted #CCC
 | 
			
		||||
       font-size: 14px;
 | 
			
		||||
       padding: 3px 16px;
 | 
			
		||||
     }
 | 
			
		||||
     #messages.guide .guide{
 | 
			
		||||
      display:unset;
 | 
			
		||||
     }
 | 
			
		||||
     $message .guide, .guide{
 | 
			
		||||
       display:none;
 | 
			
		||||
     }
 | 
			
		||||
     br.guide{
 | 
			
		||||
       display:inline-block;
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg.info a,
 | 
			
		||||
     #messages .msg.info a:visited{
 | 
			
		||||
       color: var(--xrf-primary);
 | 
			
		||||
       text-decoration: underline;
 | 
			
		||||
       transition:0.3s;
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg.info a:hover,
 | 
			
		||||
     #messages button:hover{
 | 
			
		||||
       filter: brightness(1.4);
 | 
			
		||||
     }
 | 
			
		||||
     #messages .msg.multiline {
 | 
			
		||||
       padding: 2px 14px;
 | 
			
		||||
     }
 | 
			
		||||
     #messages button {
 | 
			
		||||
       text-decoration:none;
 | 
			
		||||
       margin: 0px 15px 10px 0px;
 | 
			
		||||
       background: var(--xrf-primary);
 | 
			
		||||
       font-family: var(--xrf-font-sans-serif);
 | 
			
		||||
       color: #FFF;
 | 
			
		||||
       border-radius: 7px;
 | 
			
		||||
       padding: 11px 15px;
 | 
			
		||||
       border: 0;
 | 
			
		||||
       font-weight: bold;
 | 
			
		||||
       box-shadow: 0px 0px 5px 5px #0002;
 | 
			
		||||
       pointer-events:all;
 | 
			
		||||
     }
 | 
			
		||||
     #messages,#chatbar,#chatbar *, #messages *{
 | 
			
		||||
     }
 | 
			
		||||
 | 
			
		||||
    #messages input{
 | 
			
		||||
      padding: 7px 15px;
 | 
			
		||||
      border-block: none;
 | 
			
		||||
      border-inline: none;
 | 
			
		||||
      border: 1px solid #888;
 | 
			
		||||
      background: var(--xrf-lighter-gray);
 | 
			
		||||
      height: 18px;
 | 
			
		||||
      max-width:168px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
   </style>`
 | 
			
		||||
							
								
								
									
										114
									
								
								src/3rd/js/extra/$connections.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/3rd/js/extra/$connections.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
connectionsComponent = {
 | 
			
		||||
 | 
			
		||||
  html: `
 | 
			
		||||
   <div id="connections">
 | 
			
		||||
      <h2>Network channels:</h2>
 | 
			
		||||
      <table>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td>Webcam</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            <select id="webcam"></select>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td>Chat</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            <select id="chatnetwork"></select>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td>Scene</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            <select id="scene"></select>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </table>
 | 
			
		||||
      <div id="settings"></div>
 | 
			
		||||
      <br>
 | 
			
		||||
      <button id="connect" onclick="$connections.connect()">📡 Connect!</button>
 | 
			
		||||
      <br>
 | 
			
		||||
    </div>
 | 
			
		||||
  `,
 | 
			
		||||
 | 
			
		||||
  init: (el) => new Proxy({
 | 
			
		||||
 | 
			
		||||
    webcam:       [{plugin:{name:"No thanks"},config(){}}],
 | 
			
		||||
    chatnetwork:  [{plugin:{name:"No thanks"},config(){}}],
 | 
			
		||||
    scene:        [{plugin:{name:"No thanks"},config(){}}],
 | 
			
		||||
 | 
			
		||||
    $webcam:       $webcam      = el.querySelector("#webcam"),
 | 
			
		||||
    $chatnetwork:  $chatnetwork = el.querySelector("#chatnetwork"),
 | 
			
		||||
    $scene:        $scene       = el.querySelector("#scene"),
 | 
			
		||||
    $settings:     $settings    = el.querySelector("#settings"),
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    install(opts){
 | 
			
		||||
      this.opts  = opts
 | 
			
		||||
      document.dispatchEvent( new CustomEvent("$connections:ready", {detail: opts}) )
 | 
			
		||||
 | 
			
		||||
      $webcam.addEventListener('change',      () => this.renderSettings() ) 
 | 
			
		||||
      $chatnetwork.addEventListener('change', () => this.renderSettings() ) 
 | 
			
		||||
      $scene.addEventListener('change',       () => this.renderSettings() ) 
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    show(){
 | 
			
		||||
      if( !network.connected ){
 | 
			
		||||
          $chat.send({message:"", el})
 | 
			
		||||
          this.renderSettings()
 | 
			
		||||
      }else $chat.send({message:"you are already connected, refresh page to create new connection",class:['info']})
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    connect(){
 | 
			
		||||
      navigator.share({
 | 
			
		||||
        url: 'https://foo.com',
 | 
			
		||||
        title: 'your meeting link'
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    renderSettings(){
 | 
			
		||||
      let opts = {webcam: $webcam.value, chatnetwork: $chatnetwork.value, scene: $scene.value }
 | 
			
		||||
      let theWebcam      = this.webcam.find(      (p) => p.plugin.name == $webcam.value )
 | 
			
		||||
      let theChatnetwork = this.chatnetwork.find( (p) => p.plugin.name == $chatnetwork.value )
 | 
			
		||||
      let theScene       = this.scene.find(       (p) => p.plugin.name == $scene.value )
 | 
			
		||||
      $settings.innerHTML = ''
 | 
			
		||||
      $settings.appendChild(theWebcam.config(opts))
 | 
			
		||||
      if( theChatnetwork.plugin.name != theWebcam.plugin.name ) $settings.appendChild( theChatnetwork.config(opts) )
 | 
			
		||||
      if( theScene.plugin.name != theWebcam.plugin.name && theScene.plugin.name != theChatnetwork.plugin.name ) 
 | 
			
		||||
        $settings.appendChild( scene.config(opts) )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  },{
 | 
			
		||||
 | 
			
		||||
    get(data,k,v){ return data[k] },
 | 
			
		||||
    set(data,k,v){ 
 | 
			
		||||
      data[k] = v 
 | 
			
		||||
      switch( k ){
 | 
			
		||||
        case "webcam":      $webcam.innerHTML       = `<option>${data[k].map((p)=>p.plugin.name).join('</option><option>')}</option>`; break;
 | 
			
		||||
        case "chatnetwork": $chatnetwork.innerHTML  = `<option>${data[k].map((p)=>p.plugin.name).join('</option><option>')}</option>`; break;
 | 
			
		||||
        case "scene":       $scene.innerHTML        = `<option>${data[k].map((p)=>p.plugin.name).join('</option><option>')}</option>`; break;
 | 
			
		||||
      } 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reactify component!
 | 
			
		||||
document.addEventListener('network:ready', (opts) => {
 | 
			
		||||
  opts = opts.detail
 | 
			
		||||
  document.head.innerHTML += connectionsComponent.css 
 | 
			
		||||
  $connections = document.createElement('div')
 | 
			
		||||
  $connections.innerHTML = connectionsComponent.html
 | 
			
		||||
  $connections = connectionsComponent.init($connections)
 | 
			
		||||
  $connections.install(opts)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// alpine component for displaying meetings
 | 
			
		||||
 | 
			
		||||
connectionsComponent.css = `
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
      button#connect{
 | 
			
		||||
        float: right;
 | 
			
		||||
        height: 43px;
 | 
			
		||||
        margin: 0px;
 | 
			
		||||
      }
 | 
			
		||||
   </style>`
 | 
			
		||||
| 
						 | 
				
			
			@ -1,173 +0,0 @@
 | 
			
		|||
MEETING = {
 | 
			
		||||
 | 
			
		||||
  html: `
 | 
			
		||||
     <div id="videos" style="pointer-events:none"></div>
 | 
			
		||||
     <div id="chat" aria-live="assertive" aria-relevant></div>
 | 
			
		||||
     <div id="chatfooter">
 | 
			
		||||
       <div id="chatbar">
 | 
			
		||||
           <input id="chatline" type="text" placeholder=""></input>
 | 
			
		||||
       </div>
 | 
			
		||||
       <button id="showchat" class="btn">show chat</button>
 | 
			
		||||
     </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  `,
 | 
			
		||||
 | 
			
		||||
  init: (el) => new Proxy({
 | 
			
		||||
 | 
			
		||||
    scene:    null,
 | 
			
		||||
    visible:  false,
 | 
			
		||||
    //$overlay: $overlay = el.querySelector('#overlay'),
 | 
			
		||||
    //
 | 
			
		||||
    install(opts){
 | 
			
		||||
      this.scene = opts.scene 
 | 
			
		||||
 | 
			
		||||
      document.body.appendChild( el )
 | 
			
		||||
      document.dispatchEvent( new CustomEvent("MEETING:ready", {detail: opts}) )
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    start(){
 | 
			
		||||
      this.scene.addEventListener('meeting.peer.add',    () => console.log("$meeting.peer.add") )
 | 
			
		||||
      this.scene.addEventListener('meeting.peer.remove', () => console.log("$meeting.peer.remove") )
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    toggle:   () => MEETING.visible = !MEETING.visible,
 | 
			
		||||
 | 
			
		||||
  },{
 | 
			
		||||
 | 
			
		||||
    get(data,k,v){ return data[k] },
 | 
			
		||||
    set(data,k,v){ 
 | 
			
		||||
      data[k] = v    
 | 
			
		||||
      switch( k ){
 | 
			
		||||
        case "visible": el.style.display = data.visible ? 'block' : 'none'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reactify component!
 | 
			
		||||
document.addEventListener('XRFMENU:ready', (opts) => {
 | 
			
		||||
  opts = opts.detail
 | 
			
		||||
  XRFMENU.buttons = ([`<a class="btn" aria-label="button" aria-description="start text/audio/video chat" id="meeting" onclick="MEETING.toggle()" target="_blank">🧑🤝🧑 meeting</a><br>`])
 | 
			
		||||
                    .concat(XRFMENU.buttons)
 | 
			
		||||
  document.head.innerHTML += MEETING.css 
 | 
			
		||||
  $meeting = document.createElement('div')
 | 
			
		||||
  $meeting.innerHTML = MEETING.html
 | 
			
		||||
  MEETING = MEETING.init($meeting)
 | 
			
		||||
  MEETING.install(opts)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// alpine component for displaying meetings
 | 
			
		||||
 | 
			
		||||
MEETING.css = `
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
     #videos{
 | 
			
		||||
       display:grid-auto-columns;
 | 
			
		||||
       grid-column-gap:5px;
 | 
			
		||||
       margin-bottom:15px;
 | 
			
		||||
       position: fixed;
 | 
			
		||||
       top: 0;
 | 
			
		||||
       left: 0;
 | 
			
		||||
       bottom: 0;
 | 
			
		||||
       right: 0;
 | 
			
		||||
       margin: 15px;
 | 
			
		||||
       z-index:1500;
 | 
			
		||||
     }
 | 
			
		||||
     #videos > video{
 | 
			
		||||
       border-radius:7px;
 | 
			
		||||
       display:inline-block;
 | 
			
		||||
       background:black;
 | 
			
		||||
       width:80px;
 | 
			
		||||
       height:60px;
 | 
			
		||||
       margin-right:5px;
 | 
			
		||||
       margin-bottom:5px;
 | 
			
		||||
       vertical-align:top;
 | 
			
		||||
       pointer-events:all;
 | 
			
		||||
     }
 | 
			
		||||
     #videos > video:hover{
 | 
			
		||||
       filter: brightness(1.8);
 | 
			
		||||
       cursor:pointer;
 | 
			
		||||
     }
 | 
			
		||||
 | 
			
		||||
     #chatbar,
 | 
			
		||||
     button#showchat{
 | 
			
		||||
       z-index: 1500;
 | 
			
		||||
       position: fixed;
 | 
			
		||||
       bottom: 20px;
 | 
			
		||||
       left: 20px;
 | 
			
		||||
       width: 48%;
 | 
			
		||||
       background: white;
 | 
			
		||||
       padding: 0px 0px 0px 15px; 
 | 
			
		||||
       border-radius: 30px;
 | 
			
		||||
       max-width: 500px;
 | 
			
		||||
       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;
 | 
			
		||||
     }
 | 
			
		||||
     #chatbar input{
 | 
			
		||||
       border:none;
 | 
			
		||||
       width:90%;
 | 
			
		||||
       box-sizing:border-box;
 | 
			
		||||
     }
 | 
			
		||||
     #chat{
 | 
			
		||||
       position: absolute;
 | 
			
		||||
       top: 100px;
 | 
			
		||||
       left: 0;
 | 
			
		||||
       right: 0;
 | 
			
		||||
       bottom: 88px;
 | 
			
		||||
       padding: 15px;
 | 
			
		||||
       pointer-events: none;
 | 
			
		||||
       overflow-y:auto;
 | 
			
		||||
     }
 | 
			
		||||
     #chat .msg{
 | 
			
		||||
       background: #fff;
 | 
			
		||||
       display: inline-block;
 | 
			
		||||
       padding: 6px 17px;
 | 
			
		||||
       border-radius: 20px;
 | 
			
		||||
       color: #000c;
 | 
			
		||||
       margin-bottom: 10px;
 | 
			
		||||
       line-height:23px;
 | 
			
		||||
       pointer-events:visible;
 | 
			
		||||
       border: 1px solid #ccc;
 | 
			
		||||
     }
 | 
			
		||||
     #chat .msg.info{
 | 
			
		||||
       background: #333;
 | 
			
		||||
       color: #FFF;
 | 
			
		||||
       font-size: 14px;
 | 
			
		||||
       padding: 0px 16px;
 | 
			
		||||
     }
 | 
			
		||||
     #chat .msg.info a,
 | 
			
		||||
     #chat .msg.info a:visited{
 | 
			
		||||
       color: #aaf;
 | 
			
		||||
       text-decoration: none;
 | 
			
		||||
       transition:0.3s;
 | 
			
		||||
     }
 | 
			
		||||
     #chat .msg.info a:hover,
 | 
			
		||||
     #chat button:hover{
 | 
			
		||||
       filter: brightness(1.8);
 | 
			
		||||
       text-decoration: underline;
 | 
			
		||||
     }
 | 
			
		||||
     #chat button {
 | 
			
		||||
       margin: 0px 15px 10px 0px;
 | 
			
		||||
       background: #07F;
 | 
			
		||||
       color: #FFF;
 | 
			
		||||
       border-radius: 7px;
 | 
			
		||||
       padding: 11px 15px;
 | 
			
		||||
       border: 0;
 | 
			
		||||
       font-weight: bold;
 | 
			
		||||
       box-shadow: 0px 0px 5px 5px #0002;
 | 
			
		||||
       pointer-events:all;
 | 
			
		||||
     }
 | 
			
		||||
     #chat,#chatbar,#chatbar *, #chat *{
 | 
			
		||||
       font-family:monospace;
 | 
			
		||||
       font-size:15px;
 | 
			
		||||
     }
 | 
			
		||||
   </style>`
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
// reactive component for displaying the menu 
 | 
			
		||||
XRFMENU = {
 | 
			
		||||
menuComponent = {
 | 
			
		||||
 | 
			
		||||
  html: `
 | 
			
		||||
    <div id="overlay" class="xrf">
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ XRFMENU = {
 | 
			
		|||
    <div class="xrf footer">
 | 
			
		||||
      <div class="menu">
 | 
			
		||||
        <div id="buttons"></div>
 | 
			
		||||
        <a class="btn" id="more" onclick="XRFMENU.toggle()"></a><br>
 | 
			
		||||
        <a class="btn" id="more" aria-description="menu with options, like extra accessibility" onclick="$menu.toggle()"></a><br>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  `,
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ XRFMENU = {
 | 
			
		|||
    morelabel:  '⚡',
 | 
			
		||||
    collapsed:    false,
 | 
			
		||||
    logo:       './../../assets/logo.png',
 | 
			
		||||
    buttons:    [`<a class="btn" aria-label="button" aria-description="share URL/screenshot/embed"  id="share"   onclick="XRFMENU.share()">🔗 share</a><br>`],
 | 
			
		||||
    buttons:    [`<a class="btn" aria-label="button" aria-description="share URL/screenshot/embed"  id="share"   onclick="$menu.share()">🔗 share</a><br>`],
 | 
			
		||||
 | 
			
		||||
    $overlay: $overlay = el.querySelector('#overlay'),
 | 
			
		||||
    $logo:    $logo    = el.querySelector('.logo'),
 | 
			
		||||
| 
						 | 
				
			
			@ -29,12 +29,29 @@ XRFMENU = {
 | 
			
		|||
    $buttons: $buttons = el.querySelector('#buttons'),
 | 
			
		||||
    $btnMore: $btnMore = el.querySelector('#more'),
 | 
			
		||||
 | 
			
		||||
    toggle:   () => XRFMENU.collapsed = !XRFMENU.collapsed,
 | 
			
		||||
    install:  (opts) => {
 | 
			
		||||
      XRFMENU.bindToWindow() // bind functions like notify to window 
 | 
			
		||||
    toggle:   () => $menu.collapsed = !$menu.collapsed,
 | 
			
		||||
    install:  (xrf) => {
 | 
			
		||||
      this.xrf = xrf
 | 
			
		||||
      $menu.bindToWindow() // bind functions like notify to window 
 | 
			
		||||
      window.notify('loading '+document.location.search.substr(1))
 | 
			
		||||
      document.body.appendChild(el)
 | 
			
		||||
      document.dispatchEvent( new CustomEvent("XRFMENU:ready", {detail: opts}) )
 | 
			
		||||
      document.dispatchEvent( new CustomEvent("$menu:ready", {detail: xrf}) )
 | 
			
		||||
 | 
			
		||||
      // add screenshot component with camera to capture bigger size equirects
 | 
			
		||||
      // document.querySelector('a-scene').components.screenshot.capture('perspective')
 | 
			
		||||
      $('a-scene').setAttribute("screenshot",{camera: "[camera]",width: 4096*2, height:2048*2})
 | 
			
		||||
  
 | 
			
		||||
      if( window.outerWidth > 800 )
 | 
			
		||||
        setTimeout( () => window.notify("use WASD-keys and mouse-drag to move around",{timeout:false}),2000 )
 | 
			
		||||
  
 | 
			
		||||
      xrf.addEventListener('href', (data) => data.selected ? window.notify(`href: ${data.xrf.string}`) : false )
 | 
			
		||||
  
 | 
			
		||||
      // enable user-uploaded asset files
 | 
			
		||||
      let fileLoaders = $menu.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) )
 | 
			
		||||
      })
 | 
			
		||||
      el.querySelector("#overlay > input[type=submit]").addEventListener("click", fileLoaders )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  },{
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +63,9 @@ XRFMENU = {
 | 
			
		|||
        case "logo":       $logo.style.backgroundImage = `url(${v})`;     break;
 | 
			
		||||
        case "css":        document.head.innerHTML += v;                  break;
 | 
			
		||||
        case "morelabel":  $btnMore.innerText = data.morelabel;           break;
 | 
			
		||||
        case "buttons":    $buttons.innerHTML = this.renderButtons(data); break;
 | 
			
		||||
        case "buttons":    $buttons.innerHTML = this.renderButtons(data); 
 | 
			
		||||
                           document.dispatchEvent( new CustomEvent("$menu:buttons:render", {detail: el.querySelector('.menu') }) )
 | 
			
		||||
                           break;
 | 
			
		||||
        case "collapsed":  $overlay.style.display = data.collapsed ? 'block' : 'none'
 | 
			
		||||
                           $buttons.style.display = data.collapsed ? 'block' : 'none'
 | 
			
		||||
                           break;
 | 
			
		||||
| 
						 | 
				
			
			@ -59,22 +78,25 @@ XRFMENU = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// reactify component!
 | 
			
		||||
$xrfmenu = document.createElement('div')
 | 
			
		||||
$xrfmenu.innerHTML = XRFMENU.html
 | 
			
		||||
XRFMENU = XRFMENU.init($xrfmenu)
 | 
			
		||||
$menu = document.createElement('div')
 | 
			
		||||
$menu.innerHTML = menuComponent.html
 | 
			
		||||
$menu = menuComponent.init($menu)
 | 
			
		||||
 | 
			
		||||
// attach menu functions which are less related to rendering 
 | 
			
		||||
let utils = {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  bindToWindow(opts){
 | 
			
		||||
 | 
			
		||||
    window.notify   = XRFMENU.notify(window)
 | 
			
		||||
    window.notify   = $menu.notify(window)
 | 
			
		||||
 | 
			
		||||
    // reroute console messages to snackbar notifications
 | 
			
		||||
    console.log = ( (log) => function(str){
 | 
			
		||||
      if( String(str).match(/(:.*#|note:)/) ) window.notify(str)
 | 
			
		||||
      log(str)
 | 
			
		||||
    })(console.log)
 | 
			
		||||
    console.log = ( (log,console) => function(str){
 | 
			
		||||
      if( String(str).match(/(:.*#|note:|:\/\/)/) ) window.notify( str )
 | 
			
		||||
      log.call(console,str)
 | 
			
		||||
    })(console.log, console)
 | 
			
		||||
 | 
			
		||||
    // allow iframe to open url
 | 
			
		||||
    window.addEventListener('message', (event) => {
 | 
			
		||||
      if (event.data && event.data.url) {
 | 
			
		||||
| 
						 | 
				
			
			@ -242,7 +264,8 @@ let utils = {
 | 
			
		|||
  },
 | 
			
		||||
 | 
			
		||||
  notify(scope){
 | 
			
		||||
    return function notify(str,opts){
 | 
			
		||||
    return function notify(_str,opts){
 | 
			
		||||
      str = _str.replace(/(^\w+):/,"<div class='badge'>\$1</div>") 
 | 
			
		||||
      opts = opts || {status:'info'}        
 | 
			
		||||
      opts = Object.assign({ status, timeout:4000 },opts)
 | 
			
		||||
      if( typeof str == 'string' ){
 | 
			
		||||
| 
						 | 
				
			
			@ -252,7 +275,9 @@ let utils = {
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
      opts.message = str
 | 
			
		||||
      window.XRFMENU.SnackBar( opts )
 | 
			
		||||
      window.$menu.SnackBar( opts )
 | 
			
		||||
      opts.message = _str
 | 
			
		||||
      document.dispatchEvent( new CustomEvent("notify", {detail:opts}) )
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -290,7 +315,7 @@ let utils = {
 | 
			
		|||
    newHash += `&${lastPos}`
 | 
			
		||||
    document.location.hash = newHash.replace(/&&/,'&')
 | 
			
		||||
                                    .replace(/#&/,'')
 | 
			
		||||
    XRFMENU.copyToClipboard( window.location.href );
 | 
			
		||||
    $menu.copyToClipboard( window.location.href );
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  copyToClipboard(text){
 | 
			
		||||
| 
						 | 
				
			
			@ -306,13 +331,13 @@ let utils = {
 | 
			
		|||
  share(){
 | 
			
		||||
    let inMeeting = $('[meeting]')
 | 
			
		||||
    let url = window.location.href
 | 
			
		||||
    if( !inMeeting ) XRFMENU.updateHashPosition()
 | 
			
		||||
    if( !inMeeting ) $menu.updateHashPosition()
 | 
			
		||||
    else url = $('[meeting]').components['meeting'].data.link
 | 
			
		||||
    XRFMENU.copyToClipboard( url )
 | 
			
		||||
    $menu.copyToClipboard( url )
 | 
			
		||||
    // End of *TODO* 
 | 
			
		||||
    window.notify(`<h2>${ inMeeting ? 'Meeting link ' : 'Link'} copied to clipboard!</h2> <br>Now share it with your friends ❤️<br>
 | 
			
		||||
      <canvas id="qrcode" width="121" height="121"></canvas><br>
 | 
			
		||||
      <button onclick="XRFMENU.download()">💾 download scene file</button> <br>
 | 
			
		||||
      <button onclick="$menu.download()">💾 download scene file</button> <br>
 | 
			
		||||
      <button onclick="alert('this might take a while'); $('a-scene').components.screenshot.capture('equirectangular')">📷 download 360 screenshot</button> <br>
 | 
			
		||||
      <a class="btn" target="_blank" href="https://github.com/coderofsalvation/xrfragment-helloworld">🖥 clone & selfhost this experience</a><br>
 | 
			
		||||
      <br>
 | 
			
		||||
| 
						 | 
				
			
			@ -330,7 +355,7 @@ let utils = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// map to component
 | 
			
		||||
for( let i in utils ) XRFMENU[i] = utils[i]
 | 
			
		||||
for( let i in utils ) $menu[i] = utils[i]
 | 
			
		||||
 | 
			
		||||
//$('a-scene').addEventListener('XRF', this.onXRFready )
 | 
			
		||||
//    
 | 
			
		||||
| 
						 | 
				
			
			@ -359,7 +384,7 @@ for( let i in utils ) XRFMENU[i] = utils[i]
 | 
			
		|||
//    //  $('a#meeting').addEventListener('click', () => {
 | 
			
		||||
//    //    if( aScene.getAttribute('meeting') ){ // meeting already, start breakout room
 | 
			
		||||
//    //      let parentRoom = document.location.href
 | 
			
		||||
//    //      XRFMENU.updateHashPosition(true) 
 | 
			
		||||
//    //      $menu.updateHashPosition(true) 
 | 
			
		||||
//    //      let meeting = $('[meeting]').components['meeting']
 | 
			
		||||
//    //      meeting.data.parentRoom = parentRoom
 | 
			
		||||
//    //      meeting.update()
 | 
			
		||||
| 
						 | 
				
			
			@ -392,7 +417,7 @@ for( let i in utils ) XRFMENU[i] = utils[i]
 | 
			
		|||
//    window.AFRAME.XRF.addEventListener('href', (data) => data.selected ? window.notify(`href: ${data.xrf.string}`) : false )
 | 
			
		||||
//
 | 
			
		||||
//    // enable user-uploaded asset files
 | 
			
		||||
//    let fileLoaders = XRFMENU.loadFile({
 | 
			
		||||
//    let fileLoaders = $menu.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) )
 | 
			
		||||
//    })
 | 
			
		||||
| 
						 | 
				
			
			@ -400,7 +425,7 @@ for( let i in utils ) XRFMENU[i] = utils[i]
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
// finally add some css 
 | 
			
		||||
XRFMENU.css = `
 | 
			
		||||
$menu.css = `
 | 
			
		||||
  <style type="text/css">
 | 
			
		||||
    :root {
 | 
			
		||||
        --xrf-primary: #6839dc;
 | 
			
		||||
| 
						 | 
				
			
			@ -411,26 +436,25 @@ XRFMENU.css = `
 | 
			
		|||
        --xrf-overlay-bg: #fffb;
 | 
			
		||||
        --xrf-box-shadow: #0005;
 | 
			
		||||
        --xrf-red: red;
 | 
			
		||||
        --xrf-black: #424280;
 | 
			
		||||
        --xrf-white: #fdfdfd;
 | 
			
		||||
        --xrf-dark-gray: #343334;
 | 
			
		||||
        --xrf-gray: #ecf7ff47;
 | 
			
		||||
        --xrf-gray: #424280;
 | 
			
		||||
        --xrf-white: #fdfdfd;
 | 
			
		||||
        --xrf-light-gray: #efefef;
 | 
			
		||||
        --xrf-lighter-gray: #e4e2fb96;
 | 
			
		||||
        --xrf-font-sans-serif: system-ui, -apple-system, segoe ui, roboto, ubuntu, helvetica, cantarell, noto sans, sans-serif;
 | 
			
		||||
        --xrf-font-monospace: menlo, monaco, lucida console, liberation mono, dejavu sans mono, bitstream vera sans mono, courier new, monospace, serif;
 | 
			
		||||
        --xrf-font-size-0: 12px;
 | 
			
		||||
        --xrf-font-size-1: 14px;
 | 
			
		||||
        --xrf-font-size-2: 17px;
 | 
			
		||||
        --xrf-font-size-3: 21px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* CSS reset */
 | 
			
		||||
    html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type="button"],[type="reset"],[type="submit"],button{-webkit-appearance:button}[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
 | 
			
		||||
 | 
			
		||||
    .xrf table tr td{
 | 
			
		||||
      vertical-align:top;
 | 
			
		||||
    }
 | 
			
		||||
    .xrf table tr td:nth-child(1){
 | 
			
		||||
      padding-right:35px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .xrf button,
 | 
			
		||||
    .xrf input[type="submit"],
 | 
			
		||||
    .xrf .btn {
 | 
			
		||||
| 
						 | 
				
			
			@ -455,6 +479,7 @@ XRFMENU.css = `
 | 
			
		|||
    .xrf input[type="submit"]:hover,
 | 
			
		||||
    .xrf .btn:hover {
 | 
			
		||||
      background: var(--xrf-secondary);
 | 
			
		||||
      text-decoration:none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .xrf, .xrf *{
 | 
			
		||||
| 
						 | 
				
			
			@ -561,7 +586,7 @@ XRFMENU.css = `
 | 
			
		|||
      height:33px;
 | 
			
		||||
      z-index:2000;
 | 
			
		||||
      cursor:pointer;
 | 
			
		||||
      min-width:107px;
 | 
			
		||||
      min-width:130px;
 | 
			
		||||
      text-decoration:none;
 | 
			
		||||
      margin-top: 15px;
 | 
			
		||||
      line-height:36px;
 | 
			
		||||
| 
						 | 
				
			
			@ -570,6 +595,7 @@ XRFMENU.css = `
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    .xrf a.btn#more{
 | 
			
		||||
      z-index:3000;
 | 
			
		||||
      width: 19px;
 | 
			
		||||
      min-width: 19px;
 | 
			
		||||
      font-size:16px;
 | 
			
		||||
| 
						 | 
				
			
			@ -768,5 +794,39 @@ XRFMENU.css = `
 | 
			
		|||
      text-align:right;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .badge {
 | 
			
		||||
      display:inline-block;
 | 
			
		||||
      color: var(--xrf-white);
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
      background: var(--xrf-gray);
 | 
			
		||||
      border-radius:5px;
 | 
			
		||||
      padding:0px 4px;
 | 
			
		||||
      font-size: var(--xrf-font-size-0);
 | 
			
		||||
      margin-right:10px
 | 
			
		||||
    }
 | 
			
		||||
    a.badge {
 | 
			
		||||
      text-decoration:none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .xrf select{
 | 
			
		||||
      min-width: 200px;oborder-inline: none;
 | 
			
		||||
      border-inline: none;
 | 
			
		||||
      border-block: none;
 | 
			
		||||
      border: 1px solid #AAA;
 | 
			
		||||
      box-shadow: 0px 0px 5px #0003;
 | 
			
		||||
      height: 31px;
 | 
			
		||||
      border-radius: 5px;
 | 
			
		||||
      background: var(--xrf-lighter-gray);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .xrf table tr td {
 | 
			
		||||
       vertical-align:middle;
 | 
			
		||||
    }
 | 
			
		||||
    .xrf table tr td:nth-child(1){
 | 
			
		||||
      padding-right:35px;
 | 
			
		||||
      min-width:80px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  </style>
 | 
			
		||||
`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										143
									
								
								src/3rd/js/extra/accessibility.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/3rd/js/extra/accessibility.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,143 @@
 | 
			
		|||
window.accessibility = (opts) => new Proxy({
 | 
			
		||||
  opts,
 | 
			
		||||
 | 
			
		||||
  enabled: false,
 | 
			
		||||
 | 
			
		||||
  // features
 | 
			
		||||
  speak_movements: true,
 | 
			
		||||
  speak_keyboard: true,
 | 
			
		||||
  speak_notifications: true,
 | 
			
		||||
 | 
			
		||||
  // audio settings
 | 
			
		||||
  speak_rate: 1,
 | 
			
		||||
  speak_pitch: 1,
 | 
			
		||||
  speak_volume: 1,
 | 
			
		||||
  speak_voice: -1,
 | 
			
		||||
 | 
			
		||||
  toggle(){ this.enabled = !this.enabled },
 | 
			
		||||
 | 
			
		||||
  settings(){
 | 
			
		||||
    this.toggle() // *TODO* should show settings screen 
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  speak(str, override){
 | 
			
		||||
    if( !this.enabled || !str) return
 | 
			
		||||
    str = str.replace(/\/\//,' ')
 | 
			
		||||
             .replace(/:/,'')
 | 
			
		||||
             .replace(/\//,' slash ')
 | 
			
		||||
             .replace(/\./,' dot ')
 | 
			
		||||
             .replace(/#/,' hash ')
 | 
			
		||||
             .replace(/&/,' and ')
 | 
			
		||||
             .replace(/=/,' is ')
 | 
			
		||||
    let speech = window.speechSynthesis
 | 
			
		||||
    let utterance = new SpeechSynthesisUtterance( str )
 | 
			
		||||
    if( this.speak_voice != -1) 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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    utterance.rate   = this.speak_rate
 | 
			
		||||
    utterance.pitch  = this.speak_pitch
 | 
			
		||||
    utterance.volume = this.speak_volume
 | 
			
		||||
    if( override ) speech.cancel() 
 | 
			
		||||
    speech.speak(utterance)
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  init(){
 | 
			
		||||
 | 
			
		||||
    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,true)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('notify', (e) => {
 | 
			
		||||
      let opts    = e.detail
 | 
			
		||||
      let status  = `${opts.status} ` || ''
 | 
			
		||||
      if( !this.enabled ) return
 | 
			
		||||
      this.speak(opts.message)
 | 
			
		||||
      $chat.send({message: opts.message, class:["info","guide"]})
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    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('$chat:receive', (e) => {
 | 
			
		||||
      let opts = e.detail
 | 
			
		||||
      opts.message = opts.message || ''
 | 
			
		||||
      if( opts.class && ~opts.class.indexOf('info') ) opts.message = `info: ${opts.message}`
 | 
			
		||||
      this.speak(e.detail.message)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    opts.addEventListener('pos', (opts) => {
 | 
			
		||||
      let obj
 | 
			
		||||
      let description
 | 
			
		||||
      let msg = "You've 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 your teleportation was refused because it cannot be found within this world"
 | 
			
		||||
      } 
 | 
			
		||||
      $chat.send({html: () => msg, class:["info","guide"]})
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  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": {
 | 
			
		||||
                        let message = (v?"boosting":"unboosting") + " accessibility features"
 | 
			
		||||
                        $('#accessibility.btn').style.filter= v ? 'brightness(1.0)' : 'brightness(0.5)'
 | 
			
		||||
                        if( v ) $chat.visible = true
 | 
			
		||||
                        $chat.send({message,class:['info','guide']})
 | 
			
		||||
                        data.enabled = true
 | 
			
		||||
                        data.speak(message)
 | 
			
		||||
                        data.enabled = v
 | 
			
		||||
                        $chat.$messages.classList[ v ? 'add' : 'remove' ]('guide')
 | 
			
		||||
                        if( !data.readTranscript && (data.readTranscript = true) ){
 | 
			
		||||
                          data.speak( data.sanitizeTranscript() )
 | 
			
		||||
                        }
 | 
			
		||||
                      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
document.addEventListener('$menu:ready', (e) => {
 | 
			
		||||
  window.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()">👩🚀 accessibility</a><br>`])
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -1,38 +0,0 @@
 | 
			
		|||
// this orchestrates multiplayer events from the scene graph
 | 
			
		||||
 | 
			
		||||
window.meeting = (THREE, scene) => new Proxy({
 | 
			
		||||
 
 | 
			
		||||
  status: 'offline',
 | 
			
		||||
  peers: {},
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  add(peerid,data){
 | 
			
		||||
    data = {lastUpdated: new Date().getTime(), id: peerid, ...data }
 | 
			
		||||
    this.peers[peerid] = data 
 | 
			
		||||
    scene.dispatchEvent({type:'meeting.peer.add', peer})
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  remove(peerid,data){
 | 
			
		||||
    delete this.peers[peerid]
 | 
			
		||||
    scene.dispatchEvent({type:'meeting.peer.remove', peer})
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  send(opts){
 | 
			
		||||
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  receive(opts){
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
},
 | 
			
		||||
{ 
 | 
			
		||||
  // auto-trigger events on changes 
 | 
			
		||||
  get(meeting,k,receiver){ return meeting[k] },
 | 
			
		||||
  set(meeting,k,v){
 | 
			
		||||
    let from   = meeting[k]
 | 
			
		||||
    meeting[k] = v
 | 
			
		||||
    switch( k ){
 | 
			
		||||
      default: scene.dispatchEvent({type:`meeting.${k}.change`, from, to:v})
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										51
									
								
								src/3rd/js/extra/network.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/3rd/js/extra/network.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
// this orchestrates multiplayer events from the scene graph
 | 
			
		||||
 | 
			
		||||
window.network = (opts) => new Proxy({
 | 
			
		||||
 | 
			
		||||
  connected: false,
 | 
			
		||||
  peers: {},
 | 
			
		||||
  plugin: {},
 | 
			
		||||
  opts,
 | 
			
		||||
 | 
			
		||||
  start(url){
 | 
			
		||||
    console.log("starting network with url "+(url?url:"default"))
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  add(peerid,data){
 | 
			
		||||
    data = {lastUpdated: new Date().getTime(), id: peerid, ...data }
 | 
			
		||||
    this.peers[peerid] = data 
 | 
			
		||||
    opts.scene.dispatchEvent({type:'network.peer.add', peer})
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  remove(peerid,data){
 | 
			
		||||
    delete this.peers[peerid]
 | 
			
		||||
    opts.scene.dispatchEvent({type:'network.peer.remove', peer})
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  send(opts){
 | 
			
		||||
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  receive(opts){
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
},
 | 
			
		||||
{ 
 | 
			
		||||
  // auto-trigger events on changes 
 | 
			
		||||
  get(data,k,receiver){ return data[k] },
 | 
			
		||||
  set(data,k,v){
 | 
			
		||||
    let from   = data[k]
 | 
			
		||||
    data[k] = v
 | 
			
		||||
    //switch( k ){
 | 
			
		||||
    //  default: network.opts.scene.dispatchEvent({type:`network.${k}.change`, from, to:v})
 | 
			
		||||
    //}
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
      
 | 
			
		||||
document.addEventListener('$menu:ready', (e) => {
 | 
			
		||||
  window.network = network(e.detail) 
 | 
			
		||||
  document.dispatchEvent( new CustomEvent("network:ready", e ) )
 | 
			
		||||
  $menu.buttons = ([`<a class="btn" aria-label="button" aria-description="start text/audio/video chat" id="meeting" onclick="$connections.show()">🧑🤝🧑 connect</a><br>`])
 | 
			
		||||
                    .concat($menu.buttons)
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										63
									
								
								src/3rd/js/extra/network/matrix.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/3rd/js/extra/network/matrix.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
window.matrix = (opts) => new Proxy({
 | 
			
		||||
 | 
			
		||||
  plugin:{
 | 
			
		||||
    type: 'network',
 | 
			
		||||
    name: '[matrix] channel',
 | 
			
		||||
    description: '[matrix] is a standardized decentralized privacy-friendly protocol',
 | 
			
		||||
    url: 'https://matrix.org',
 | 
			
		||||
    video: false,
 | 
			
		||||
    audio: false,
 | 
			
		||||
    chat: true,
 | 
			
		||||
    scene: true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  html: {
 | 
			
		||||
    generic: (opts) => `<table>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td><a href="${opts.url}" target="_blank" class="badge">matrix</a></td>
 | 
			
		||||
            <td>
 | 
			
		||||
              <input type="text" id="channelname" placeholder="channel name"/>
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </table>
 | 
			
		||||
      </div>
 | 
			
		||||
    `
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  init(){
 | 
			
		||||
    let network = window.network
 | 
			
		||||
    network.plugin['matrix'] = this
 | 
			
		||||
    $connections.chatnetwork = $connections.chatnetwork.concat([this])
 | 
			
		||||
    $connections.scene       = $connections.scene.concat([this])
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  config(opts){
 | 
			
		||||
    opts = {...opts, ...this.plugin }
 | 
			
		||||
    let el   = document.createElement('div')
 | 
			
		||||
    let html = this.html.generic(opts)
 | 
			
		||||
    for( let i in opts ){
 | 
			
		||||
      if( this.html[i] ) html += this.html[i](opts)
 | 
			
		||||
    }
 | 
			
		||||
    el.innerHTML = html
 | 
			
		||||
    el.addEventListener('mouseover', () => {
 | 
			
		||||
      window.notify(`${opts.name} is ${opts.description}.<br>You can basically make up your own channelname or use an existing one`)
 | 
			
		||||
    })
 | 
			
		||||
    return el
 | 
			
		||||
  }
 | 
			
		||||
},
 | 
			
		||||
{ 
 | 
			
		||||
  // auto-trigger events on changes 
 | 
			
		||||
  get(data,k,receiver){ return data[k] },
 | 
			
		||||
  set(data,k,v){
 | 
			
		||||
    let from   = data[k]
 | 
			
		||||
    data[k] = v
 | 
			
		||||
    switch( k ){
 | 
			
		||||
      default: matrix.opts.scene.dispatchEvent({type:`matrix.${k}.change`, from, to:v})
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
      
 | 
			
		||||
document.addEventListener('$connections:ready', (e) => {
 | 
			
		||||
  matrix(e.detail).init()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								src/3rd/js/extra/network/matrix/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/3rd/js/extra/network/matrix/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
node_modules
 | 
			
		||||
							
								
								
									
										3
									
								
								src/3rd/js/extra/network/matrix/lib.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/3rd/js/extra/network/matrix/lib.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
export { MatrixProvider } from "matrix-crdt";
 | 
			
		||||
//export * as Y from "yjs";
 | 
			
		||||
//export sdk from "matrix-js-sdk";
 | 
			
		||||
							
								
								
									
										1777
									
								
								src/3rd/js/extra/network/matrix/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1777
									
								
								src/3rd/js/extra/network/matrix/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										17
									
								
								src/3rd/js/extra/network/matrix/package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/3rd/js/extra/network/matrix/package.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "matrix",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1",
 | 
			
		||||
    "build": "webpack"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "",
 | 
			
		||||
  "license": "ISC",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "matrix-crdt": "^0.2.1-alpha.1",
 | 
			
		||||
    "webpack-cli": "^5.1.4",
 | 
			
		||||
    "yjs": "^13.6.10"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/3rd/js/extra/network/matrix/webpack.config.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/3rd/js/extra/network/matrix/webpack.config.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
module.exports = {
 | 
			
		||||
  entry: './lib.js',
 | 
			
		||||
  output: {
 | 
			
		||||
    library: {
 | 
			
		||||
      type: "umd",
 | 
			
		||||
      name: "matrix"
 | 
			
		||||
    },
 | 
			
		||||
    filename: "matrix-crdt.js",
 | 
			
		||||
    path: require('path').resolve(__dirname, '../../../../../../dist')
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,85 @@
 | 
			
		|||
window.trystero = (opts) => new Proxy({
 | 
			
		||||
 | 
			
		||||
  plugin:{
 | 
			
		||||
    type: 'network',
 | 
			
		||||
    name: 'Peer2Peer',
 | 
			
		||||
    description: 'P2P using WebRTC over bittorrent for signaling & encryption',
 | 
			
		||||
    url: 'https://github.com/dmotz/trystero',
 | 
			
		||||
    video: true,
 | 
			
		||||
    audio: true,
 | 
			
		||||
    chat: true,
 | 
			
		||||
    scene: true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  html: {
 | 
			
		||||
    generic: (opts) => `<table id="trystero">
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td><a href="${opts.url}" target="_blank" class="badge">P2P</a></td>
 | 
			
		||||
            <td>
 | 
			
		||||
              <input type="text" id="channelname" placeholder="channel name"/>
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </table>
 | 
			
		||||
      </div>
 | 
			
		||||
    `
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  handle: null, // { selfId: .... } when connected
 | 
			
		||||
  link:   null,
 | 
			
		||||
  selfId: null,
 | 
			
		||||
  connected: false,
 | 
			
		||||
  names:  {},
 | 
			
		||||
  chat:   { send: null, get: null },
 | 
			
		||||
  name:   { send: null, get: null },
 | 
			
		||||
 | 
			
		||||
  init(){
 | 
			
		||||
    let network = window.network
 | 
			
		||||
    network.plugin['trystero'] = this
 | 
			
		||||
    $connections.webcam = $connections.webcam.concat([this])
 | 
			
		||||
    $connections.chatnetwork = $connections.chatnetwork.concat([this])
 | 
			
		||||
    $connections.scene       = $connections.scene.concat([this])
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  connect(){
 | 
			
		||||
    // embedded https://github.com/dmotz/trystero (trystero-torrent.min.js build)
 | 
			
		||||
    console.dir(opts)
 | 
			
		||||
    this.handle     = joinRoom( room.config, room.link )
 | 
			
		||||
    this.send({message:'📡 [trystero] opening P2P WebRTC-channel via bittorrent',class:['info']})
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  send(opts){ $chat.send({...opts, source: 'trystero'}) },
 | 
			
		||||
 | 
			
		||||
  config(opts){
 | 
			
		||||
    opts = {...opts, ...this.plugin }
 | 
			
		||||
    let el   = document.createElement('div')
 | 
			
		||||
    let html = this.html.generic(opts)
 | 
			
		||||
    for( let i in opts ){
 | 
			
		||||
      if( this.html[i] ) html += this.html[i](opts)
 | 
			
		||||
    }
 | 
			
		||||
    el.innerHTML = html
 | 
			
		||||
    el.addEventListener('mouseover', () => {
 | 
			
		||||
      window.notify(`${opts.name} is ${opts.description} <br>by using a serverless technology called <a href="${opts.url}" target="_blank">trystero</a>.<br>You can basically make up your own channelname or choose an existing one`)
 | 
			
		||||
    })
 | 
			
		||||
    return el
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
},
 | 
			
		||||
{ 
 | 
			
		||||
  // auto-trigger events on changes 
 | 
			
		||||
  get(data,k,receiver){ return data[k] },
 | 
			
		||||
  set(data,k,v){
 | 
			
		||||
    let from   = data[k]
 | 
			
		||||
    data[k] = v
 | 
			
		||||
    switch( k ){
 | 
			
		||||
      default: trystero.opts.scene.dispatchEvent({type:`trystero.${k}.change`, from, to:v})
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
      
 | 
			
		||||
document.addEventListener('$connections:ready', (e) => {
 | 
			
		||||
  trystero(e.detail).init()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
//window.meeting = window.meeting||{}
 | 
			
		||||
//window.meeting.trystero = async function(el,com,data){
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +62,6 @@ xrf.frag.src.enableSourcePortation = (src) => {
 | 
			
		|||
xrf.frag.src.externalSRC = (url,frag,opts) => {
 | 
			
		||||
  fetch(url, { method: 'HEAD' })
 | 
			
		||||
  .then( (res) => {
 | 
			
		||||
    console.log(`loading src ${url}`)
 | 
			
		||||
    let mimetype = res.headers.get('Content-type')
 | 
			
		||||
    if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/)    ) mimetype = 'gltf'
 | 
			
		||||
    //if( url.match(/\.(fbx|stl|obj)$/) ) mimetype = 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue