openvibers/openvibe.connect Commits

A share's page sees its computer connect; the picker scene shows the choice being made (#5)

OP OpenVibers committed 11 days ago · 44ac501113 · 4 files, +43 −5

public/js/connect.js asks the share's API every 4 s while the page is visible (ten minutes at most) and flips the
state line to Connected or Not connected, so someone running the command the first time sees it connect without
reloading (role=status, aria-live polite). The home's "You pick who gets in" scene is marked for openvibe-shared
4.106.0's ghost cursor: it walks the three choices, and only its choice looks chosen while it does.

Changes

ddiff --git a/public/css/connect.css b/public/css/connect.css
iindex 67a0ee2..430119b 100644
---- a/public/css/connect.css
++++ b/public/css/connect.css
@@@ -43,8 +43,12 @@
.cn-pick { width: min(100%, 340px); margin: 0 auto; display: grid; gap: 8px; }
.cn-pick-row { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--ovk-line); background: var(--ovk-panel); font-size: 14px; color: var(--ovk-muted); }
.cn-pick-row i { width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--ovk-line); flex: none; }
--.cn-pick-row.is-on { color: var(--ovk-text); border-color: color-mix(in srgb, var(--room-accent) 55%, var(--ovk-line)); }
--.cn-pick-row.is-on i { border-color: var(--room-accent); background: radial-gradient(circle, var(--room-accent) 0 4px, transparent 5px); }
++.cn-pick-row.is-on, .cn-pick-row[data-ov-ghost-on] { color: var(--ovk-text); border-color: color-mix(in srgb, var(--room-accent) 55%, var(--ovk-line)); }
++.cn-pick-row.is-on i, .cn-pick-row[data-ov-ghost-on] i { border-color: var(--room-accent); background: radial-gradient(circle, var(--room-accent) 0 4px, transparent 5px); }
++/* While the ghost cursor is choosing, only its choice looks chosen. */
++.cn-pick:has([data-ov-ghost-on]) .cn-pick-row.is-on:not([data-ov-ghost-on]) { color: var(--ovk-muted); border-color: var(--ovk-line); }
++.cn-pick:has([data-ov-ghost-on]) .cn-pick-row.is-on:not([data-ov-ghost-on]) i { border-color: var(--ovk-line); background: none; }
++.cn-pick-row { transition: border-color .2s ease, color .2s ease; }
.cn-pick-names { display: flex; gap: 6px; padding-left: 40px; }
.cn-pick-names b { padding: 3px 10px; border-radius: 999px; font-size: 12.5px; font-weight: 600; background: color-mix(in srgb, var(--room-accent) 16%, transparent); color: var(--room-strong); }
ddiff --git a/public/js/connect.js b/public/js/connect.js
nnew file mode 100644
iindex 0000000..cae90e8
---- /dev/null
++++ b/public/js/connect.js
@@@ -0,0 +1,32 @@
++/* OpenVibe Connect: a share's page notices when its computer connects (or drops), so someone running the command for
++ the first time sees "Connected" without reloading. It asks /api/v1/shares/:id every 4 seconds while the page is
++ visible, for at most ten minutes, and changes only the state line. Without this script the page is complete. */
++(function () {
++ 'use strict';
++ var head = document.querySelector('[data-cn-share]');
++ if (!head || !window.fetch) return;
++ var id = head.getAttribute('data-cn-share');
++ var state = head.querySelector('.cn-state');
++ var line = head.querySelector('[data-cn-line]');
++ if (!state || !line || state.classList.contains('is-closed')) return;
++ var started = Date.now();
++ var last = state.classList.contains('is-on');
++ function show(on) {
++ if (on === last) return;
++ last = on;
++ state.classList.toggle('is-on', on);
++ state.classList.toggle('is-off', !on);
++ state.lastChild.textContent = on ? 'Connected' : 'Not connected';
++ line.textContent = on ? 'Connected just now.' : 'The computer disconnected. Run ov-connect again to bring it back.';
++ }
++ function ask() {
++ if (Date.now() - started > 10 * 60 * 1000) return;
++ if (document.hidden) { setTimeout(ask, 4000); return; }
++ fetch('/api/v1/shares/' + encodeURIComponent(id), { credentials: 'same-origin', headers: { accept: 'application/json' } })
++ .then(function (r) { return r.ok ? r.json() : null; })
++ .then(function (j) { if (j && j.share && j.share.status === 'active') show(!!j.share.connected); })
++ .catch(function () { /* the next ask tries again */ })
++ .then(function () { setTimeout(ask, 4000); });
++ }
++ setTimeout(ask, 4000);
++})();
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 248c458..114c168 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -85,6 +85,7 @@ function layout(o) {
<link rel="stylesheet" href="${esc(ovServe.url('room.css'))}">
<link rel="stylesheet" href="${esc(ovServe.url('showcase.css'))}">
<link rel="stylesheet" href="${asset('css/connect.css')}">
++<script src="${asset('js/connect.js')}" defer></script>
<script src="${esc(ovServe.url('kit-web.js'))}" defer></script>
<script src="${esc(ovServe.url('motion.js'))}" defer></script>
<meta name="ov-boost" content="connect@${esc(RELEASE)}">`,
ddiff --git a/server/views/index.js b/server/views/index.js
iindex 0c28a7b..3ce5b59 100644
---- a/server/views/index.js
++++ b/server/views/index.js
@@@ -52,7 +52,8 @@ function phone() {
}
function picker(mode = 'named') {
-- return `<div class="cn-pick" role="img" aria-label="Choosing who can open a share">${Object.entries(MODE_TEXT).map(([k, v]) => `<span class="cn-pick-row${k === mode ? ' is-on' : ''}"><i></i>${esc(v)}</span>`).join('')}
++ // The ghost cursor (openvibe-shared 4.106.0 motion.js) walks the three choices; the server draws "named" chosen.
++ return `<div class="cn-pick" role="img" aria-label="Choosing who can open a share" data-ov-ghost>${Object.entries(MODE_TEXT).map(([k, v], i) => `<span class="cn-pick-row${k === mode ? ' is-on' : ''}" data-ov-ghost-step="${i + 1}"><i></i>${esc(v)}</span>`).join('')}
<span class="cn-pick-names"><b>@sam</b><b>@riley</b></span></div>`;
}
@@@ -216,9 +217,9 @@ ${modeChoices(share.mode, staff)}
<p class="cn-back"><a href="/">${kit.icon('back')} Your shares</a></p>
${notice ? `<p class="cn-notice" role="status">${esc(notice)}</p>` : ''}
${error ? `<p class="cn-error" role="alert">${esc(error)}</p>` : ''}
--<header class="cn-head"><span class="cn-state ${closed ? 'is-closed' : connected ? 'is-on' : 'is-off'}"><i aria-hidden="true"></i>${esc(state)}</span>
++<header class="cn-head" data-cn-share="${esc(share.id)}"><span class="cn-state ${closed ? 'is-closed' : connected ? 'is-on' : 'is-off'}" role="status" aria-live="polite"><i aria-hidden="true"></i><span>${esc(state)}</span></span>
<h1>${closed ? esc(share.host) : `<a href="${esc(share.url)}">${esc(share.host)}</a>`}</h1>
--<p>${esc(stateText)}</p></header>
++<p data-cn-line>${esc(stateText)}</p></header>
${key ? keyBlock(share, key, baseUrl) : ''}
<dl class="cn-facts">
<div><dt>Who can open it</dt><dd>${esc(MODE_TEXT[share.mode] || share.mode)}${share.mode === 'named' && share.named.length ? `: ${share.named.map((n) => `@${esc(n)}`).join(', ')}` : ''}</dd></div>