fuzzyworld

north-star / commit

show multiple qr codes when running locally

6cb482fe1d89d586e3ba0c5765ef667b82ee1778 · Pavel Safronov · 2026-03-15 10:14:43 -0700 · parents 1a128926d69a

show multiple qr codes when running locally
 src/app.js     | 61 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 src/index.html |  5 +----
 src/style.css  | 14 ++++++++++++++
 3 files changed, 65 insertions(+), 15 deletions(-)

diff --git a/src/app.js b/src/app.js
index caeef30..ae679f0 100644
--- a/src/app.js
+++ b/src/app.js
@@ -12,8 +12,7 @@ const nameForm = document.getElementById('name-form');
 const selfNameInput = document.getElementById('self-name-input');
 const selfColorInput = document.getElementById('self-color-input');
 const roomCurrent = document.getElementById('room-current');
-const inviteRoomQr = document.getElementById('invite-room-qr');
-const inviteRoomQrStatus = document.getElementById('invite-room-qr-status');
+const inviteRoomQrList = document.getElementById('invite-room-qr-list');
 const calibrateHeadingLink = document.getElementById('calibrate-heading-link');
 const createRoomLink = document.getElementById('create-room-link');
 const participantsPanel = document.getElementById('participants-panel');
@@ -151,6 +150,7 @@ const uid = authSignInResponse.user.uid;
 console.debug('User ID:', uid);
 
 const BASE_ROOM_URL = `${window.location.origin}${window.location.pathname}`;
+const GITLAB_PAGES_BASE_URL = 'https://pavelsafronov.gitlab.io/north-star/';
 const ROOM_ID_LENGTH = 8;
 const ROOM_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
 
@@ -205,9 +205,32 @@ if (createRoomLink) {
     createRoomLink.href = BASE_ROOM_URL;
 }
 
-if (inviteRoomQr) {
+const renderInviteQrCard = async ({ title, url, statusText }) => {
+    if (!inviteRoomQrList) return;
+
+    const card = document.createElement('div');
+    card.className = 'room-qr-card';
+    card.setAttribute('aria-label', `${title} invite QR code`);
+
+    const label = document.createElement('p');
+    label.className = 'room-qr-title';
+    label.textContent = title;
+    card.appendChild(label);
+
+    const qrImage = document.createElement('img');
+    qrImage.className = 'room-qr';
+    qrImage.alt = `${title}: scan to join room ${roomId}`;
+    card.appendChild(qrImage);
+
+    const status = document.createElement('p');
+    status.className = 'room-link-status';
+    status.textContent = statusText;
+    card.appendChild(status);
+
+    inviteRoomQrList.appendChild(card);
+
     try {
-        const qrDataUrl = await QRCode.toDataURL(inviteRoomUrl, {
+        const qrDataUrl = await QRCode.toDataURL(url, {
             width: 400,
             margin: 4,
             errorCorrectionLevel: 'H',
@@ -216,15 +239,31 @@ if (inviteRoomQr) {
                 light: '#ffffff'
             }
         });
-        inviteRoomQr.src = qrDataUrl;
-        inviteRoomQr.alt = `Scan to join room ${roomId}`;
-        if (inviteRoomQrStatus) inviteRoomQrStatus.textContent = 'Scan to join this room';
+        qrImage.src = qrDataUrl;
     } catch (err) {
         console.error('Failed to generate invite QR code', err);
-        if (inviteRoomQrStatus) {
-            inviteRoomQrStatus.textContent = 'Failed to create QR';
-            inviteRoomQrStatus.classList.add('error');
-        }
+        status.textContent = 'Failed to create QR';
+        status.classList.add('error');
+    }
+};
+
+if (inviteRoomQrList) {
+    inviteRoomQrList.textContent = '';
+
+    const roomHash = buildRoomHash(roomId, roomKeyBase64);
+    await renderInviteQrCard({
+        title: 'This Device URL',
+        url: inviteRoomUrl,
+        statusText: 'Scan to join this room'
+    });
+
+    const isLocalRun = isLocalCommit || ['localhost', '127.0.0.1', '::1'].includes(location.hostname);
+    if (isLocalRun) {
+        await renderInviteQrCard({
+            title: 'GitLab Pages URL',
+            url: `${GITLAB_PAGES_BASE_URL}#${roomHash}`,
+            statusText: 'Scan to join on hosted app'
+        });
     }
 }
 
diff --git a/src/index.html b/src/index.html
index 79fdf06..23f8956 100644
--- a/src/index.html
+++ b/src/index.html
@@ -45,10 +45,7 @@
         <aside id="participants-panel" class="participants-panel" aria-label="Participants"></aside>
     </div>
     <div class="room-links">
-        <div class="room-qr-card" aria-label="Room invite QR code">
-            <img id="invite-room-qr" class="room-qr" alt="Room invite QR code" />
-            <p id="invite-room-qr-status" class="room-link-status"></p>
-        </div>
+        <div id="invite-room-qr-list" class="room-qr-list" aria-label="Room invite QR codes"></div>
     </div>
     <div>
         <p id="debug" style="display: none;"></p>
diff --git a/src/style.css b/src/style.css
index 27ffb4f..e9bd433 100644
--- a/src/style.css
+++ b/src/style.css
@@ -87,6 +87,13 @@ a {
     text-align: center;
 }
 
+.room-qr-list {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    gap: 10px;
+}
+
 .room-links>a {
     text-decoration: underline;
     text-underline-offset: 0.15em;
@@ -104,6 +111,13 @@ a {
     box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
 }
 
+.room-qr-title {
+    margin: 0;
+    font-size: 0.72em;
+    font-weight: 700;
+    color: #334155;
+}
+
 .room-qr {
     width: 200px;
     height: 200px;