janusweb 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy.yml +71 -0
- package/Dockerfile +14 -0
- package/README.md +210 -7
- package/janusxr.com/.args +2 -0
- package/janusxr.com/.init.lua +30 -0
- package/media/assets/webui/apps/comms/voip.js +27 -25
- package/media/assets/webui/apps/editor/editor.js +3 -0
- package/media/assets/webui/apps/navigation/navigation.js +1 -1
- package/media/assets/webui/apps/xrfragment/level0-sidecarfile.js +308 -0
- package/media/assets/webui/apps/xrfragment/level2-hyperlink.js +265 -0
- package/media/assets/webui/apps/xrfragment/level7-engine-prefixes.export.js +93 -0
- package/media/assets/webui/apps/xrfragment/level7-engine-prefixes.import.js +229 -0
- package/media/assets/webui/apps/xrfragment/patch/metaquest-fix-flickering.js +15 -0
- package/media/assets/webui/apps/xrfragment/xrfragment.json +14 -0
- package/media/assets/webui/default.json +1 -0
- package/media/images/portal.svg +130 -0
- package/media/index.html +48 -1
- package/package.json +2 -2
- package/scripts/client.js +24 -0
- package/scripts/config.js +12 -1
- package/scripts/janusbase.js +1 -0
- package/scripts/janusparagraph.js +169 -22
- package/scripts/janusxrplayer.js +4 -6
- package/scripts/room.js +59 -36
- package/scripts/translators/paragraph/html-xml-rss.js +47 -0
- package/scripts/translators/peertube.js +89 -0
- package/scripts/translators/xrfragments.js +5 -5
- package/tests/room/paragraph.xml +47 -0
- package/utils/build.sh +1 -0
- package/utils/dev-link.sh +75 -0
- package/utils/release.binary.sh +36 -0
- package/utils/release.docker.sh +11 -0
|
@@ -1,9 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
|
+
* <paragraph> text-to-texture translator. The builtin XMLtranslator allows:
|
|
3
|
+
*
|
|
4
|
+
* 1. inline html : <paragraph><![CDATA[ <b>hello world</b> ]]></paragraph>
|
|
5
|
+
* 2. inline html (partial) : <paragraph selector="span"><![CDATA[ <b>hello <span>world</span></b> ]]></paragraph>
|
|
6
|
+
* 3. external url : <paragraph url="https://janusxr.org"/>
|
|
7
|
+
* 4. external url (partials) : <paragraph url="https://janusxr.org" selector=".infoText"/>
|
|
8
|
+
* 5. janusroom container html: <paragraph selector=".someclass"/>
|
|
9
|
+
* 6. janusroom container xml : <paragraph selector="tag subtag title"/>
|
|
10
|
+
* 7. RSS : <paragraph url="https://my.org/foo.rss" selector="item description"/>
|
|
11
|
+
* 8. XML : <paragraph url="https://my.org/bar.xml" selector="title"/>
|
|
12
|
+
* 9. any data- or selector-format via 'paragraph_translator' event
|
|
13
|
+
*
|
|
14
|
+
* EXAMPLE TRANSLATOR:
|
|
15
|
+
*
|
|
16
|
+
* room.addEventListener("paragraph_translator", function(e){
|
|
17
|
+
* const {translator,paragraph} = e.detail
|
|
18
|
+
* // now you can override the default translator
|
|
19
|
+
* translator.fetch = async (url) => this.text = "a response" // my custom fetch function
|
|
20
|
+
* translator.translate = async ( ) => ["<h1>head</h1>",this.text] // my x2html translator
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* NOTES:
|
|
24
|
+
* 1. By default, selectors are done via `document.querySelectorAll`
|
|
25
|
+
* 2. in case of multiple results:
|
|
26
|
+
* 2.1 clicking the paragraph with cycle through them
|
|
27
|
+
* 2.2 setting cycle="3000" allows for (2sec per item) slideshows
|
|
28
|
+
* 2.3 setting `rooms.objects.myparagraph.index++` cycles to next item
|
|
29
|
+
* 3. width/height-attributes offers finer control of texture size
|
|
30
|
+
* 4. ttl-attributes allows finer control of webrequest-cache (default 2 mins)
|
|
31
|
+
* 5. setting `rooms.object.myparagraph.selector = "#section2"' allows for statemanagement
|
|
32
|
+
* 6. separation of `this.text` (for JML) and `this.html` (for texture) is necessary (to not break JML/glb exports)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
elation.require(['janusweb.janusbase','janusweb.translators.paragraph.html-xml-rss'], function() {
|
|
2
36
|
elation.component.add('engine.things.janusparagraph', function() {
|
|
3
37
|
this.postinit = function() {
|
|
4
38
|
elation.engine.things.janusparagraph.extendclass.postinit.call(this);
|
|
5
39
|
this.defineProperties({
|
|
6
|
-
text: {type: 'string', default: '', set: this.
|
|
40
|
+
text: {type: 'string', default: '', set: this.textToHTML },
|
|
7
41
|
font_size: {type: 'integer', default: 16, set: this.updateTexture},
|
|
8
42
|
text_col: {type: 'color', default: 0x000000, set: this.updateTexture},
|
|
9
43
|
back_col: {type: 'color', default: 0xffffff, set: this.updateTexture},
|
|
@@ -18,8 +52,33 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
18
52
|
shadow: { type: 'boolean', default: true, set: this.updateMaterial },
|
|
19
53
|
shadow_receive: { type: 'boolean', default: true, set: this.updateMaterial, comment: 'Receive shadows from self and other objects' },
|
|
20
54
|
shadow_cast: { type: 'boolean', default: true, set: this.updateMaterial, comment: 'Cast shadows onto self and other objects' },
|
|
55
|
+
url: { type: 'string', default: '', set: this.fetchSource },
|
|
56
|
+
selector: { type: 'string', default: '', set: this.fetchSource },
|
|
57
|
+
index: { type: 'integer', default: 0, set: this.updateHTML },
|
|
58
|
+
cycle: {type: 'integer', default: 0, set: this.updateHTML },
|
|
59
|
+
width: { type: 'integer', default: 1024 },
|
|
60
|
+
height: { type: 'integer', default: 1024 },
|
|
61
|
+
ttl: { type: 'integer', default: 120000, comment: 'expire texture/html after ms (default:2 mins)' },
|
|
21
62
|
});
|
|
22
63
|
}
|
|
64
|
+
|
|
65
|
+
this.paragraphs = [] // for cycle / partial purposes
|
|
66
|
+
|
|
67
|
+
this.textToHTML = function(){
|
|
68
|
+
// IMPORTANT: skip infinite loop
|
|
69
|
+
if( this.html == this.text ) return
|
|
70
|
+
if( this.text ){
|
|
71
|
+
if( this.html ){ // reset stuff
|
|
72
|
+
this.url = this.selector = ''
|
|
73
|
+
this.cycle = this.indexes = 0;
|
|
74
|
+
}
|
|
75
|
+
this.fetchSource() // expect 'selector' not being set here
|
|
76
|
+
}else{
|
|
77
|
+
this.html = ''
|
|
78
|
+
this.updateTexture()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
23
82
|
this.createObject3D = function() {
|
|
24
83
|
var material = this.createMaterial();
|
|
25
84
|
var geo = new THREE.PlaneGeometry(2,2);
|
|
@@ -27,6 +86,14 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
27
86
|
var mesh = new THREE.Mesh(geo, material);
|
|
28
87
|
mesh.castShadow = this.shadow && this.shadow_cast;
|
|
29
88
|
mesh.receiveShadow = this.shadow && this.shadow_receive;
|
|
89
|
+
|
|
90
|
+
elation.events.add(this, 'click', () => {
|
|
91
|
+
if( this.indexes ){
|
|
92
|
+
this.index++
|
|
93
|
+
this.cycle = 0 // disable
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
30
97
|
return mesh;
|
|
31
98
|
}
|
|
32
99
|
this.createForces = function() {
|
|
@@ -37,9 +104,9 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
37
104
|
this.createMaterial = function() {
|
|
38
105
|
var texture = this.createTexture();
|
|
39
106
|
var sidemap = {
|
|
40
|
-
'back':
|
|
107
|
+
'back': THREE.FrontSide,
|
|
41
108
|
'front': THREE.BackSide,
|
|
42
|
-
'none':
|
|
109
|
+
'none': THREE.DoubleSide
|
|
43
110
|
};
|
|
44
111
|
var matargs = {
|
|
45
112
|
color: 0xffffff,
|
|
@@ -58,26 +125,28 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
58
125
|
}
|
|
59
126
|
this.createTexture = function() {
|
|
60
127
|
this.canvas = document.createElement('canvas');
|
|
61
|
-
this.canvas.width =
|
|
62
|
-
this.canvas.height =
|
|
128
|
+
this.canvas.width = this.width;
|
|
129
|
+
this.canvas.height = this.height;
|
|
63
130
|
this.texture = new THREE.Texture(this.canvas);
|
|
64
131
|
this.texture.encoding = THREE.sRGBEncoding;
|
|
65
|
-
this.updateTexture();
|
|
132
|
+
if( this.text ) this.updateTexture();
|
|
66
133
|
return this.texture;
|
|
67
134
|
}
|
|
68
135
|
this.updateTexture = function() {
|
|
69
136
|
if (!this.canvas || !this.texture) return;
|
|
70
137
|
var ctx = this.canvas.getContext('2d'),
|
|
71
138
|
texture = this.texture;
|
|
72
|
-
this.canvas.width =
|
|
73
|
-
this.canvas.height =
|
|
139
|
+
this.canvas.width = this.width;
|
|
140
|
+
this.canvas.height = this.height;
|
|
141
|
+
|
|
74
142
|
var text_col = '#' + this.text_col.getHexString(),
|
|
75
143
|
back_col = 'rgba(' + (this.back_col.r * 255) + ', ' + (this.back_col.g * 255) + ', ' + (this.back_col.b * 255) + ', ' + this.back_alpha + ')';
|
|
76
144
|
var basestyle = 'font-family: sans-serif;' +
|
|
77
145
|
'font-size: ' + this.font_size + 'px;' +
|
|
146
|
+
'box-sizing: border-box;' +
|
|
78
147
|
'color: ' + text_col + ';' +
|
|
79
148
|
'background: ' + (this.transparent ? 'transparent' : back_col) + ';' +
|
|
80
|
-
|
|
149
|
+
`max-width: ${this.width}px;` +
|
|
81
150
|
'padding: 5px;';
|
|
82
151
|
|
|
83
152
|
// We need to sanitize our HTML in case someone provides us with malformed markup.
|
|
@@ -92,6 +161,19 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
92
161
|
content = content.replace(/<img(.*?)>/g, "<img$1 />");
|
|
93
162
|
|
|
94
163
|
var styletag = '<style>.paragraphcontainer { ' + basestyle + '} .br { height: 1em; } .hr { margin: .5em 0; border: 1px inset #ccc; height: 0px; }';
|
|
164
|
+
styletag += 'a { color:unset; text-decoration: none; }' // dont confuse users with nonclickable links
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
// hybrid 2D/3D styling: apply styles from container HTML if any
|
|
169
|
+
styletag += [ ...(new DOMParser)
|
|
170
|
+
.parseFromString(room.fullsource,"text/html")
|
|
171
|
+
.querySelectorAll("style[type=\"text/css\"]")
|
|
172
|
+
]
|
|
173
|
+
.map( (el) => el.innerText )
|
|
174
|
+
.join("\n")
|
|
175
|
+
|
|
176
|
+
|
|
95
177
|
if (this.css) {
|
|
96
178
|
styletag += this.css;
|
|
97
179
|
}
|
|
@@ -147,13 +229,19 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
147
229
|
shadow: [ 'property', 'shadow' ],
|
|
148
230
|
shadow_receive: [ 'property', 'shadow_receive' ],
|
|
149
231
|
shadow_cast: [ 'property', 'shadow_cast' ],
|
|
232
|
+
url: [ 'property', 'url' ],
|
|
233
|
+
selector: [ 'property', 'selector' ],
|
|
234
|
+
index: ['property','index'],
|
|
235
|
+
cycle: ['property','cycle'],
|
|
236
|
+
width: ['property','width'],
|
|
237
|
+
height: ['property','height'],
|
|
238
|
+
ttl: ['property','ttl'],
|
|
150
239
|
};
|
|
151
240
|
}
|
|
152
241
|
return this._proxyobject;
|
|
153
242
|
}
|
|
154
243
|
|
|
155
244
|
this.toInlineHTML = async function(){
|
|
156
|
-
this.html = this.text
|
|
157
245
|
try{
|
|
158
246
|
// find all img tags and capture src attributes
|
|
159
247
|
const imgRegex = /<img[^>]*src=["'](?!data:)([^"']+)["']/gi;
|
|
@@ -171,19 +259,78 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
171
259
|
});
|
|
172
260
|
this.html = updatedHtml
|
|
173
261
|
}catch(e){ console.error(e) } // continue when inlining failed
|
|
174
|
-
this.updateTexture()
|
|
175
262
|
};
|
|
176
263
|
|
|
177
|
-
this.toDataURL = function(url){
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
264
|
+
this.toDataURL = async function(url){
|
|
265
|
+
const fullUrl = this.room.getFullRoomURL(url)
|
|
266
|
+
const finalUrl = this.room.isLocal( fullUrl) ? url : `${elation.engine.assets.corsproxy||''}${url}`
|
|
267
|
+
return await this.useCache(url, async () =>
|
|
268
|
+
await fetch(finalUrl)
|
|
269
|
+
.then(response => response.blob())
|
|
270
|
+
.then(blob => new Promise((resolve, reject) => {
|
|
271
|
+
const reader = new FileReader()
|
|
272
|
+
reader.onloadend = () => resolve(reader.result)
|
|
273
|
+
reader.onerror = reject
|
|
274
|
+
reader.readAsDataURL(blob)
|
|
275
|
+
}))
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
this.fetchSource = async function(){
|
|
280
|
+
if( this.url && this.fetchSource.last == this.url ) return // avoid fake updates
|
|
281
|
+
this.fetchSource.last = this.url || this.fetchSource.last
|
|
282
|
+
|
|
283
|
+
let translator = { // fallback translator for non-url content
|
|
284
|
+
translate: async () => [this.html],
|
|
285
|
+
fetch: async (uri) => {
|
|
286
|
+
if( String(this.text).match(/^data:text/) ){ // support text data-uri's
|
|
287
|
+
return await window.fetch(this.text).then( (r) => r.text() )
|
|
288
|
+
}
|
|
289
|
+
return this.text || room.fullsource
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// allow (via event) other scripts to select different translator based on url
|
|
293
|
+
room.dispatchEvent({type:'paragraph_translator', detail:{ paragraph:this, translator }})
|
|
294
|
+
this.html = await translator.fetch.call(this, this.url || ' ')
|
|
295
|
+
await this.toInlineHTML()
|
|
296
|
+
this.paragraphs = await translator.translate.apply(this)
|
|
297
|
+
await this.updateHTML()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.useCache = async function(url, myFetch) {
|
|
301
|
+
const cache = elation.janusweb.urlcache = elation.janusweb.urlcache || {}
|
|
302
|
+
if (cache[url]) return cache[url];
|
|
303
|
+
const data = cache[url] = await myFetch();
|
|
304
|
+
setTimeout(() => delete cache[url], this.ttl); // default 2 mins TTL cleanup
|
|
305
|
+
return data;
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
this.autoRotateIndex = function(){
|
|
309
|
+
if( this.paragraphs.length > 1 ){
|
|
310
|
+
if( this.cycle == 0 ){
|
|
311
|
+
clearInterval(this.intervalId )
|
|
312
|
+
this.intervalId = false
|
|
313
|
+
}
|
|
314
|
+
if( !this.intervalId && this.cycle ){
|
|
315
|
+
this.intervalId = setInterval( () => {
|
|
316
|
+
this.index = (this.index+1) % this.indexes
|
|
317
|
+
this.html = this.paragraphs[ this.index ]
|
|
318
|
+
}, this.cycle )
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this.updateHTML = async function(html){
|
|
324
|
+
// skip uninited url content
|
|
325
|
+
if( !this.html && this.url ) return
|
|
326
|
+
if( this.html ){
|
|
327
|
+
this.indexes = this.paragraphs.length > 1 ? this.paragraphs.length : 1
|
|
328
|
+
this.html = this.paragraphs[ this.index % this.indexes ]
|
|
329
|
+
if( !this.html || this.htmlLast == this.html ) return // skip fake updates
|
|
330
|
+
this.htmlLast = this.html
|
|
331
|
+
this.autoRotateIndex()
|
|
332
|
+
this.updateTexture()
|
|
333
|
+
}
|
|
187
334
|
}
|
|
188
335
|
|
|
189
336
|
}, elation.engine.things.janusbase);
|
package/scripts/janusxrplayer.js
CHANGED
|
@@ -412,7 +412,7 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
412
412
|
this.buttonwaspressed = false;
|
|
413
413
|
if (!this.menu.parent || this.menu.parent === this) {
|
|
414
414
|
player.appendChild(this.menu);
|
|
415
|
-
player.worldToLocal(this.pointer.localToWorld(this.menu.pos.set(0
|
|
415
|
+
player.worldToLocal(this.pointer.localToWorld(this.menu.pos.set(0,0,-.25)));
|
|
416
416
|
this.menu.billboard = 'y';
|
|
417
417
|
} else {
|
|
418
418
|
//this.appendChild(this.menu);
|
|
@@ -656,9 +656,8 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
656
656
|
}
|
|
657
657
|
},
|
|
658
658
|
handleRaycastEnter(ev) {
|
|
659
|
-
let proxyobj = ev.data.object
|
|
660
|
-
|
|
661
|
-
obj = proxyobj._target;
|
|
659
|
+
let proxyobj = ev.data.object,
|
|
660
|
+
obj = proxyobj._target;
|
|
662
661
|
let oldactiveobject = this.activeobject;
|
|
663
662
|
if (this.activeobject) {
|
|
664
663
|
this.engine.client.view.proxyEvent({
|
|
@@ -724,7 +723,6 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
724
723
|
this.engine.client.view.proxyEvent(evdata, evdata.element);
|
|
725
724
|
},
|
|
726
725
|
handleRaycastMove(ev) {
|
|
727
|
-
if( ! ev?.data?.intersection?.face?.normal ) return // skip those!
|
|
728
726
|
this.updateLaserEndpoint(ev.data.intersection.point, ev.data.intersection.face.normal);
|
|
729
727
|
if (this.activeobject) {
|
|
730
728
|
let obj = this.activeobject.objects['3d'];
|
|
@@ -781,7 +779,7 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
781
779
|
}
|
|
782
780
|
},
|
|
783
781
|
handleSelect(ev) {
|
|
784
|
-
if (this.activeobject
|
|
782
|
+
if (this.activeobject) {
|
|
785
783
|
let obj = this.activeobject.objects['3d'];
|
|
786
784
|
console.log('CLICK IT', this.activeobject);
|
|
787
785
|
this.engine.client.view.proxyEvent({
|
package/scripts/room.js
CHANGED
|
@@ -3,11 +3,16 @@ elation.require([
|
|
|
3
3
|
'engine.things.generic', 'engine.things.label', 'engine.things.skybox',
|
|
4
4
|
'janusweb.object', 'janusweb.portal', 'janusweb.image', 'janusweb.video', 'janusweb.text', 'janusweb.janusparagraph',
|
|
5
5
|
'janusweb.sound', 'janusweb.januslight', 'janusweb.janusparticle', 'janusweb.janusghost',
|
|
6
|
-
'janusweb.translators.bookmarks', 'janusweb.translators.reddit', 'janusweb.translators.error', 'janusweb.translators.blank', 'janusweb.translators.default', 'janusweb.translators.dat', 'janusweb.translators.janusvfs', 'janusweb.translators.xrfragments'
|
|
6
|
+
'janusweb.translators.bookmarks', 'janusweb.translators.reddit', 'janusweb.translators.error', 'janusweb.translators.blank', 'janusweb.translators.default', 'janusweb.translators.dat', 'janusweb.translators.janusvfs', 'janusweb.translators.xrfragments', 'janusweb.translators.peertube'
|
|
7
7
|
], function() {
|
|
8
8
|
let roomTranslators = false;
|
|
9
9
|
function initRoomTranslators(room) {
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
room.translators = roomTranslators = {
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
'^peertube$': elation.janusweb.translators.peertube({janus: janus}),
|
|
15
|
+
|
|
11
16
|
'^janus-vfs:': elation.janusweb.translators.janusvfs({janus: janus}),
|
|
12
17
|
'^about:blank$': elation.janusweb.translators.blank({janus: janus}),
|
|
13
18
|
'^bookmarks$': elation.janusweb.translators.bookmarks({janus: janus}),
|
|
@@ -15,7 +20,7 @@ elation.require([
|
|
|
15
20
|
'^https?:\/\/(www\.)?reddit.com': elation.janusweb.translators.reddit({janus: janus}),
|
|
16
21
|
'^error$': elation.janusweb.translators.error({janus: janus}),
|
|
17
22
|
'.*\.(gltf|glb|dae)$': elation.janusweb.translators.xrfragments({janus: janus}),
|
|
18
|
-
'^default$': elation.janusweb.translators.default({janus: janus})
|
|
23
|
+
'^default$': elation.janusweb.translators.default({janus: janus}),
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
elation.component.add('engine.things.janusroom', function() {
|
|
@@ -247,7 +252,8 @@ elation.require([
|
|
|
247
252
|
orientation = spawnpoint.orientation;
|
|
248
253
|
}
|
|
249
254
|
var player = this.engine.client.player;
|
|
250
|
-
if (player.
|
|
255
|
+
if( pos.equals(player.position) ) return // ignore
|
|
256
|
+
if (player.parent.id !== this.id) {
|
|
251
257
|
// Reparent player to the room if necessary
|
|
252
258
|
this.appendChild(player.getProxyObject());
|
|
253
259
|
}
|
|
@@ -298,16 +304,25 @@ elation.require([
|
|
|
298
304
|
spawnpoint.orientation.multiply(node.orientation);
|
|
299
305
|
}
|
|
300
306
|
spawnpoint.orientation.multiply(new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0))); // Flip 180 degrees from portal orientation
|
|
301
|
-
|
|
307
|
+
return spawnpoint;
|
|
302
308
|
}
|
|
303
309
|
}
|
|
304
|
-
}
|
|
310
|
+
}
|
|
311
|
+
if (this.urlhash) {
|
|
305
312
|
// XR Fragments deeplink spec (Level1: URL) https://xrfragment.org/#teleport%20camera
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
313
|
+
// backwards-compat: pos-names are deprecated
|
|
314
|
+
let obj
|
|
315
|
+
new URLSearchParams( this.urlhash.replace(/pos=/,'') ).forEach( (v,name) => {
|
|
316
|
+
let obj = this.getObjectById(name) || this.getObjectByDeepName(name)
|
|
317
|
+
if (obj) {
|
|
318
|
+
obj.localToWorld(spawnpoint.position.set(0,0,0));
|
|
319
|
+
if( obj.type == 'PerspectiveCamera' ){
|
|
320
|
+
spawnpoint.position.y -= 1.6 // https://xrfragment.org/#teleport%20camera%20spawnpoint
|
|
321
|
+
}
|
|
322
|
+
spawnpoint.orientation.setFromRotationMatrix(obj.objects['3d'].matrixWorld.lookAt(spawnpoint.position, obj.localToWorld(V(0,0,-1)), obj.localToWorld(V(0,1,0).sub(spawnpoint.position))));
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
if( obj ) elation.events.fire({element: this, type: 'href', data: {href,opts}});
|
|
311
326
|
}
|
|
312
327
|
return spawnpoint;
|
|
313
328
|
}
|
|
@@ -561,23 +576,28 @@ elation.require([
|
|
|
561
576
|
this.debugwindow.show();
|
|
562
577
|
this.debugwindow.center();
|
|
563
578
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
} else {
|
|
568
|
-
this.properties.url = url;
|
|
569
|
-
}
|
|
570
|
-
var baseurl = baseurloverride;
|
|
579
|
+
|
|
580
|
+
this.getBaseUrl = function(url, baseurloverride){
|
|
581
|
+
let baseurl = baseurloverride;
|
|
571
582
|
if (!baseurl) {
|
|
572
583
|
if (url && url.match("://") && !this.baseurl) {
|
|
573
584
|
baseurl = url.split('/');
|
|
574
585
|
if (baseurl.length > 3) baseurl.pop();
|
|
575
586
|
baseurl = baseurl.join('/') + '/';
|
|
576
587
|
}
|
|
577
|
-
|
|
588
|
+
return baseurl || document.location.origin + document.location.pathname;
|
|
578
589
|
} else {
|
|
579
|
-
|
|
590
|
+
return baseurl;
|
|
580
591
|
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
this.load = function(url, baseurloverride) {
|
|
595
|
+
if (!url) {
|
|
596
|
+
url = this.properties.url;
|
|
597
|
+
} else {
|
|
598
|
+
this.properties.url = url;
|
|
599
|
+
}
|
|
600
|
+
this.baseurl = this.getBaseUrl(url,baseurloverride)
|
|
581
601
|
|
|
582
602
|
this.jsobjects = {};
|
|
583
603
|
this.cookies = {};
|
|
@@ -588,14 +608,7 @@ elation.require([
|
|
|
588
608
|
this.setTitle('loading...');
|
|
589
609
|
|
|
590
610
|
var proxyurl = this.corsproxy || '';
|
|
591
|
-
|
|
592
|
-
var fullurl = url;
|
|
593
|
-
if (fullurl[0] == '/' && fullurl[1] != '/') fullurl = this.baseurl + fullurl;
|
|
594
|
-
if (!fullurl.match(/^https?:/) && !fullurl.match(/^\/\//)) {
|
|
595
|
-
fullurl = self.location.origin + (fullurl[0] == '/' ? '' :'/') + fullurl;
|
|
596
|
-
} else if (!fullurl.match(/^https?:\/\/(localhost|127\.0\.0\.1)/) && fullurl.indexOf(document.location.origin) != 0) {
|
|
597
|
-
fullurl = proxyurl + fullurl;
|
|
598
|
-
}
|
|
611
|
+
var fullurl = this.getFullRoomURL(url, proxyurl )
|
|
599
612
|
|
|
600
613
|
elation.events.fire({element: this, type: 'room_load_queued'});
|
|
601
614
|
|
|
@@ -1655,6 +1668,8 @@ elation.require([
|
|
|
1655
1668
|
assetlist.push({
|
|
1656
1669
|
assettype: 'shader',
|
|
1657
1670
|
name: args.id,
|
|
1671
|
+
shadertype: args.shadertype || 'default',
|
|
1672
|
+
hasalpha: args.hasalpha,
|
|
1658
1673
|
fragment_src: args.src,
|
|
1659
1674
|
vertex_src: args.vertex_src,
|
|
1660
1675
|
uniforms: args.uniforms,
|
|
@@ -2103,7 +2118,7 @@ elation.require([
|
|
|
2103
2118
|
}
|
|
2104
2119
|
var urls = [this.url];
|
|
2105
2120
|
for (var i = 0; i < assets.length; i++) {
|
|
2106
|
-
var url = assets[i].
|
|
2121
|
+
var url = assets[i].getFullRoomURL();
|
|
2107
2122
|
urls.push(url);
|
|
2108
2123
|
}
|
|
2109
2124
|
return urls;
|
|
@@ -2828,14 +2843,22 @@ console.log('dispatch to parent', event, this, event.target);
|
|
|
2828
2843
|
this.handleDelayedSound = function(ev) {
|
|
2829
2844
|
// TODO - implement some visual indicator that this room is trying to play sound but was temporarily blocked
|
|
2830
2845
|
}
|
|
2831
|
-
this.getFullRoomURL = function(url) {
|
|
2832
|
-
|
|
2833
|
-
if (
|
|
2834
|
-
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2846
|
+
this.getFullRoomURL = function(url, proxyurl) {
|
|
2847
|
+
let fullurl = url;
|
|
2848
|
+
if (fullurl[0] == '/' && fullurl[1] != '/'){
|
|
2849
|
+
fullurl = this.baseurl.replace(/^(https?:\/\/[^\/]+)\/.*$/, '$1') + fullurl;
|
|
2850
|
+
}else if (!fullurl.match(/^https?:/) && !fullurl.match(/^\/\//)) {
|
|
2851
|
+
fullurl = this.baseurl + (
|
|
2852
|
+
fullurl.match(/^\.\//) ? fullurl.replace(/^\.\//,'') // './index.html' e.g.
|
|
2853
|
+
: fullurl
|
|
2854
|
+
)
|
|
2855
|
+
} else if (proxyurl && !this.isLocal(fullurl) ){
|
|
2856
|
+
fullurl = proxyurl + fullurl;
|
|
2837
2857
|
}
|
|
2838
|
-
return
|
|
2858
|
+
return fullurl
|
|
2859
|
+
}
|
|
2860
|
+
this.isLocal = function(url){
|
|
2861
|
+
return url.match(/^https?:\/\/(localhost|127\.0\.0\.1)/) || url.indexOf(document.location.origin) == 0
|
|
2839
2862
|
}
|
|
2840
2863
|
this.dispose = function() {
|
|
2841
2864
|
if (this.assetpack) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// this translator converts the following url-output to HTML:
|
|
2
|
+
// * HTML
|
|
3
|
+
// * XML
|
|
4
|
+
// * RSS
|
|
5
|
+
//
|
|
6
|
+
// NOTE1: to not bloat the core-codebase please define custom translators
|
|
7
|
+
// in assetscripts or webui apps. Thank you.
|
|
8
|
+
//
|
|
9
|
+
// NOTE2: 'this' is basically a paragraph janus object
|
|
10
|
+
|
|
11
|
+
(function(){
|
|
12
|
+
|
|
13
|
+
let XMLTranslator = {
|
|
14
|
+
fetch: async function(uri){
|
|
15
|
+
const uriExFragment = String(uri).replace(/#.*/,'')
|
|
16
|
+
const finalUrl = `${elation.engine.assets.corsproxy||''}${uriExFragment||''}`
|
|
17
|
+
return await this.useCache( finalUrl, async () =>
|
|
18
|
+
await fetch( finalUrl )
|
|
19
|
+
.then( (res) => res.text() )
|
|
20
|
+
)
|
|
21
|
+
},
|
|
22
|
+
translate: async function(){ // generic XML/RSS/HTML preprocessor [this.text to this.html]
|
|
23
|
+
if( !this.html ) return [""]
|
|
24
|
+
if( this.selector && typeof this.index != 'undefined' ){
|
|
25
|
+
const type = this.html.match(/<(rss|feed)/) ? "text/xml" : "text/html"
|
|
26
|
+
const parser = new DOMParser()
|
|
27
|
+
const xmlDoc = parser.parseFromString( this.html, type)
|
|
28
|
+
let paragraphs = [ ...xmlDoc.querySelectorAll(this.selector) ] // KiSS JML: CSS selectors level 1
|
|
29
|
+
if( !paragraphs.length ){
|
|
30
|
+
console.error(`paragraph: level1 css selector '${this.selector}' not matching anything`)
|
|
31
|
+
}
|
|
32
|
+
return paragraphs.map( (p) => type == 'text/xml' ? p.textContent : p.outerHTML )
|
|
33
|
+
}
|
|
34
|
+
return [this.html]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
elation.events.add(null, 'paragraph_translator', function(e){
|
|
39
|
+
const {translator,paragraph} = e.detail
|
|
40
|
+
// now you can override the default translator
|
|
41
|
+
if( String(paragraph.url).match(/^http/) ){
|
|
42
|
+
translator.fetch = XMLTranslator.fetch
|
|
43
|
+
}
|
|
44
|
+
translator.translate = XMLTranslator.translate // boldly always set ourselfs as default
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
})();
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
elation.require([], function() {
|
|
2
|
+
elation.component.add('janusweb.translators.peertube', function() {
|
|
3
|
+
this.init = function() {
|
|
4
|
+
this.description = "peertube website to 3D translator"
|
|
5
|
+
}
|
|
6
|
+
this.exec = function(args) {
|
|
7
|
+
return new Promise(elation.bind(this, function(resolve, reject) {
|
|
8
|
+
|
|
9
|
+
var room = this.room = args.room;
|
|
10
|
+
|
|
11
|
+
reject() // for now
|
|
12
|
+
|
|
13
|
+
this.setupEvents()
|
|
14
|
+
var datapath = elation.config.get('janusweb.datapath', '/media/janusweb');
|
|
15
|
+
|
|
16
|
+
var roomdata = {
|
|
17
|
+
assets: {
|
|
18
|
+
assetlist: [
|
|
19
|
+
// {assettype: 'model', name: 'scene', src: args.url},
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
room: {
|
|
23
|
+
pos: [0,0,0],
|
|
24
|
+
xdir: "1 0 0",
|
|
25
|
+
zdir: "0 0 1",
|
|
26
|
+
},
|
|
27
|
+
object: [
|
|
28
|
+
// {id: 'scene', js_id: 0, pos: "0 0 0", xdir: "-1 0 0", zdir: "0 0 -1", lighting: "false"}
|
|
29
|
+
],
|
|
30
|
+
link: []
|
|
31
|
+
};
|
|
32
|
+
resolve(roomdata);
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// translate XR Fragments microformat into JML
|
|
37
|
+
this.parseSource = async function(sourcecode, room){
|
|
38
|
+
this.room = room
|
|
39
|
+
const isJML = /<fireboxroom>[\s\S]*?<\/fireboxroom>/si;
|
|
40
|
+
const isPeertube = /<meta\s+[^>]*property=['"]og:platform['"]\s+[^>]*content=['"]peertube['"][^>]*\/?>/si;
|
|
41
|
+
const isPeertubeVideo = /<meta\s+[^>]*property=['"]og:type['"]\s+[^>]*content=['"]video['"][^>]*\/?>/si;
|
|
42
|
+
const isPeertubeInstance = /<meta\s+[^>]*property=['"]og:type['"]\s+[^>]*content=['"]website['"][^>]*\/?>/si;
|
|
43
|
+
const is = {
|
|
44
|
+
JML: sourcecode.match(isJML),
|
|
45
|
+
peertube: sourcecode.match(isPeertube),
|
|
46
|
+
peertubeVideo: sourcecode.match(isPeertubeVideo),
|
|
47
|
+
peertubeInstance: sourcecode.match(isPeertubeInstance),
|
|
48
|
+
}
|
|
49
|
+
if( is.JML || !is.peertube ) return // JML takes precedence over microformats
|
|
50
|
+
if( !is.peertubeVideo && !is.peertubeInstance ) return
|
|
51
|
+
|
|
52
|
+
// ok..peertube time: get domtree
|
|
53
|
+
let el = document.createElement("div")
|
|
54
|
+
el.innerHTML = sourcecode
|
|
55
|
+
|
|
56
|
+
// try to fetch title + cover
|
|
57
|
+
const title = el.querySelector("title")
|
|
58
|
+
let logo = el.querySelector("meta[property='og:image']")?.getAttribute("content")
|
|
59
|
+
logo = logo ? logo : el.querySelector("meta[property='og:image:url']")?.getAttribute("content")
|
|
60
|
+
|
|
61
|
+
let url = new URL( String(room.url) )
|
|
62
|
+
let instance = url?.origin
|
|
63
|
+
let search = url?.search ? new URLSearchParams(url.search).get("search") : ""
|
|
64
|
+
|
|
65
|
+
// return JML
|
|
66
|
+
return room.parseSource(`
|
|
67
|
+
<title>${ title ? title.innerText.replace(/\n.*/g,'') : room.baseurl.split("/").pop() }</title>
|
|
68
|
+
<FireBoxRoom>
|
|
69
|
+
<Assets>
|
|
70
|
+
${ logo ? `<assetimage src="${logo}" id="logo"/>` : ''}
|
|
71
|
+
<assetscript src="https://codeberg.org/coderofsalvation/janus-script-peertube/raw/branch/master/build/janus-script-peertube.js"/>
|
|
72
|
+
<assetobject src="https://codeberg.org/coderofsalvation/janus-script-peertube/raw/branch/master/build/asset/button.glb" id="button"/>
|
|
73
|
+
<assetobject src="https://codeberg.org/coderofsalvation/janus-script-peertube/raw/branch/master/build/asset/button.icon.glb" id="icon"/>
|
|
74
|
+
<assetsound src="https://codeberg.org/coderofsalvation/janus-script-peertube/raw/branch/master/build/asset/button.click.mp3" id="button-click"/>
|
|
75
|
+
<assetsound src="https://codeberg.org/coderofsalvation/janus-script-peertube/raw/branch/master/build/asset/button.hover.mp3" id="button-hover"/>
|
|
76
|
+
</Assets>
|
|
77
|
+
<Room use_local_asset="room_2pedestal" pos="1.15 0 -5" rotation="0 1.5708 0" fwd="1 0 0">
|
|
78
|
+
${ is.peertubeInstance ? `<peertube-app pos="4.55 2.71 -4.7" scale="2 2 1" rotation="0 90 0" js_id="mypeertube-app" instance="${instance}" ${search?`search="${search}"`:``} autoload="true"/>`
|
|
79
|
+
: `<peertube pos="4.55 2.71 -4.7" scale="2 2 1" rotation="0 90 0" js_id="peertube" src="${room.url}" auto_play="true"/>`
|
|
80
|
+
}
|
|
81
|
+
${ logo ? `<image id="logo"/>` : ''}
|
|
82
|
+
</Room>
|
|
83
|
+
</FireBoxRoom>
|
|
84
|
+
`)
|
|
85
|
+
}
|
|
86
|
+
// peertube microformat heuristic: <meta property="og:platform" content="PeerTube">
|
|
87
|
+
this.parseSource.regex = /<meta\s+[^>]*property=['"]og:platform['"]\s+[^>]*content=['"]peertube['"][^>]*\/?>/si;
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -18,12 +18,13 @@ elation.require([], function() {
|
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
room: {
|
|
21
|
+
gravity: 0,
|
|
21
22
|
pos: [0,0,0],
|
|
22
23
|
xdir: "1 0 0",
|
|
23
24
|
zdir: "0 0 1",
|
|
24
25
|
},
|
|
25
26
|
object: [
|
|
26
|
-
{id: 'scene', js_id:
|
|
27
|
+
{id: 'scene', js_id: "scene", pos: "0 0 0", xdir: "-1 0 0", zdir: "0 0 -1"}
|
|
27
28
|
],
|
|
28
29
|
link: []
|
|
29
30
|
};
|
|
@@ -34,8 +35,8 @@ elation.require([], function() {
|
|
|
34
35
|
this.spawnUserAtFragment = function(source) {
|
|
35
36
|
// XR Fragments deeplink spec: explicit or default spawn
|
|
36
37
|
// https://xrfragment.org/#teleport%20camera
|
|
37
|
-
if( !
|
|
38
|
-
|
|
38
|
+
if( ! room.urlhash ) room.urlhash = 'spawn'
|
|
39
|
+
room.setPlayerPosition.apply(room)
|
|
39
40
|
console.log("[xrfragment] camera teleport")
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -78,7 +79,7 @@ elation.require([], function() {
|
|
|
78
79
|
<Assets>
|
|
79
80
|
<assetobject id="scene" src="${hrefNoHash}"/>
|
|
80
81
|
</Assets>
|
|
81
|
-
<Room>
|
|
82
|
+
<Room gravity="0">
|
|
82
83
|
<object pos="0 0 0" collision_id="scene" id="scene" />
|
|
83
84
|
</Room>
|
|
84
85
|
</FireBoxRoom>
|
|
@@ -90,4 +91,3 @@ elation.require([], function() {
|
|
|
90
91
|
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
|
-
|