janusweb 1.5.56 → 1.7.1
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 +258 -19
- 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 +16 -0
- package/media/assets/webui/apps/locomotion/locomotion-assets.json +3 -0
- package/media/assets/webui/apps/locomotion/teleport.mp3 +0 -0
- package/media/assets/webui/apps/locomotion/teleporter.js +75 -43
- package/media/assets/webui/apps/navigation/navigation.js +6 -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/apps/xrmenu/images/teleport.png +0 -0
- package/media/assets/webui/apps/xrmenu/xrmenu-assets.json +2 -1
- package/media/assets/webui/apps/xrmenu/xrmenu.js +39 -15
- package/media/assets/webui/default.json +1 -0
- package/media/images/portal.svg +130 -0
- package/media/index.html +48 -1
- package/package.json +1 -1
- package/scripts/client.js +24 -0
- package/scripts/config.js +12 -1
- package/scripts/janusbase.js +14 -22
- package/scripts/janusghost.js +2 -2
- package/scripts/janusparagraph.js +195 -12
- package/scripts/janusplayer.js +9 -6
- package/scripts/room.js +91 -44
- package/scripts/sound.js +1 -1
- package/scripts/text.js +2 -1
- package/scripts/translators/paragraph/html-xml-rss.js +47 -0
- package/scripts/translators/peertube.js +89 -0
- package/scripts/translators/xrfragments.js +93 -0
- package/tests/room/paragraph.xml +47 -0
- package/utils/build.sh +1 -0
- package/utils/clean-build.sh +5 -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},
|
|
@@ -11,14 +45,40 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
11
45
|
cull_face: { type: 'string', default: 'back', set: this.updateMaterial },
|
|
12
46
|
css: {type: 'string', set: this.updateTexture },
|
|
13
47
|
depth_write: { type: 'boolean', default: true },
|
|
48
|
+
transparent: {type: 'boolean', set: this.updateTexture },
|
|
14
49
|
depth_test: { type: 'boolean', default: true },
|
|
15
50
|
collision_id: { type: 'string', default: 'cube' },
|
|
16
51
|
collision_scale: { type: 'vector3', default: V(.5, .5, .02) },
|
|
17
52
|
shadow: { type: 'boolean', default: true, set: this.updateMaterial },
|
|
18
53
|
shadow_receive: { type: 'boolean', default: true, set: this.updateMaterial, comment: 'Receive shadows from self and other objects' },
|
|
19
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)' },
|
|
20
62
|
});
|
|
21
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
|
+
|
|
22
82
|
this.createObject3D = function() {
|
|
23
83
|
var material = this.createMaterial();
|
|
24
84
|
var geo = new THREE.PlaneGeometry(2,2);
|
|
@@ -26,6 +86,14 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
26
86
|
var mesh = new THREE.Mesh(geo, material);
|
|
27
87
|
mesh.castShadow = this.shadow && this.shadow_cast;
|
|
28
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
|
+
|
|
29
97
|
return mesh;
|
|
30
98
|
}
|
|
31
99
|
this.createForces = function() {
|
|
@@ -36,9 +104,9 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
36
104
|
this.createMaterial = function() {
|
|
37
105
|
var texture = this.createTexture();
|
|
38
106
|
var sidemap = {
|
|
39
|
-
'back':
|
|
107
|
+
'back': THREE.FrontSide,
|
|
40
108
|
'front': THREE.BackSide,
|
|
41
|
-
'none':
|
|
109
|
+
'none': THREE.DoubleSide
|
|
42
110
|
};
|
|
43
111
|
var matargs = {
|
|
44
112
|
color: 0xffffff,
|
|
@@ -57,26 +125,28 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
57
125
|
}
|
|
58
126
|
this.createTexture = function() {
|
|
59
127
|
this.canvas = document.createElement('canvas');
|
|
60
|
-
this.canvas.width =
|
|
61
|
-
this.canvas.height =
|
|
128
|
+
this.canvas.width = this.width;
|
|
129
|
+
this.canvas.height = this.height;
|
|
62
130
|
this.texture = new THREE.Texture(this.canvas);
|
|
63
131
|
this.texture.encoding = THREE.sRGBEncoding;
|
|
64
|
-
this.updateTexture();
|
|
132
|
+
if( this.text ) this.updateTexture();
|
|
65
133
|
return this.texture;
|
|
66
134
|
}
|
|
67
135
|
this.updateTexture = function() {
|
|
68
136
|
if (!this.canvas || !this.texture) return;
|
|
69
137
|
var ctx = this.canvas.getContext('2d'),
|
|
70
138
|
texture = this.texture;
|
|
71
|
-
this.canvas.width =
|
|
72
|
-
this.canvas.height =
|
|
139
|
+
this.canvas.width = this.width;
|
|
140
|
+
this.canvas.height = this.height;
|
|
141
|
+
|
|
73
142
|
var text_col = '#' + this.text_col.getHexString(),
|
|
74
143
|
back_col = 'rgba(' + (this.back_col.r * 255) + ', ' + (this.back_col.g * 255) + ', ' + (this.back_col.b * 255) + ', ' + this.back_alpha + ')';
|
|
75
144
|
var basestyle = 'font-family: sans-serif;' +
|
|
76
145
|
'font-size: ' + this.font_size + 'px;' +
|
|
146
|
+
'box-sizing: border-box;' +
|
|
77
147
|
'color: ' + text_col + ';' +
|
|
78
|
-
'background: ' + back_col + ';' +
|
|
79
|
-
|
|
148
|
+
'background: ' + (this.transparent ? 'transparent' : back_col) + ';' +
|
|
149
|
+
`max-width: ${this.width}px;` +
|
|
80
150
|
'padding: 5px;';
|
|
81
151
|
|
|
82
152
|
// We need to sanitize our HTML in case someone provides us with malformed markup.
|
|
@@ -85,12 +155,25 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
85
155
|
// styled divs instead.
|
|
86
156
|
|
|
87
157
|
var sanitarydiv = document.createElement('div');
|
|
88
|
-
sanitarydiv.innerHTML = this.
|
|
158
|
+
sanitarydiv.innerHTML = this.html;
|
|
89
159
|
var content = sanitarydiv.innerHTML.replace(/<br\s*\/?>/g, '<div class="br"></div>');
|
|
90
160
|
content = content.replace(/<hr\s*\/?>/g, '<div class="hr"></div>');
|
|
91
161
|
content = content.replace(/<img(.*?)>/g, "<img$1 />");
|
|
92
162
|
|
|
93
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
|
+
|
|
94
177
|
if (this.css) {
|
|
95
178
|
styletag += this.css;
|
|
96
179
|
}
|
|
@@ -146,9 +229,109 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
146
229
|
shadow: [ 'property', 'shadow' ],
|
|
147
230
|
shadow_receive: [ 'property', 'shadow_receive' ],
|
|
148
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'],
|
|
149
239
|
};
|
|
150
240
|
}
|
|
151
241
|
return this._proxyobject;
|
|
152
242
|
}
|
|
243
|
+
|
|
244
|
+
this.toInlineHTML = async function(){
|
|
245
|
+
try{
|
|
246
|
+
// find all img tags and capture src attributes
|
|
247
|
+
const imgRegex = /<img[^>]*src=["'](?!data:)([^"']+)["']/gi;
|
|
248
|
+
const matches = [];
|
|
249
|
+
let match;
|
|
250
|
+
while ((match = imgRegex.exec(this.html)) !== null) { // Collect all src values
|
|
251
|
+
matches.push(match[1]);
|
|
252
|
+
}
|
|
253
|
+
// fill array with base64 image-strings
|
|
254
|
+
const dataURLs = await Promise.all( matches.map(src => this.toDataURL(src)) );
|
|
255
|
+
// Replace each src in the html with the corresponding data URL
|
|
256
|
+
let updatedHtml = this.html;
|
|
257
|
+
matches.forEach((src, i) => {
|
|
258
|
+
updatedHtml = updatedHtml.replace( `src="${src}"`, `src="${dataURLs[i]}"`)
|
|
259
|
+
});
|
|
260
|
+
this.html = updatedHtml
|
|
261
|
+
}catch(e){ console.error(e) } // continue when inlining failed
|
|
262
|
+
};
|
|
263
|
+
|
|
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
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
153
336
|
}, elation.engine.things.janusbase);
|
|
154
337
|
});
|
package/scripts/janusplayer.js
CHANGED
|
@@ -56,7 +56,7 @@ elation.require(['engine.things.player', 'janusweb.external.JanusVOIP', 'ui.butt
|
|
|
56
56
|
elation.events.add(this.engine.client.view.container, 'touchcancel', elation.bind(this, this.handleTouchEnd));
|
|
57
57
|
|
|
58
58
|
this.controlstate2 = this.engine.systems.controls.addContext('janusplayer', {
|
|
59
|
-
'toggle_view': ['keyboard_nomod_v,keyboard_shift_v',
|
|
59
|
+
'toggle_view': ['keyboard_nomod_v,keyboard_shift_v', ev => { if (ev.value == 1) this.toggleCamera() }],
|
|
60
60
|
'zoom_out': ['mouse_wheel_down', ev => this.zoomView(-1, ev)],
|
|
61
61
|
'zoom_in': ['mouse_wheel_up', ev => this.zoomView(1, ev)],
|
|
62
62
|
//'browse_back': ['gamepad_any_button_4', elation.bind(this, this.browseBack)],
|
|
@@ -945,9 +945,9 @@ document.body.dispatchEvent(click);
|
|
|
945
945
|
*/
|
|
946
946
|
this.pickable = false;
|
|
947
947
|
this.setCollider('capsule', {
|
|
948
|
-
radius: this.
|
|
949
|
-
length:
|
|
950
|
-
offset: V(0, this.
|
|
948
|
+
radius: this.fatness,
|
|
949
|
+
length: this.height - (this.fatness * 2),
|
|
950
|
+
offset: V(0, this.fatness, 0)
|
|
951
951
|
});
|
|
952
952
|
} else {
|
|
953
953
|
this.removeCollider();
|
|
@@ -1230,8 +1230,11 @@ document.body.dispatchEvent(click);
|
|
|
1230
1230
|
this.defaultanimation = 'portal';
|
|
1231
1231
|
setTimeout(() => this.defaultanimation = 'idle', 3000);
|
|
1232
1232
|
}
|
|
1233
|
-
this.toggleCamera = function(
|
|
1234
|
-
if (
|
|
1233
|
+
this.toggleCamera = function(arg) {
|
|
1234
|
+
if (typeof arg == 'string') {
|
|
1235
|
+
this.setCameraView(arg);
|
|
1236
|
+
} else if (!arg || arg.value) {
|
|
1237
|
+
// Legacy handling - if arg is a control input event, check to make sure this is a button press, not a release
|
|
1235
1238
|
if (this.cameraview == 'firstperson') {
|
|
1236
1239
|
this.setCameraView('thirdperson');
|
|
1237
1240
|
} else {
|
package/scripts/room.js
CHANGED
|
@@ -3,19 +3,25 @@ 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'
|
|
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
|
-
function initRoomTranslators() {
|
|
10
|
-
|
|
9
|
+
function initRoomTranslators(room) {
|
|
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}),
|
|
14
19
|
'^dat:': elation.janusweb.translators.dat({janus: janus}),
|
|
15
20
|
'^https?:\/\/(www\.)?reddit.com': elation.janusweb.translators.reddit({janus: janus}),
|
|
16
21
|
'^error$': elation.janusweb.translators.error({janus: janus}),
|
|
17
|
-
'
|
|
18
|
-
|
|
22
|
+
'.*\.(gltf|glb|dae)$': elation.janusweb.translators.xrfragments({janus: janus}),
|
|
23
|
+
'^default$': elation.janusweb.translators.default({janus: janus}),
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
elation.component.add('engine.things.janusroom', function() {
|
|
21
27
|
this.postinit = function() {
|
|
@@ -100,7 +106,7 @@ elation.require([
|
|
|
100
106
|
});
|
|
101
107
|
|
|
102
108
|
if (!roomTranslators) {
|
|
103
|
-
initRoomTranslators();
|
|
109
|
+
initRoomTranslators(this);
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
this.spawnpoint = new THREE.Object3D();
|
|
@@ -246,7 +252,8 @@ elation.require([
|
|
|
246
252
|
orientation = spawnpoint.orientation;
|
|
247
253
|
}
|
|
248
254
|
var player = this.engine.client.player;
|
|
249
|
-
if (player.
|
|
255
|
+
if( pos.equals(player.position) ) return // ignore
|
|
256
|
+
if (player.parent.id !== this.id) {
|
|
250
257
|
// Reparent player to the room if necessary
|
|
251
258
|
this.appendChild(player.getProxyObject());
|
|
252
259
|
}
|
|
@@ -297,15 +304,25 @@ elation.require([
|
|
|
297
304
|
spawnpoint.orientation.multiply(node.orientation);
|
|
298
305
|
}
|
|
299
306
|
spawnpoint.orientation.multiply(new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0))); // Flip 180 degrees from portal orientation
|
|
300
|
-
|
|
307
|
+
return spawnpoint;
|
|
301
308
|
}
|
|
302
309
|
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
310
|
+
}
|
|
311
|
+
if (this.urlhash) {
|
|
312
|
+
// XR Fragments deeplink spec (Level1: URL) https://xrfragment.org/#teleport%20camera
|
|
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}});
|
|
309
326
|
}
|
|
310
327
|
return spawnpoint;
|
|
311
328
|
}
|
|
@@ -559,23 +576,28 @@ elation.require([
|
|
|
559
576
|
this.debugwindow.show();
|
|
560
577
|
this.debugwindow.center();
|
|
561
578
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
} else {
|
|
566
|
-
this.properties.url = url;
|
|
567
|
-
}
|
|
568
|
-
var baseurl = baseurloverride;
|
|
579
|
+
|
|
580
|
+
this.getBaseUrl = function(url, baseurloverride){
|
|
581
|
+
let baseurl = baseurloverride;
|
|
569
582
|
if (!baseurl) {
|
|
570
|
-
if (url && !this.baseurl) {
|
|
583
|
+
if (url && url.match("://") && !this.baseurl) {
|
|
571
584
|
baseurl = url.split('/');
|
|
572
585
|
if (baseurl.length > 3) baseurl.pop();
|
|
573
586
|
baseurl = baseurl.join('/') + '/';
|
|
574
|
-
this.baseurl = baseurl;
|
|
575
587
|
}
|
|
588
|
+
return baseurl || document.location.origin + document.location.pathname;
|
|
576
589
|
} else {
|
|
577
|
-
|
|
590
|
+
return baseurl;
|
|
578
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)
|
|
579
601
|
|
|
580
602
|
this.jsobjects = {};
|
|
581
603
|
this.cookies = {};
|
|
@@ -586,14 +608,7 @@ elation.require([
|
|
|
586
608
|
this.setTitle('loading...');
|
|
587
609
|
|
|
588
610
|
var proxyurl = this.corsproxy || '';
|
|
589
|
-
|
|
590
|
-
var fullurl = url;
|
|
591
|
-
if (fullurl[0] == '/' && fullurl[1] != '/') fullurl = this.baseurl + fullurl;
|
|
592
|
-
if (!fullurl.match(/^https?:/) && !fullurl.match(/^\/\//)) {
|
|
593
|
-
fullurl = self.location.origin + fullurl;
|
|
594
|
-
} else if (!fullurl.match(/^https?:\/\/(localhost|127\.0\.0\.1)/) && fullurl.indexOf(document.location.origin) != 0) {
|
|
595
|
-
fullurl = proxyurl + fullurl;
|
|
596
|
-
}
|
|
611
|
+
var fullurl = this.getFullRoomURL(url, proxyurl )
|
|
597
612
|
|
|
598
613
|
elation.events.fire({element: this, type: 'room_load_queued'});
|
|
599
614
|
|
|
@@ -706,11 +721,22 @@ elation.require([
|
|
|
706
721
|
}
|
|
707
722
|
}));
|
|
708
723
|
}
|
|
709
|
-
this.loadFromSource = function(sourcecode, baseurl) {
|
|
724
|
+
this.loadFromSource = async function(sourcecode, baseurl) {
|
|
710
725
|
if (baseurl) {
|
|
711
726
|
this.baseurl = baseurl;
|
|
712
727
|
}
|
|
713
|
-
|
|
728
|
+
|
|
729
|
+
var source
|
|
730
|
+
// scan sourcecode for non-JML microformats within translators
|
|
731
|
+
for( m in roomTranslators ){
|
|
732
|
+
let rt = roomTranslators[m]
|
|
733
|
+
if( rt.parseSource && rt.parseSource.regex ){
|
|
734
|
+
source = await rt.parseSource(sourcecode,this)
|
|
735
|
+
if( source ) break;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if( !source ) source = this.parseSource(sourcecode)
|
|
739
|
+
|
|
714
740
|
if (source && source.source) {
|
|
715
741
|
this.roomsrc = source.source;
|
|
716
742
|
var datapath = elation.config.get('janusweb.datapath', '/media/janusweb');
|
|
@@ -1642,6 +1668,8 @@ elation.require([
|
|
|
1642
1668
|
assetlist.push({
|
|
1643
1669
|
assettype: 'shader',
|
|
1644
1670
|
name: args.id,
|
|
1671
|
+
shadertype: args.shadertype || 'default',
|
|
1672
|
+
hasalpha: args.hasalpha,
|
|
1645
1673
|
fragment_src: args.src,
|
|
1646
1674
|
vertex_src: args.vertex_src,
|
|
1647
1675
|
uniforms: args.uniforms,
|
|
@@ -1955,8 +1983,19 @@ elation.require([
|
|
|
1955
1983
|
*/
|
|
1956
1984
|
|
|
1957
1985
|
}
|
|
1958
|
-
this.
|
|
1959
|
-
|
|
1986
|
+
this.getObjectByDeepName = function(name) {
|
|
1987
|
+
if( !this.janus.currentroom ) return
|
|
1988
|
+
let obj = this.janus.currentroom.objects['3d'].getObjectByName(name)
|
|
1989
|
+
if( obj ){ // return polyglot THREE/janusweb object for convenience
|
|
1990
|
+
return new Proxy(obj,{
|
|
1991
|
+
set(me,k,v){ obj[k] = v; return true; },
|
|
1992
|
+
get(me,k){
|
|
1993
|
+
if( k == 'objects' ) return { '3d': me }
|
|
1994
|
+
return obj[k];
|
|
1995
|
+
}
|
|
1996
|
+
})
|
|
1997
|
+
}
|
|
1998
|
+
return obj
|
|
1960
1999
|
}
|
|
1961
2000
|
this.getObjectsByClassName = function(classname) {
|
|
1962
2001
|
var objects = [];
|
|
@@ -2079,7 +2118,7 @@ elation.require([
|
|
|
2079
2118
|
}
|
|
2080
2119
|
var urls = [this.url];
|
|
2081
2120
|
for (var i = 0; i < assets.length; i++) {
|
|
2082
|
-
var url = assets[i].
|
|
2121
|
+
var url = assets[i].getFullRoomURL();
|
|
2083
2122
|
urls.push(url);
|
|
2084
2123
|
}
|
|
2085
2124
|
return urls;
|
|
@@ -2804,14 +2843,22 @@ console.log('dispatch to parent', event, this, event.target);
|
|
|
2804
2843
|
this.handleDelayedSound = function(ev) {
|
|
2805
2844
|
// TODO - implement some visual indicator that this room is trying to play sound but was temporarily blocked
|
|
2806
2845
|
}
|
|
2807
|
-
this.getFullRoomURL = function(url) {
|
|
2808
|
-
|
|
2809
|
-
if (
|
|
2810
|
-
|
|
2811
|
-
}
|
|
2812
|
-
|
|
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;
|
|
2813
2857
|
}
|
|
2814
|
-
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
|
|
2815
2862
|
}
|
|
2816
2863
|
this.dispose = function() {
|
|
2817
2864
|
if (this.assetpack) {
|
package/scripts/sound.js
CHANGED
|
@@ -65,7 +65,7 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
65
65
|
}
|
|
66
66
|
//this.audio.panner.maxDistance = this.properties.distance;
|
|
67
67
|
if (this.dist) {
|
|
68
|
-
this.audio.setRefDistance(this.dist);
|
|
68
|
+
this.audio.setRefDistance(Math.max(0, this.dist));
|
|
69
69
|
} else {
|
|
70
70
|
//this.audio.panner.distanceModel = 'linear';
|
|
71
71
|
}
|
package/scripts/text.js
CHANGED
|
@@ -11,7 +11,7 @@ elation.require(['engine.things.label'], function() {
|
|
|
11
11
|
'align': { type: 'string', default: 'center', refreshGeometry: true },
|
|
12
12
|
'verticalalign': { type: 'string', default: 'bottom', refreshGeometry: true },
|
|
13
13
|
'zalign': { type: 'string', default: 'back', refreshGeometry: true },
|
|
14
|
-
'emissive': { type: 'color', default: 0x000000 },
|
|
14
|
+
'emissive': { type: 'color', default: 0x000000, set: this.updateMaterial },
|
|
15
15
|
'opacity': { type: 'float', default: 1.0 },
|
|
16
16
|
'depth_write': { type: 'bool', default: true },
|
|
17
17
|
'depth_test': { type: 'bool', default: true },
|
|
@@ -120,6 +120,7 @@ elation.require(['engine.things.label'], function() {
|
|
|
120
120
|
this.material.metalness = this.metalness;
|
|
121
121
|
this.material.envMap = this.getEnvmap();
|
|
122
122
|
}
|
|
123
|
+
this.material.emissive = this.emissive;
|
|
123
124
|
this.material.needsUpdate = true;
|
|
124
125
|
}
|
|
125
126
|
}
|
|
@@ -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
|
+
})();
|