janusweb 1.7.1 → 1.7.3
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/README.md +1 -1
- package/media/assets/webui/apps/comms/chat.html +6 -7
- package/media/assets/webui/apps/comms/comms.css +282 -84
- package/media/assets/webui/apps/comms/comms.html +6 -5
- package/media/assets/webui/apps/comms/comms.js +30 -14
- package/media/assets/webui/apps/comms/userlist.html +10 -17
- package/media/assets/webui/apps/comms/voip.js +111 -1
- package/media/assets/webui/apps/editor/editor-properties.js +34 -2
- package/media/assets/webui/apps/editor/editor.css +27 -0
- package/media/assets/webui/apps/editor/editor.js +326 -49
- package/media/assets/webui/apps/editor/objecticons-small.png +0 -0
- package/media/assets/webui/apps/inventory/inventory.css +71 -0
- package/media/assets/webui/apps/inventory/inventory.html +16 -61
- package/media/assets/webui/apps/inventory/inventory.js +342 -72
- package/media/assets/webui/apps/inventory/inventory.json +1 -4
- package/media/assets/webui/apps/inventory/objecticons-small.png +0 -0
- package/media/assets/webui/apps/xrfragment/level2-hyperlink.js +7 -5
- package/media/assets/webui/apps/xrfragment/xrfragment.json +1 -2
- package/package.json +2 -2
- package/scripts/client.js +4 -27
- package/scripts/config.js +7 -2
- package/scripts/janusbase.js +21 -3
- package/scripts/janusweb.js +24 -3
- package/scripts/object.js +67 -7
- package/scripts/room.js +113 -7
- package/media/assets/webui/apps/xrfragment/patch/metaquest-fix-flickering.js +0 -15
|
@@ -1387,7 +1387,9 @@ elation.elements.define('janus-voip-client-callbutton', class extends elation.el
|
|
|
1387
1387
|
this.detached = true;
|
|
1388
1388
|
this.button = elation.elements.create('ui-button', {
|
|
1389
1389
|
append: this,
|
|
1390
|
-
|
|
1390
|
+
// buttonlabel lets a host (e.g. the invite menu) relabel the action;
|
|
1391
|
+
// set as an attribute so it survives the deferred create() lifecycle.
|
|
1392
|
+
label: this.getAttribute('buttonlabel') || '📞',
|
|
1391
1393
|
});
|
|
1392
1394
|
this.button.addEventListener('click', ev => this.handleClick(ev));
|
|
1393
1395
|
super.create();
|
|
@@ -1416,6 +1418,114 @@ super.create();
|
|
|
1416
1418
|
}
|
|
1417
1419
|
}
|
|
1418
1420
|
});
|
|
1421
|
+
/**
|
|
1422
|
+
* The invite hub in the comms bar. A popup button that gathers the ways to
|
|
1423
|
+
* bring people together: invite people already here into voice, copy a link
|
|
1424
|
+
* that pulls in someone who isn't here, or schedule a meetup at a set time.
|
|
1425
|
+
* Replaces the bare 📞 call button, which it reuses internally for the voice
|
|
1426
|
+
* path so the existing "ring" chat-signaling flow is unchanged.
|
|
1427
|
+
*/
|
|
1428
|
+
elation.elements.define('janus-comms-invite', class extends elation.elements.ui.popupbutton {
|
|
1429
|
+
create() {
|
|
1430
|
+
this.label = '➕ Invite';
|
|
1431
|
+
this.title = 'Invite people / meet up';
|
|
1432
|
+
super.create();
|
|
1433
|
+
this.popupcontent = this.buildMenu();
|
|
1434
|
+
}
|
|
1435
|
+
createPopup() {
|
|
1436
|
+
super.createPopup();
|
|
1437
|
+
// Class hook for the opaque, high-z popup styling in comms.css. Without an
|
|
1438
|
+
// opaque backdrop the popup (rendered at document root) lets the scene and
|
|
1439
|
+
// the chat transcript bleed through. Repositioning as the async rows lay
|
|
1440
|
+
// out is handled by ui.popupbutton's ResizeObserver.
|
|
1441
|
+
if (this.popup) this.popup.classList.add('janus-comms-invite-popup');
|
|
1442
|
+
}
|
|
1443
|
+
buildMenu() {
|
|
1444
|
+
let menu = elation.elements.create('ui-content');
|
|
1445
|
+
menu.classList.add('janus-comms-invite-menu');
|
|
1446
|
+
|
|
1447
|
+
// Invite people already in the room into voice chat. Reuses the existing
|
|
1448
|
+
// call button element, so the mic picker + "ring ring" chat signaling and
|
|
1449
|
+
// the accept/decline card all keep working exactly as before.
|
|
1450
|
+
this.callbutton = elation.elements.create('janus-voip-client-callbutton', {
|
|
1451
|
+
append: menu,
|
|
1452
|
+
buttonlabel: '🎙 Invite to voice',
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
// Copy a link that brings someone who isn't here into this room.
|
|
1456
|
+
this.copybutton = elation.elements.create('ui-button', { append: menu, label: '🔗 Copy room link' });
|
|
1457
|
+
this.copybutton.addEventListener('click', (ev) => this.handleCopyLink(ev));
|
|
1458
|
+
|
|
1459
|
+
// "Schedule a meetup" is hidden for now: without a backend that persists the
|
|
1460
|
+
// invite and emails/notifies the invitee it can't actually deliver anything.
|
|
1461
|
+
// The seam (handleSchedule + buildRoomLink(time) + comms-schedule-request)
|
|
1462
|
+
// stays below so it can be re-enabled once that backend lands.
|
|
1463
|
+
// this.appendSchedule(menu);
|
|
1464
|
+
|
|
1465
|
+
return menu;
|
|
1466
|
+
}
|
|
1467
|
+
appendSchedule(menu) {
|
|
1468
|
+
let schedule = document.createElement('div');
|
|
1469
|
+
schedule.className = 'janus-comms-schedule';
|
|
1470
|
+
let label = document.createElement('label');
|
|
1471
|
+
label.innerHTML = '📅 Schedule a meetup';
|
|
1472
|
+
this.timeinput = document.createElement('input');
|
|
1473
|
+
this.timeinput.type = 'datetime-local';
|
|
1474
|
+
this.schedulebutton = elation.elements.create('ui-button', { label: 'Copy invite link' });
|
|
1475
|
+
this.schedulebutton.addEventListener('click', (ev) => this.handleSchedule(ev));
|
|
1476
|
+
schedule.appendChild(label);
|
|
1477
|
+
schedule.appendChild(this.timeinput);
|
|
1478
|
+
schedule.appendChild(this.schedulebutton);
|
|
1479
|
+
menu.appendChild(schedule);
|
|
1480
|
+
}
|
|
1481
|
+
buildRoomLink(opts) {
|
|
1482
|
+
opts = opts || {};
|
|
1483
|
+
let base = (typeof janus != 'undefined' && janus.currentroom && janus.currentroom.url)
|
|
1484
|
+
|| location.href.replace(/#.*/, '');
|
|
1485
|
+
let url = new URL(base, location.href);
|
|
1486
|
+
if (opts.time) url.searchParams.set('t', Math.floor(opts.time / 1000));
|
|
1487
|
+
// Position anchoring is object-based: a #<js_id> fragment teleports the
|
|
1488
|
+
// arriving player to a named room object. Only meaningful when one exists.
|
|
1489
|
+
if (opts.anchor) url.hash = opts.anchor;
|
|
1490
|
+
return url.toString();
|
|
1491
|
+
}
|
|
1492
|
+
copyToClipboard(text) {
|
|
1493
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1494
|
+
return navigator.clipboard.writeText(text).catch(() => this.copyFallback(text));
|
|
1495
|
+
}
|
|
1496
|
+
this.copyFallback(text);
|
|
1497
|
+
return Promise.resolve();
|
|
1498
|
+
}
|
|
1499
|
+
copyFallback(text) {
|
|
1500
|
+
let ta = document.createElement('textarea');
|
|
1501
|
+
ta.value = text;
|
|
1502
|
+
ta.style.position = 'fixed';
|
|
1503
|
+
ta.style.opacity = '0';
|
|
1504
|
+
document.body.appendChild(ta);
|
|
1505
|
+
ta.select();
|
|
1506
|
+
try { document.execCommand('copy'); } catch (e) {}
|
|
1507
|
+
document.body.removeChild(ta);
|
|
1508
|
+
}
|
|
1509
|
+
flashLabel(button, text, revert) {
|
|
1510
|
+
button.setLabel(text);
|
|
1511
|
+
setTimeout(() => button.setLabel(revert), 1500);
|
|
1512
|
+
}
|
|
1513
|
+
handleCopyLink(ev) {
|
|
1514
|
+
let link = this.buildRoomLink();
|
|
1515
|
+
this.copyToClipboard(link);
|
|
1516
|
+
this.flashLabel(this.copybutton, '✓ Link copied', '🔗 Copy room link');
|
|
1517
|
+
}
|
|
1518
|
+
handleSchedule(ev) {
|
|
1519
|
+
let val = this.timeinput.value;
|
|
1520
|
+
let time = val ? new Date(val).getTime() : null;
|
|
1521
|
+
let link = this.buildRoomLink({ time: time });
|
|
1522
|
+
this.copyToClipboard(link);
|
|
1523
|
+
this.flashLabel(this.schedulebutton, '✓ Invite copied', 'Copy invite link');
|
|
1524
|
+
document.dispatchEvent(new CustomEvent('comms-schedule-request', {
|
|
1525
|
+
detail: { time: time, url: link },
|
|
1526
|
+
}));
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1419
1529
|
elation.elements.define('janus-voip-client-incomingcall', class extends elation.elements.base {
|
|
1420
1530
|
create() {
|
|
1421
1531
|
this.actions = elation.elements.create('ui-flexpanel', { append: this });
|
|
@@ -75,10 +75,41 @@ elation.elements.define('janus-ui-editor-property', class extends elation.elemen
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
if (this.label) {
|
|
78
|
-
elation.elements.create('ui-text', {
|
|
78
|
+
let labelobj = elation.elements.create('ui-text', {
|
|
79
79
|
text: this.label,
|
|
80
80
|
append: this
|
|
81
81
|
});
|
|
82
|
+
// Clicking the label activates the editor's control, like a standard <label>.
|
|
83
|
+
elation.events.add(labelobj, 'click', (ev) => this.activateControl(ev));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
mousedown(ev) {
|
|
87
|
+
// Always (re)fire selection on click, even when this property is already the
|
|
88
|
+
// selected one — the base ui.item suppresses re-selection, which made
|
|
89
|
+
// re-clicking a property (to re-assert its edit mode) a no-op.
|
|
90
|
+
if (this.selectable) {
|
|
91
|
+
this.select(ev);
|
|
92
|
+
ev.stopPropagation();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
activateControl(ev) {
|
|
96
|
+
// Mirror a native <label>: clicking the label activates the editor's control
|
|
97
|
+
// (the first non-label child; the label is a ui-text we create). Prefer the
|
|
98
|
+
// element's own focus() — ui-input.focus() focuses its field, ui-toggle.focus()
|
|
99
|
+
// flips it — and fall back to forwarding the click for controls without focus().
|
|
100
|
+
let control = null;
|
|
101
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
102
|
+
let child = this.children[i];
|
|
103
|
+
if (child.tagName && child.tagName.toLowerCase() != 'ui-text') { control = child; break; }
|
|
104
|
+
}
|
|
105
|
+
if (!control) return;
|
|
106
|
+
if (typeof control.focus == 'function') {
|
|
107
|
+
control.focus();
|
|
108
|
+
} else {
|
|
109
|
+
control.dispatchEvent(new MouseEvent(ev.type, {
|
|
110
|
+
bubbles: true, cancelable: true, view: window,
|
|
111
|
+
clientX: ev.clientX, clientY: ev.clientY, button: ev.button
|
|
112
|
+
}));
|
|
82
113
|
}
|
|
83
114
|
}
|
|
84
115
|
updateValue(value) {
|
|
@@ -381,10 +412,11 @@ elation.elements.define('janus-ui-editor-property-boolean', class extends elatio
|
|
|
381
412
|
value: { type: 'boolean' },
|
|
382
413
|
});
|
|
383
414
|
if (this.label) {
|
|
384
|
-
elation.elements.create('ui-text', {
|
|
415
|
+
let labelobj = elation.elements.create('ui-text', {
|
|
385
416
|
text: this.label,
|
|
386
417
|
append: this
|
|
387
418
|
});
|
|
419
|
+
elation.events.add(labelobj, 'click', (ev) => this.activateControl(ev));
|
|
388
420
|
}
|
|
389
421
|
this.elements = elation.elements.fromString(`
|
|
390
422
|
<ui-toggle name="toggle"></ui-toggle>
|
|
@@ -169,3 +169,30 @@ janus-ui-editor-source ui-button[name="refresh"] {
|
|
|
169
169
|
top: 0;
|
|
170
170
|
right: 0;
|
|
171
171
|
}
|
|
172
|
+
|
|
173
|
+
/* Scene-tree type icons (Blender-style): ui.treeviewitem renders an
|
|
174
|
+
<span class="st-typeicon st-type-<tag>"> in each row's label (via attrs.icon),
|
|
175
|
+
drawn from the engine object-icon sprite (20px cells). All primitives share the
|
|
176
|
+
'object' icon; unknown tags (custom elements) fall back to the generic icon.
|
|
177
|
+
Negative top/bottom margins keep the 20px glyph from inflating the row, and
|
|
178
|
+
vertical-align centres it with the label text. */
|
|
179
|
+
.st-typeicon {
|
|
180
|
+
display: inline-block;
|
|
181
|
+
width: 21px;
|
|
182
|
+
height: 21px;
|
|
183
|
+
margin: -5px 2px -5px 0;
|
|
184
|
+
vertical-align: middle;
|
|
185
|
+
background-image: url(./objecticons-small.png);
|
|
186
|
+
background-repeat: no-repeat;
|
|
187
|
+
background-position: -21px -40px; /* default / custom element: generic geometry */
|
|
188
|
+
opacity: .85;
|
|
189
|
+
}
|
|
190
|
+
.st-type-object { background-position: -21px -20px; } /* cube */
|
|
191
|
+
.st-type-light { background-position: -251px -20px; } /* point light */
|
|
192
|
+
.st-type-text,
|
|
193
|
+
.st-type-paragraph { background-position: -168px -40px; } /* text */
|
|
194
|
+
.st-type-image { background-position: -105px -20px; } /* grid/plane */
|
|
195
|
+
.st-type-video { background-position: -126px -40px; } /* camera */
|
|
196
|
+
.st-type-sound { background-position: -210px -40px; } /* speaker */
|
|
197
|
+
.st-type-link { background-position: -189px -60px; } /* portal */
|
|
198
|
+
.st-type-particle { background-position: -84px -40px; } /* particles */
|