main: work in progress [might break]
This commit is contained in:
parent
c1d3afe540
commit
d719fb7e02
|
@ -0,0 +1,95 @@
|
|||
AFRAME.registerComponent('add', {
|
||||
schema:{
|
||||
comps: {type:"array"}
|
||||
},
|
||||
|
||||
init: function () { },
|
||||
|
||||
getInstallables: function(){
|
||||
const installed = document.querySelector('[launcher]').components.launcher.system.getLaunchables()
|
||||
return this.data.comps.map( (c) => {
|
||||
return installed[c] ? null : c
|
||||
})
|
||||
.filter( (c) => c ) // filters out null elements
|
||||
},
|
||||
|
||||
events:{
|
||||
launcher: function(){
|
||||
if( this.el.sceneEl.renderer.xr.isPresenting ){
|
||||
this.el.sceneEl.exitVR() // *FIXME* we need a gui
|
||||
}
|
||||
|
||||
let msg = `Which item to add to the menu?\n\n`
|
||||
const coms = this.getInstallables()
|
||||
for( let i = 0; i < coms.length; i++ ){
|
||||
msg += `${i+1}. ${coms[i]}\n`
|
||||
}
|
||||
let choice = prompt(msg, 1)
|
||||
if( parseInt(choice) == NaN ) return console.log("choice NaN")
|
||||
|
||||
// add a-entity + selected component
|
||||
const com = coms[ parseInt(choice)-1 ]
|
||||
if( !com ) return console.log("choice != component")
|
||||
AFRAME.utils.require([com])
|
||||
.then( () => {
|
||||
let el = document.createElement('a-entity')
|
||||
el.setAttribute( com.split("/").pop(), "")
|
||||
this.el.sceneEl.appendChild(el)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
manifest: { // HTML5 manifest to identify app to xrsh
|
||||
"short_name": "+",
|
||||
"name": "Add item",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://css.gg/add-r.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/?source=pwa",
|
||||
"start_url": "/?source=pwa",
|
||||
"background_color": "#3367D6",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#3367D6",
|
||||
"category":"system",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "What is the latest news?",
|
||||
"cli":{
|
||||
"usage": "helloworld <type> [options]",
|
||||
"example": "helloworld news",
|
||||
"args":{
|
||||
"--latest": {type:"string"}
|
||||
}
|
||||
},
|
||||
"short_name": "Today",
|
||||
"description": "View weather information for today",
|
||||
"url": "/today?source=pwa",
|
||||
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||
}
|
||||
],
|
||||
"description": "adds item to menu",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/images/screenshot1.png",
|
||||
"type": "image/png",
|
||||
"sizes": "540x720",
|
||||
"form_factor": "narrow"
|
||||
}
|
||||
],
|
||||
"help":`
|
||||
Helloworld application
|
||||
|
||||
This is a help file which describes the application.
|
||||
It will be rendered thru troika text, and will contain
|
||||
headers based on non-punctualized lines separated by linebreaks,
|
||||
in above's case "\nHelloworld application\n" will qualify as header.
|
||||
`
|
||||
}
|
||||
|
||||
});
|
||||
|
289
com/app.js
289
com/app.js
|
@ -1,289 +0,0 @@
|
|||
// this is a highlevel way of loading buildless 'apps' (a collection of js components)
|
||||
|
||||
AFRAME.required = {}
|
||||
AFRAME.app = new Proxy({
|
||||
|
||||
order:0,
|
||||
|
||||
components: {}, // component-strings in this array are automatically
|
||||
// added to each app
|
||||
add(component, entity){
|
||||
// categorize by component to prevent similar apps loading duplicate dependencies simultaniously
|
||||
this[component] = this[component] || []
|
||||
this[component].push(entity)
|
||||
entity.data.order = entity.data.order || this.count()
|
||||
},
|
||||
count(){
|
||||
let n = 0
|
||||
this.foreach( () => n++ )
|
||||
return n
|
||||
},
|
||||
foreach(cb){
|
||||
const isArray = (e) => e.push
|
||||
let arr = []
|
||||
for( let i in this ){
|
||||
if( isArray(this[i]) ) this[i].map( (app) => arr.push(app.el.app) )
|
||||
}
|
||||
arr.sort( (a,b) => a.data.order > b.data.order )
|
||||
.map( cb )
|
||||
}
|
||||
},{
|
||||
get(me,k) { return me[k] },
|
||||
set(me,k,v){ me[k] = v }
|
||||
})
|
||||
|
||||
/*
|
||||
* This is the abstract 'app' component
|
||||
*/
|
||||
|
||||
appComponent = {
|
||||
schema:{
|
||||
"uri":{ type:"string"}
|
||||
},
|
||||
|
||||
events:{
|
||||
"app:ready": function(){
|
||||
let {id,component,type} = this.parseAppURI(this.data.uri)
|
||||
AFRAME.app[component].map( (app) => {
|
||||
if( !app.el.getAttribute(component) ){
|
||||
if( AFRAME.components[ component ] ){
|
||||
app.el.setAttribute(component,app.data)
|
||||
}else console.warn(`${component} was not fully downloaded yet (${app.data.uri})`)
|
||||
}
|
||||
})
|
||||
},
|
||||
"requires:ready": function(){
|
||||
let {id,component,type} = this.parseAppURI(this.data.uri)
|
||||
AFRAME.app[component].map( (app) => {
|
||||
if( app.readyFired ) return
|
||||
setTimeout( () => {
|
||||
if( this.el.dom ) this.el.dom.style.display = '' // finally show dom elements
|
||||
app.el.emit('ready')
|
||||
app.readyFired = true
|
||||
},400) // big js scripts need some parsing time
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
init: function() {
|
||||
let {id,component,type} = this.parseAppURI(this.data.uri)
|
||||
let sel = `script#${component}`
|
||||
if( AFRAME.app[component] || AFRAME.components[component] || document.head.querySelector(sel) ) return AFRAME.app.add(component,this)
|
||||
AFRAME.app.add(component,this)
|
||||
this.require([ this.data.uri ], 'app:ready')
|
||||
},
|
||||
|
||||
parseAppURI: AFRAME.AComponent.prototype.parseAppURI = function(uri){
|
||||
return {
|
||||
id: String(uri).split("/").pop(), // 'a/b/c/mycom.js' => 'mycom.js'
|
||||
component: String(uri).split("/").pop().split(".js").shift(), // 'mycom.js' => 'mycom'
|
||||
type: String(uri).split(".").pop() // 'mycom.js' => 'js'
|
||||
}
|
||||
},
|
||||
// usage: require(["./app/foo.js"])
|
||||
// require({foo: "https://foo.com/foo.js"})
|
||||
require: AFRAME.AComponent.prototype.require = function(packages,readyEvent){
|
||||
let deps = []
|
||||
if( !packages.map ) packages = Object.values(packages)
|
||||
packages.map( (package) => {
|
||||
try{
|
||||
let id = package.split("/").pop()
|
||||
// prevent duplicate requests
|
||||
if( AFRAME.required[id] ) return
|
||||
AFRAME.required[id] = true
|
||||
|
||||
if( !document.head.querySelector(`script#${id}`) ){
|
||||
let {component,type} = this.parseAppURI(package)
|
||||
let p = new Promise( (resolve,reject) => {
|
||||
switch(type){
|
||||
case "js": let script = document.createElement("script")
|
||||
script.id = id
|
||||
script.src = package
|
||||
script.onload = () => resolve()
|
||||
script.onerror = (e) => reject(e)
|
||||
document.head.appendChild(script)
|
||||
break;
|
||||
case "css": let link = document.createElement("link")
|
||||
link.id = id
|
||||
link.href = package
|
||||
link.rel = 'stylesheet'
|
||||
document.head.appendChild(link)
|
||||
resolve()
|
||||
break;
|
||||
}
|
||||
})
|
||||
deps.push(p)
|
||||
}
|
||||
}catch(e){ console.error(`package ${package} could not be retrieved..aborting :(`); throw e; }
|
||||
})
|
||||
Promise.all(deps).then( () => {
|
||||
this.el.emit( readyEvent || 'requireReady', packages)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AFRAME.registerComponent('app', appComponent)
|
||||
AFRAME.registerComponent('com', appComponent)
|
||||
|
||||
/*
|
||||
* Here are monkeypatched AFRAME component prototype functions
|
||||
*
|
||||
* monkeypatching initComponent will trigger events when components
|
||||
* are initialized (that way apps can react to attached components)
|
||||
* basically, in both situations:
|
||||
* <a-entity foo="a:1"/>
|
||||
* <a-entity app="uri: myapp.js"/> <!-- myapp.js calls this.require(['foo.js']) -->
|
||||
*
|
||||
* event 'foo' will be triggered as both entities (in)directly require component 'foo'
|
||||
*/
|
||||
|
||||
AFRAME.AComponent.prototype.initComponent = function(initComponent){
|
||||
return function(){
|
||||
this.el.emit( this.attrName, this)
|
||||
this.scene = AFRAME.scenes[0] // mount scene for convenience
|
||||
return initComponent.apply(this,arguments)
|
||||
}
|
||||
}( AFRAME.AComponent.prototype.initComponent)
|
||||
|
||||
|
||||
AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
|
||||
let setupApp = function(){
|
||||
updateProperties.apply(this,arguments)
|
||||
|
||||
if( !this.data || !this.data.uri || this.isApp ) return // only deal with apps (once)
|
||||
|
||||
// ensure overlay
|
||||
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")
|
||||
}
|
||||
|
||||
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})
|
||||
}
|
||||
})
|
||||
|
||||
// reactify components with dom-definition
|
||||
if( this.data.uri && this.dom && !this.el.dom ){
|
||||
|
||||
tasks = {
|
||||
|
||||
createReactiveDOMElement: () => {
|
||||
this.el.dom = document.createElement('div')
|
||||
this.el.dom.className = this.parseAppURI(this.data.uri).component
|
||||
this.el.dom.innerHTML = this.com.dom.html(this)
|
||||
this.el.dom.style.display = 'none'
|
||||
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 += `<style id="${this.attrName}">${this.dom.css(this)}</style>`
|
||||
}
|
||||
return tasks
|
||||
},
|
||||
|
||||
scaleDOMvsXR: () => {
|
||||
if( this.dom.scale ) this.el.setAttribute('scale',`${this.dom.scale} ${this.dom.scale} ${this.dom.scale}`)
|
||||
return tasks
|
||||
},
|
||||
|
||||
setupListeners: () => {
|
||||
this.scene.addEventListener('apps:2D', () => this.el.setAttribute('visible', false) )
|
||||
this.scene.addEventListener('apps:XR', () => {
|
||||
this.el.setAttribute('visible', true)
|
||||
this.el.setAttribute("html",`html:#${this.el.uid}; cursor:#cursor`)
|
||||
})
|
||||
return tasks
|
||||
},
|
||||
|
||||
initAutoComponents: () => {
|
||||
for ( let i in AFRAME.app.components ) {
|
||||
this.el.setAttribute( i, AFRAME.app.components[i] )
|
||||
}
|
||||
return tasks
|
||||
},
|
||||
|
||||
triggerKeyboardForInputs: () => {
|
||||
// https://developer.oculus.com/documentation/web/webxr-keyboard ;
|
||||
[...this.el.dom.querySelectorAll('[type=text]')].map( (input) => {
|
||||
let triggerKeyboard = function(){
|
||||
this.focus()
|
||||
console.log("focus")
|
||||
}
|
||||
input.addEventListener('click', triggerKeyboard )
|
||||
})
|
||||
return tasks
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks
|
||||
.addCSS()
|
||||
.createReactiveDOMElement()
|
||||
.scaleDOMvsXR()
|
||||
.triggerKeyboardForInputs()
|
||||
.setupListeners()
|
||||
.initAutoComponents()
|
||||
|
||||
document.querySelector('#overlay').appendChild(this.el.dom)
|
||||
this.el.emit('DOMready',{el: this.el.dom})
|
||||
|
||||
}else this.data = reactify( this.el, this.el )
|
||||
|
||||
// assign unique app id
|
||||
if( !this.el.uid ) this.el.uid = '_'+String(Math.random()).substr(10)
|
||||
|
||||
// require coms
|
||||
let requires = {}
|
||||
for ( let i in AFRAME.app.components ) {
|
||||
if( !AFRAME.components[i] ) requires[i] = AFRAME.app.components[i]
|
||||
}
|
||||
if( this.requires ) requires = {...requires, ...this.requires }
|
||||
if( Object.values(requires).length ) this.require( requires, 'requires:ready' )
|
||||
else this.el.emit('requires:ready' )
|
||||
|
||||
// mark app as being initialized
|
||||
this.isApp = true
|
||||
this.el.app = this
|
||||
}
|
||||
return function(){
|
||||
try{ setupApp.apply(this,arguments) }catch(e){ console.error(e) }
|
||||
}
|
||||
}( AFRAME.AComponent.prototype.updateProperties)
|
||||
|
||||
document.head.innerHTML += `
|
||||
<style type="text/css">
|
||||
/* 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}
|
||||
|
||||
a-scene{
|
||||
position:fixed;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
}
|
||||
canvas{
|
||||
z-index:10;
|
||||
}
|
||||
|
||||
#overlay{
|
||||
display: flex; /* tile modals */
|
||||
z-index:10;
|
||||
}
|
||||
#overlay.hide{
|
||||
z-index:-10;
|
||||
}
|
||||
</style>
|
||||
`
|
|
@ -0,0 +1,166 @@
|
|||
AFRAME.registerComponent('cast', {
|
||||
schema:{
|
||||
comps: {type:"array"}
|
||||
},
|
||||
|
||||
requires: {
|
||||
dom: "./com/dom.js", // interpret .dom object
|
||||
xd: "./com/xd.js", // allow switching between 2D/3D
|
||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
||||
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
||||
winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", //
|
||||
},
|
||||
|
||||
dom: {
|
||||
scale: 0.8,
|
||||
events: ['click','keydown'],
|
||||
html: (me) => `<div>
|
||||
<video id="video" autoplay playsinline style="width:100%"></video>
|
||||
</div>`,
|
||||
|
||||
css: (me) => `.helloworld-window div.pad { padding:11px; }`
|
||||
},
|
||||
|
||||
init: function () { },
|
||||
|
||||
getInstallables: function(){
|
||||
const installed = document.querySelector('[launcher]').components.launcher.system.getLaunchables()
|
||||
return this.data.comps.map( (c) => {
|
||||
return installed[c] ? null : c
|
||||
})
|
||||
.filter( (c) => c ) // filters out null elements
|
||||
},
|
||||
|
||||
events:{
|
||||
|
||||
launcher: async function(){
|
||||
if( this.el.sceneEl.renderer.xr.isPresenting ){
|
||||
this.el.sceneEl.exitVR() // *FIXME* we need a gui
|
||||
}
|
||||
const el = document.querySelector('body');
|
||||
const cropTarget = await CropTarget.fromElement(el);
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia();
|
||||
const [track] = stream.getVideoTracks();
|
||||
this.track = track
|
||||
this.stream = stream
|
||||
this.createWindow()
|
||||
}
|
||||
},
|
||||
|
||||
createWindow: async function(){
|
||||
let s = await AFRAME.utils.require(this.requires)
|
||||
|
||||
// instance this component
|
||||
const instance = this.el.cloneNode(false)
|
||||
this.el.sceneEl.appendChild( instance )
|
||||
instance.setAttribute("dom", "")
|
||||
instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button
|
||||
instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
||||
instance.setAttribute("grabbable","")
|
||||
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||
instance.track = this.track
|
||||
instance.stream = this.stream
|
||||
|
||||
const setupWindow = () => {
|
||||
instance.dom.style.display = 'none'
|
||||
|
||||
const video = instance.dom.querySelector('video')
|
||||
video.addEventListener( "loadedmetadata", function () {
|
||||
let width = Math.round(window.innerWidth*0.4)
|
||||
let factor = width / this.videoWidth
|
||||
let height = Math.round(this.videoHeight * factor)
|
||||
new WinBox("Casting Tab",{
|
||||
width,
|
||||
height,
|
||||
x:"center",
|
||||
y:"center",
|
||||
id: instance.uid, // important hint for html-mesh
|
||||
root: document.querySelector("#overlay"),
|
||||
mount: instance.dom,
|
||||
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
||||
oncreate: () => {
|
||||
|
||||
// instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
||||
}
|
||||
});
|
||||
instance.dom.style.display = '' // show
|
||||
|
||||
// hint grabbable's obb-collider to track the window-object
|
||||
instance.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0'
|
||||
instance.components['obb-collider'].update()
|
||||
})
|
||||
video.srcObject = instance.stream
|
||||
video.play()
|
||||
|
||||
this.createVideoTexture.apply(instance)
|
||||
}
|
||||
setTimeout( () => setupWindow(), 10 ) // give new components time to init
|
||||
},
|
||||
|
||||
createVideoTexture: function(){
|
||||
console.dir(this)
|
||||
const texture = new THREE.VideoTexture( video );
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
const geometry = new THREE.PlaneGeometry( 16, 9 );
|
||||
geometry.scale( 0.2, 0.2, 0.2 );
|
||||
const material = new THREE.MeshBasicMaterial( { map: texture } );
|
||||
const mesh = new THREE.Mesh( geometry, material );
|
||||
mesh.lookAt( this.sceneEl.camera.position );
|
||||
this.object3D.add(mesh)
|
||||
},
|
||||
|
||||
manifest: { // HTML5 manifest to identify app to xrsh
|
||||
"short_name": "tab",
|
||||
"name": "Browser Tab",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://css.gg/cast.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/?source=pwa",
|
||||
"start_url": "/?source=pwa",
|
||||
"background_color": "#3367D6",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#3367D6",
|
||||
"category":"system",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "What is the latest news?",
|
||||
"cli":{
|
||||
"usage": "helloworld <type> [options]",
|
||||
"example": "helloworld news",
|
||||
"args":{
|
||||
"--latest": {type:"string"}
|
||||
}
|
||||
},
|
||||
"short_name": "Today",
|
||||
"description": "View weather information for today",
|
||||
"url": "/today?source=pwa",
|
||||
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||
}
|
||||
],
|
||||
"description": "adds item to menu",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/images/screenshot1.png",
|
||||
"type": "image/png",
|
||||
"sizes": "540x720",
|
||||
"form_factor": "narrow"
|
||||
}
|
||||
],
|
||||
"help":`
|
||||
Helloworld application
|
||||
|
||||
This is a help file which describes the application.
|
||||
It will be rendered thru troika text, and will contain
|
||||
headers based on non-punctualized lines separated by linebreaks,
|
||||
in above's case "\nHelloworld application\n" will qualify as header.
|
||||
`
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -100,8 +100,8 @@ AFRAME.registerComponent('dom',{
|
|||
},
|
||||
|
||||
addCSS: function(){
|
||||
if( this.dom.css && !document.head.querySelector(`style#${this.attrName}`) ){
|
||||
document.head.innerHTML += `<style id="${this.attrName}">${this.dom.css(this)}</style>`
|
||||
if( this.dom.css && !document.head.querySelector(`style#${this.com.attrName}`) ){
|
||||
document.head.innerHTML += `<style id="${this.com.attrName}">${this.dom.css(this)}</style>`
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
|
|
@ -14,13 +14,26 @@ AFRAME.registerComponent('helloworld-iframe', {
|
|||
dom: {
|
||||
scale: 3,
|
||||
events: ['click','keydown'],
|
||||
html: (me) => `<div>
|
||||
html: (me) => `<div class="relative">
|
||||
<span id="warning">
|
||||
<b>Unfortunately,</b><br><br>
|
||||
The browser does not allow IFRAME rendering<br>
|
||||
in immersive mode (for security reasons).<br><br>
|
||||
</span>
|
||||
<iframe src=""></iframe>
|
||||
</div>`,
|
||||
|
||||
css: (me) => `
|
||||
.XR #overlay .winbox.iframe{ visibility: visible; } /* don't hide in XR mode */
|
||||
.winbox.iframe iframe { background:#FFF; }
|
||||
.XR #overlay .winbox.iframe{
|
||||
visibility: visible;
|
||||
position:relative;
|
||||
} /* don't hide in XR mode */
|
||||
.winbox.iframe > .wb-body { background:#FFF !important;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.winbox.iframe iframe { z-index:10; }
|
||||
.winbox.iframe #warning { position:absolute; z-index:9; top:100px; left:20px; width:100%; height:50px; color:black; display:none; }
|
||||
`
|
||||
},
|
||||
|
||||
|
@ -40,6 +53,10 @@ AFRAME.registerComponent('helloworld-iframe', {
|
|||
let URL = this.data.url || prompt('enter URL to display','https://fabien.benetou.fr/Wiki/Wiki')
|
||||
if( !URL ) return
|
||||
|
||||
this.createWindow()
|
||||
},
|
||||
|
||||
createWindow: function(){
|
||||
let s = await AFRAME.utils.require(this.requires)
|
||||
|
||||
// instance this component
|
||||
|
@ -51,12 +68,16 @@ AFRAME.registerComponent('helloworld-iframe', {
|
|||
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(1.39) )
|
||||
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||
|
||||
this.el.sceneEl.addEventListener('3D', () => {
|
||||
instance.dom.querySelector('#warning').style.display = 'block' // show warning
|
||||
})
|
||||
|
||||
const setupWindow = () => {
|
||||
const com = instance.components['helloworld-iframe']
|
||||
instance.dom.style.display = 'none'
|
||||
new WinBox("Hello World",{
|
||||
width: 250,
|
||||
height: 150,
|
||||
new WinBox( this.manifest.short_name+" "+URL,{
|
||||
width: Math.round(window.innerWidth*0.6),
|
||||
height: Math.round(window.innerHeight*0.6),
|
||||
class:["iframe"],
|
||||
x:"center",
|
||||
y:"center",
|
||||
|
|
|
@ -14,7 +14,7 @@ AFRAME.registerComponent('helloworld-window', {
|
|||
init: function(){ },
|
||||
|
||||
dom: {
|
||||
scale: 3,
|
||||
scale: 0.8,
|
||||
events: ['click','keydown'],
|
||||
html: (me) => `<div>
|
||||
<div class="pad"> <span>${me.data.foo}</span> <b>${me.data.myvalue}</b></span>
|
||||
|
@ -40,8 +40,10 @@ AFRAME.registerComponent('helloworld-window', {
|
|||
const instance = this.el.cloneNode(false)
|
||||
this.el.sceneEl.appendChild( instance )
|
||||
instance.setAttribute("dom", "")
|
||||
instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button
|
||||
instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(1.39) )
|
||||
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
||||
instance.setAttribute("grabbable","")
|
||||
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||
|
||||
const setupWindow = () => {
|
||||
|
@ -61,11 +63,15 @@ AFRAME.registerComponent('helloworld-window', {
|
|||
});
|
||||
instance.dom.style.display = '' // show
|
||||
|
||||
// hint grabbable's obb-collider to track the window-object
|
||||
instance.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0'
|
||||
instance.components['obb-collider'].update()
|
||||
|
||||
// data2event demo
|
||||
instance.setAttribute("data2event","")
|
||||
com.data.myvalue = 1
|
||||
com.data.foo = `instance ${instance.uid}: `
|
||||
setInterval( () => com.data.myvalue++, 200 )
|
||||
//instance.setAttribute("data2event","")
|
||||
//com.data.myvalue = 1
|
||||
//com.data.foo = `instance ${instance.uid}: `
|
||||
//setInterval( () => com.data.myvalue++, 500 )
|
||||
}
|
||||
|
||||
setTimeout( () => setupWindow(), 10 ) // give new components time to init
|
||||
|
|
|
@ -11,6 +11,7 @@ AFRAME.registerComponent('helloworld', {
|
|||
|
||||
await AFRAME.utils.require(this.dependencies)
|
||||
this.el.setAttribute("data2event","")
|
||||
this.el.setAttribute("grabbable","")
|
||||
|
||||
this.el.innerHTML = `
|
||||
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
|
||||
|
|
|
@ -234,6 +234,7 @@ AFRAME.registerComponent('launcher', {
|
|||
console.log("launcher.js: launching "+el.launchCom.el.getAttributeNames().shift())
|
||||
launcher.preventAccidentalButtonPresses()
|
||||
el.launchCom.el.emit('launcher') // launch component!
|
||||
this.data.open = false // close to prevent infinite loop of clicks when leaving immersive mode
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -360,6 +361,7 @@ AFRAME.registerSystem('launcher',{
|
|||
return hasEvent ? el : null
|
||||
})
|
||||
this.updateLauncher()
|
||||
return seen
|
||||
},
|
||||
|
||||
updateLauncher: function(){
|
||||
|
|
|
@ -3,8 +3,6 @@ AFRAME.registerComponent('xrfragments', {
|
|||
url: { type:"string"}
|
||||
},
|
||||
|
||||
dependencies:['dom'],
|
||||
|
||||
init: function () {
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue