// // this is just an AFRAME wrapper for golden-layout v2 (docs: https://golden-layout.github.io/golden-layout/) // // AFRAME.registerComponent('windowmanager', { schema: { }, requires:{ "goldenlayout_css1": "https://unpkg.com/golden-layout@2.6.0/dist/css/goldenlayout-base.css", "goldenlayout_css2": "https://unpkg.com/golden-layout@2.6.0/dist/css/themes/goldenlayout-dark-theme.css" }, dom: { events: [], // for stylesheet see bottom of file html: (me) => `
`, }, events:{ ready: function(){ this.initLayout(this) } }, init: function () { this.require( this.requires ) }, initLayout: async function(){ if( this.goldenLayout !== undefined || !this.el.dom.querySelector(".modals")) return console.warn("TODO: fix duplicate ready-events") let { GoldenLayout } = await import("https://cdn.skypack.dev/pin/golden-layout@v2.5.0-dAz3xMzxIRpbnbfEAik0/mode=imports/optimized/golden-layout.js"); class Modal { constructor(container) { this.container = container; this.rootElement = container.element; this.rootElement.innerHTML = '' this.resizeWithContainerAutomatically = true; } } const myLayout = { root: { type: 'row', content: [ { title: 'Terminal', type: 'component', componentType: 'Modal', width: 50, }, { title: 'Welcome to XR shell', type: 'component', componentType: 'Modal', // componentState: { text: 'Component 2' } } ] } }; this.goldenLayout = new GoldenLayout( this.el.dom.querySelector('.modals')); this.goldenLayout.registerComponent('Modal', Modal); this.goldenLayout.loadLayout(myLayout); }, add: function(title,el){ setTimeout( () => { let item = this.goldenLayout.addComponent('Modal', undefined, title ) console.dir(item) item.parentItem.contentItems[ item.parentItem.contentItems.length-1 ].element.querySelector('.lm_content').appendChild(el) },1000) }, manifest: { // HTML5 manifest to identify app to xrsh "short_name": "windowmanager", "name": "Window Manager", "icons": [ { "src": "/images/icons-vector.svg", "type": "image/svg+xml", "sizes": "512x512" } ], "id": "/?source=pwa", "start_url": "/?source=pwa", "background_color": "#3367D6", "display": "standalone", "scope": "/", "theme_color": "#3367D6", "shortcuts": [], "description": "2D/3D management of windows", "screenshots": [ { "src": "/images/screenshot1.png", "type": "image/png", "sizes": "540x720", "form_factor": "narrow" } ], "help":` Window Manager The window manager manages all the windows in 2D/XR. This is a core XRSH system application ` } }); // monkeypatching updateProperties will detect a component config like: // dom: { // html: `

Welcome to XR shell

`, // css: `#hello {color:red}`, // events: ['click'] // } // and use it to create a reactive DOM-component (using native javascript Proxy) // which delegates all related DOM-events AND data-changes back to the AFRAME component AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ return function(){ updateProperties.apply(this,arguments) if( this.dom && this.data && this.data.uri ){ tasks = { generateUniqueId: () => { this.el.uid = String(Math.random()).substr(2) return tasks }, ensureOverlay: () => { let overlay = document.querySelector('#overlay') if( !overlay ){ overlay = document.createElement('div') overlay.id = "overlay" document.body.appendChild(overlay) document.querySelector("a-scene").setAttribute("webxr","overlayElement:#overlay") } tasks.overlay = overlay return tasks }, createReactiveDOMElement: () => { const reactify = (el,aframe) => new Proxy(this.data,{ get(me,k,v) { return me[k] }, set(me,k,v){ me[k] = v aframe.emit(k,{el,k,v}) } }) this.el.dom = document.createElement('div') this.el.dom.className = this.parseAppURI(this.data.uri).component this.el.dom.innerHTML = this.dom.html(this) this.data = reactify( this.dom.el, this.el ) this.dom.events.map( (e) => this.el.dom.addEventListener(e, (ev) => this.el.emit(e,ev) ) ) return tasks }, addCSS: () => { if( this.dom.css && !document.head.querySelector(`style#${this.attrName}`) ){ document.head.innerHTML += `` } return tasks }, scaleDOMvsXR: () => { if( this.dom.scale ) this.el.setAttribute('scale',`${this.dom.scale} ${this.dom.scale} ${this.dom.scale}`) return tasks }, addModalFunctions: () => { this.el.close = () => { this.el.dom.remove() this.el.removeAttribute("html") } this.el.toggleFold = () => { this.el.dom.querySelector(".modal").classList.toggle('fold') this.el.dom.querySelector('.top .fold').innerText = this.el.dom.querySelector('.modal').className.match(/fold/) ? '▢' : '_' } return tasks }, } tasks .generateUniqueId() .ensureOverlay() .addCSS() .createReactiveDOMElement() .scaleDOMvsXR() .addModalFunctions() // finally lets add the bad boy to the DOM if( this.dom && this.dom.modal ){ document.querySelector('[windowmanager]').components['windowmanager'].add( this.parseAppURI(this.data.uri).component, this.el.dom ) }else tasks.overlay.appendChild(this.el.dom) } } }( AFRAME.AComponent.prototype.updateProperties) // // base CSS for XRSH apps // // limitations / some guidelines for html-mesh compatibility: // * no icon libraries (favicon e.g.) // * 'border-radius: 2px 3px 4px 5px' (applies 2px to all corners) // document.head.innerHTML += ` `