voice-poc: multi-share grid + pop-out window per shared screen

This commit is contained in:
mats committed 2026-09-22 07:15:34 +00:00
1 parent ebbf1b07d8
commit a0d5a9a2c7
3 files changed
+87 -7

No files matched your search

+74 -6
View File
@@ -536,14 +536,46 @@ function renderTiles() {
renderStageEmpty();
}
function renderScreens() {
els.screens.innerHTML = '';
// share keys: 'me' or peer id; popped-out shares render in their own window
const poppedWindows = new Map(); // key -> {win, name}
function activeShares() {
const shares = [];
if (S.sharing && S.screenStream) shares.push({ name: `${S.name} (you)`, stream: S.screenStream });
if (S.sharing && S.screenStream) shares.push({ key: 'me', name: `${S.name} (you)`, stream: S.screenStream });
for (const peer of S.peers.values()) {
if (peer.screenStream) shares.push({ name: peer.name, stream: peer.screenStream });
if (peer.screenStream) shares.push({ key: peer.id, name: peer.name, stream: peer.screenStream });
}
for (const share of shares) {
return shares;
}
function renderScreens() {
// prune popouts whose share ended
const live = new Set(activeShares().map((s) => s.key));
for (const [key, p] of [...poppedWindows]) {
if (!live.has(key)) {
try { p.win.close(); } catch {}
poppedWindows.delete(key);
}
}
els.screens.innerHTML = '';
els.screens.classList.toggle('multi', activeShares().length > 1);
for (const share of activeShares()) {
if (poppedWindows.has(share.key)) {
// rendered in its own window — show a small placeholder card to re-embed
const ph = document.createElement('div');
ph.className = 'screen-card popped';
const btn = document.createElement('button');
btn.className = 'pop-btn';
btn.textContent = '⧉ re-embed';
btn.onclick = () => closePopout(share.key);
const label = document.createElement('div');
label.className = 'label';
label.textContent = `${share.name} — popped out`;
ph.append(btn, label);
els.screens.appendChild(ph);
continue;
}
const card = document.createElement('div');
card.className = 'screen-card';
const video = document.createElement('video');
@@ -556,11 +588,47 @@ function renderScreens() {
label.className = 'label';
label.innerHTML = '<span class="live">&#9679;</span>';
label.appendChild(document.createTextNode(share.name));
card.append(video, label);
const pop = document.createElement('button');
pop.className = 'pop-btn';
pop.title = 'Pop out in new window';
pop.textContent = '⧉';
pop.onclick = () => popOutShare(share);
card.append(video, label, pop);
els.screens.appendChild(card);
}
}
function popOutShare(share) {
if (poppedWindows.has(share.key)) return;
const win = window.open('', `share-${share.key}`, 'width=960,height=540');
if (!win) {
toast('Pop-out blocked by the browser — allow pop-ups for this page', 'error');
return;
}
win.document.write(`<!doctype html><title>${share.name} — screen share</title>
<style>html,body{margin:0;height:100%;background:#000;display:grid}video{width:100%;height:100%;object-fit:contain}</style>
<video autoplay playsinline></video>`);
win.document.close();
const v = win.document.querySelector('video');
v.srcObject = share.stream;
v.play().catch(() => {});
poppedWindows.set(share.key, { win, name: share.name });
win.addEventListener('beforeunload', () => {
poppedWindows.delete(share.key);
renderScreens();
});
renderScreens();
}
function closePopout(key) {
const p = poppedWindows.get(key);
if (p) {
poppedWindows.delete(key);
try { p.win.close(); } catch {}
renderScreens();
}
}
// ---------------------------------------------------------------------------
// spatial canvas
// ---------------------------------------------------------------------------
+1 -1
View File
@@ -88,6 +88,6 @@
<div id="toasts"></div>
<script src="app.js?v=5"></script>
<script src="app.js?v=6"></script>
</body>
</html>
+12
View File
@@ -106,6 +106,18 @@ body {
#stage:not(.empty) #stage-empty { display: none; }
#screens { display: flex; gap: 12px; flex-wrap: wrap; }
#screens.multi { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); align-content: start; }
#screens.multi .screen-card { flex: none; }
.screen-card.popped {
min-height: 120px; display: grid; place-items: center;
background: var(--bg-0); box-shadow: none; border: 1px dashed var(--bg-4);
}
.pop-btn {
position: absolute; right: 8px; top: 8px; z-index: 2;
background: rgba(0,0,0,.65); color: var(--text-0); border: none; cursor: pointer;
border-radius: 5px; padding: 3px 8px; font-size: 13px; font-weight: 600;
}
.pop-btn:hover { background: var(--brand); }
.screen-card {
position: relative; background: #000; border-radius: var(--radius); overflow: hidden;
flex: 1 1 480px; max-width: 100%; box-shadow: 0 8px 24px rgba(0,0,0,.4);