268 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
 * v0.5.1 generated at Thu Aug  1 04:21:30 PM CEST 2024
 | 
						|
 * https://xrfragment.org
 | 
						|
 * SPDX-License-Identifier: AGPL-3.0-or-later
 | 
						|
 */
 | 
						|
// reactive component for displaying the menu 
 | 
						|
 | 
						|
$editorPopup = (el) => new Proxy({
 | 
						|
 | 
						|
  html: (opts) => `
 | 
						|
    <div>
 | 
						|
      <b>#${$editor.selected.name}</b>
 | 
						|
      <table class="editorPopup">
 | 
						|
        <tbody>
 | 
						|
          <tr>
 | 
						|
            <td><b class="badge">href</a></td>
 | 
						|
            <td>
 | 
						|
              <input type="text" id="href" placeholder="https://foo.com" maxlength="255" list="objects" 
 | 
						|
                     onkeydown="document.querySelector('#editActions').classList.add('show')" 
 | 
						|
                     onkeyup="$editor.selected.edited = $editor.selected.userData.href = this.value" 
 | 
						|
                     value="${$editor.selected.userData.href||''}" />
 | 
						|
            </td>
 | 
						|
          </tr>
 | 
						|
          <tr>
 | 
						|
            <td><b class="badge">src</a></td>
 | 
						|
            <td>
 | 
						|
              <input type="text" id="src" placeholder="https://foo.com" maxlength="255" list="objects"
 | 
						|
                     onkeydown="document.querySelector('#editActions').classList.add('show')" 
 | 
						|
                     onkeyup="$editor.selected.edited = $editor.selected.userData.src = this.value" 
 | 
						|
                     value="${$editor.selected.userData.src||''}" />
 | 
						|
            </td>
 | 
						|
          </tr>
 | 
						|
          <tr>
 | 
						|
            <td><b class="badge">tag</a></td>
 | 
						|
            <td>
 | 
						|
              <input type="text" id="tag" placeholder="foo bar" maxlength="255" 
 | 
						|
                     onkeydown="document.querySelector('#editActions').classList.add('show')" 
 | 
						|
                     onkeyup="$editor.selected.edited = $editor.selected.userData.tag = this.value" 
 | 
						|
                     value="${$editor.selected.userData.tag||''}" />
 | 
						|
            </td>
 | 
						|
          </tr>
 | 
						|
        </tbody>
 | 
						|
      </table>
 | 
						|
      <datalist id="objects">
 | 
						|
        <option>https://xrfragment.org/index.glb#pos=start</option>
 | 
						|
        <option>
 | 
						|
        ${opts.objectNames.join('</option><option>')}
 | 
						|
        </option>
 | 
						|
      </datalist>
 | 
						|
      <br>
 | 
						|
      <div id="editActions">
 | 
						|
        <button class="download" onclick="$editor.export()"><i class="gg-software-download"></i>    download scene file</button> 
 | 
						|
        <br>
 | 
						|
        NOTE: updates to src-values will require reloading the scene 
 | 
						|
      </div>
 | 
						|
    </div>
 | 
						|
    <style type="text/css">
 | 
						|
      table.editorPopup input{
 | 
						|
        min-width:200px;
 | 
						|
      }
 | 
						|
      table.editorPopup tr td:nth-child(1){
 | 
						|
        text-align:left;
 | 
						|
      }
 | 
						|
      #editActions{
 | 
						|
        visibility:hidden;
 | 
						|
      }
 | 
						|
      #editActions.show{
 | 
						|
        visibility:visible;
 | 
						|
      }
 | 
						|
    </style>
 | 
						|
  `,
 | 
						|
 | 
						|
  init(opts){
 | 
						|
    el.innerHTML = this.html(opts)
 | 
						|
    return (this.el = el)
 | 
						|
  },
 | 
						|
 | 
						|
},{
 | 
						|
 | 
						|
  get(me,k,v){ return me[k] },
 | 
						|
 | 
						|
  set(me,k,v){ 
 | 
						|
    me[k] = v    
 | 
						|
  }
 | 
						|
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
$editor = (el,opts) => new Proxy({
 | 
						|
 | 
						|
  html: `
 | 
						|
    <div style="position:absolute; width:100%; text-align:right; right:166px;">
 | 
						|
      <button class="btn edit-btn">
 | 
						|
        <i class="gg-pen"></i>
 | 
						|
      </button>
 | 
						|
    </div>
 | 
						|
    <style type="text/css">
 | 
						|
      .xrf button.edit-btn{
 | 
						|
        height: 32px;
 | 
						|
        width: 30px;
 | 
						|
        margin-top: 7px;
 | 
						|
      }
 | 
						|
      .edit-btn.enabled,
 | 
						|
      .edit-btn.enabled:hover{
 | 
						|
        background:black;
 | 
						|
      }
 | 
						|
      .edit-btn i.gg-pen{
 | 
						|
         margin-top: -26px;
 | 
						|
         margin-left: 4px;
 | 
						|
         width: 10px;
 | 
						|
         color: var(--xrf-white); 
 | 
						|
      }
 | 
						|
    </style>
 | 
						|
  `,
 | 
						|
 | 
						|
  selecting: false,
 | 
						|
  editing: false,
 | 
						|
  helper: null,
 | 
						|
  selected: null,
 | 
						|
  objectNames: [],
 | 
						|
 | 
						|
  init(opts){
 | 
						|
    el.innerHTML = this.html
 | 
						|
    window.frontend.el.querySelector('#topbar').appendChild(el);
 | 
						|
    el.querySelector('.edit-btn').addEventListener('click', () =>  {
 | 
						|
      if( $editor.selecting || $editor.editing ) this.reset()
 | 
						|
      else{
 | 
						|
        $editor.selecting = true 
 | 
						|
        $editor.editing   = false 
 | 
						|
      }
 | 
						|
    })
 | 
						|
 | 
						|
    xrf.addEventListener('export', (e) => this.updateOriginalScene(e) )
 | 
						|
    xrf.addEventListener('href', (opts) => {
 | 
						|
      if( $editor.selecting || $editor.editing ) return opts.promise().reject("$editor should block hrefs while editing") // never resolve (block hrefs from interfering)
 | 
						|
    })
 | 
						|
    return this
 | 
						|
  },
 | 
						|
 | 
						|
  reset(){
 | 
						|
    if( this.helper) xrf.scene.remove(this.helper)
 | 
						|
    $editor.selecting = false
 | 
						|
    $editor.editing = false
 | 
						|
  },
 | 
						|
 | 
						|
  export(){
 | 
						|
    window.frontend.download()
 | 
						|
    this.reset()
 | 
						|
  },
 | 
						|
 | 
						|
  editNode(){
 | 
						|
    if( !this.selecting ) return console.log("not editing")
 | 
						|
    this.reset()
 | 
						|
    $editor.editing = true
 | 
						|
    this.collectObjects()
 | 
						|
      //`<b>XR Fragment:</b> #${this.selected.name}<br><br>${this.getMetaData(this.selected)}`),{
 | 
						|
    notify( $editorPopup( document.createElement('div') ).init(this) , {
 | 
						|
      timeout:false,
 | 
						|
      onclose: () => this.reset()
 | 
						|
    })
 | 
						|
  },
 | 
						|
 | 
						|
  collectObjects(){
 | 
						|
    this.objectNames = []
 | 
						|
    const escape = (str) => {
 | 
						|
      let d = document.createElement('div')
 | 
						|
      d.innerText = str
 | 
						|
      return d.innerHTML
 | 
						|
    }
 | 
						|
    xrf.scene.traverse( (n) => {
 | 
						|
      if( n.userData && n.userData.href ){
 | 
						|
        this.objectNames.push( escape(n.userData.href) )
 | 
						|
      }
 | 
						|
    })
 | 
						|
    xrf.scene.traverse( (n) => {
 | 
						|
      if( n.name ) this.objectNames.push( escape('#'+n.name) )
 | 
						|
    }) 
 | 
						|
  },
 | 
						|
 | 
						|
  initEdit(scene){
 | 
						|
    if( !this.listenersInstalled ){
 | 
						|
      AFRAME.scenes[0].addEventListener('click', () => this.editNode() )
 | 
						|
      this.listenersInstalled = true
 | 
						|
    }
 | 
						|
    scene.traverse( (n) => {
 | 
						|
      let highlight = (n) => (e) => {
 | 
						|
        if( !this.selecting || this.editing ) return // do nothing
 | 
						|
        if( this.helper){
 | 
						|
          if( this.helper.selected == n.uuid ) return // already selected
 | 
						|
          xrf.scene.remove(this.helper)
 | 
						|
        }
 | 
						|
 | 
						|
        this.selected = n
 | 
						|
        this.helper = new THREE.BoxHelper( n, 0xFF00FF )
 | 
						|
        this.helper.material.linewidth = 4
 | 
						|
        this.helper.material.color     = xrf.focusLine.material.color
 | 
						|
        this.helper.material.dashSize  = xrf.focusLine.material.dashSize
 | 
						|
        this.helper.material.gapSize   = xrf.focusLine.material.gapSize  
 | 
						|
        this.helper.selected = n.uuid
 | 
						|
        xrf.scene.add(this.helper)
 | 
						|
 | 
						|
        let div = document.createElement('div')
 | 
						|
        notify(`<b>#${n.name}</b><br>${this.getMetaData(this.selected)}`)
 | 
						|
 | 
						|
      }
 | 
						|
      if( n.material ) n.addEventListener('mousemove', n.highlightOnMouseMove = highlight(n) )
 | 
						|
    }) 
 | 
						|
  },
 | 
						|
 | 
						|
  getMetaData(n){
 | 
						|
    let html = `${n.userData.href ? `<b class="badge">href</b>${n.userData.href}<br>`:''}`
 | 
						|
    html    += `${n.userData.src  ? `<b class="badge">src</b>${n.userData.src}<br>`  :''}` 
 | 
						|
    html    += `${n.userData.tag  ? `<b class="badge">tag</b>${n.userData.tag}<br>`  :''}` 
 | 
						|
    return html
 | 
						|
  },
 | 
						|
 | 
						|
  updateOriginalScene(e){
 | 
						|
    const {scene,ext} = e
 | 
						|
    scene.traverse( (n) => {
 | 
						|
      if( !n.name ) return
 | 
						|
      // overwrite node with modified userData from scene
 | 
						|
      let o = xrf.scene.getObjectByName(n.name)
 | 
						|
      if( o && o.edited ){
 | 
						|
        for( let i in o.userData ) n.userData[i] = o.userData[i]
 | 
						|
      }
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
},
 | 
						|
{
 | 
						|
 | 
						|
  get(me,k,v){ return me[k] },
 | 
						|
 | 
						|
  set(me,k,v){ 
 | 
						|
    me[k] = v    
 | 
						|
 | 
						|
    switch( k ){
 | 
						|
 | 
						|
      case "selecting":{ 
 | 
						|
          lookctl = $('[look-controls]').components['look-controls']
 | 
						|
          if( v ){
 | 
						|
            lookctl.pause() // prevent click-conflict
 | 
						|
            notify("click an object to reveal XR Fragment metadata")
 | 
						|
            xrf.interactive.raycastAll = true
 | 
						|
            me.initEdit(xrf.scene)
 | 
						|
            lookctl.pause() // prevent click-conflict
 | 
						|
            el.querySelector('.edit-btn').classList.add(['enabled'])
 | 
						|
          }else{
 | 
						|
            lookctl.pause() // prevent click-conflict
 | 
						|
            xrf.scene.traverse( (n) => {
 | 
						|
              if( n.highlightOnMouseMove ){
 | 
						|
               n.removeEventListener( 'mousemove', n.highlightOnMouseMove )    
 | 
						|
              }
 | 
						|
            })
 | 
						|
            lookctl.play() // prevent click-conflict (resume)
 | 
						|
            el.querySelector('.edit-btn').classList.remove(['enabled'])
 | 
						|
          }
 | 
						|
          break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
})
 | 
						|
 | 
						|
// reactify component!
 | 
						|
document.addEventListener('frontend:ready', (e) => {
 | 
						|
  window.$editor = $editor( document.createElement('div') ).init(e.detail)
 | 
						|
})
 |