north-star / commit
adding hideable participant controls, added north as a participant,
readme.md | 2 ++ src/app.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/index.html | 2 ++ src/style.css | 49 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 95 insertions(+), 13 deletions(-) diff --git a/readme.md b/readme.md index 09027f0..bc7696d 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,8 @@ ## Links +Repo: https://gitlab.com/PavelSafronov/north-star + GitLab: https://pavelsafronov.gitlab.io/north-star/#room=7c49d0a8 Local: http://127.0.0.1:5500/src/index.html#room=7c49d0a8 diff --git a/src/app.js b/src/app.js index 3ec084f..5952b78 100644 --- a/src/app.js +++ b/src/app.js @@ -16,6 +16,7 @@ const joinRoomLink = document.getElementById('join-room-link'); const inviteRoomStatus = document.getElementById('invite-room-status'); const createRoomLink = document.getElementById('create-room-link'); const participantsPanel = document.getElementById('participants-panel'); +const participantsToggle = document.getElementById('participants-toggle'); const logDebug = (message) => { debugParagraph.innerText = message; } @@ -209,6 +210,7 @@ if (joinRoomLink) { const NAME_STORAGE_KEY = 'north-star-self-name'; const COLOR_STORAGE_KEY = 'north-star-self-color'; const PARTICIPANT_VISIBILITY_STORAGE_KEY = 'north-star-participant-visibility'; +const PARTICIPANTS_PANEL_VISIBLE_STORAGE_KEY = 'north-star-participants-panel-visible'; const normalizeName = (value) => (value || '').trim().slice(0, 32); const normalizeColor = (value) => { const candidate = (value || '').trim().toLowerCase(); @@ -257,6 +259,37 @@ const participantVisibility = (() => { } })(); +const getInitialParticipantsPanelVisible = () => { + const saved = localStorage.getItem(PARTICIPANTS_PANEL_VISIBLE_STORAGE_KEY); + if (saved === 'true' || saved === 'false') { + return saved === 'true'; + } + + return !window.matchMedia('(max-width: 720px)').matches; +}; + +let participantsPanelVisible = getInitialParticipantsPanelVisible(); +const applyParticipantsPanelVisibility = () => { + if (participantsPanel) { + participantsPanel.classList.toggle('is-hidden', !participantsPanelVisible); + } + if (participantsToggle) { + participantsToggle.textContent = participantsPanelVisible ? 'Hide Participants' : 'Show Participants'; + participantsToggle.setAttribute('aria-expanded', String(participantsPanelVisible)); + } + + localStorage.setItem(PARTICIPANTS_PANEL_VISIBLE_STORAGE_KEY, String(participantsPanelVisible)); +}; + +if (participantsToggle) { + participantsToggle.addEventListener('click', () => { + participantsPanelVisible = !participantsPanelVisible; + applyParticipantsPanelVisibility(); + }); +} + +applyParticipantsPanelVisibility(); + const isParticipantVisible = (participantId) => participantVisibility[participantId] !== false; const setParticipantVisibility = (participantId, visible) => { participantVisibility[participantId] = visible; @@ -473,11 +506,13 @@ const renderParticipantControls = () => { mapLink.href = `https://www.google.com/maps?q=${participant.position.lat},${participant.position.lon}`; mapLink.target = '_blank'; mapLink.rel = 'noopener noreferrer'; - mapLink.textContent = 'Map'; + mapLink.textContent = '📍'; + mapLink.title = `Open ${participant.name} in Google Maps`; + mapLink.setAttribute('aria-label', `Open ${participant.name} in Google Maps`); row.appendChild(checkbox); + nameWrapper.appendChild(mapLink); row.appendChild(nameWrapper); - row.appendChild(mapLink); participantsPanel.appendChild(row); } }; @@ -485,6 +520,20 @@ const renderParticipantControls = () => { const getParticipantsToRender = () => { const participantsToRender = participants.filter(p => p.id !== selfParticipant.id); + // Add a synthetic North marker relative to the current self position for heading validation. + if (participantsToRender.findIndex(p => p.id === 'north-marker') === -1) { + participantsToRender.push({ + id: 'north-marker', + name: 'North', + color: '#ffffff', + position: { + lat: selfPosition.lat + 0.001, + lon: selfPosition.lon + }, + timestamp: Date.now() + }); + } + // Add Space Needle and Cascadia as a test participant if there are no other participants (for testing purposes, local only) if (participantsToRender.findIndex(p => p.id === 'space-needle') === -1) { participantsToRender.push({ @@ -598,7 +647,7 @@ const render = () => { document.body.style.backgroundColor = selfParticipant.color; // output debug info - logDebug(`${selfParticipant.name} | Self: ${selfParticipant.position.lat.toFixed(5)}, ${selfParticipant.position.lon.toFixed(5)} | Heading: ${selfHeading.toFixed(2)}° | Participants: ${participants.length}`); + logDebug(`Name: ${selfParticipant.name} | Position: ${selfParticipant.position.lat.toFixed(1)}, ${selfParticipant.position.lon.toFixed(1)} | Heading: ${selfHeading.toFixed(2)}° | Real participants: ${participants.length}`); }; await syncSelfParticipant(); diff --git a/src/index.html b/src/index.html index 91ed3ae..e7b3fe2 100644 --- a/src/index.html +++ b/src/index.html @@ -25,6 +25,8 @@ </form> <div id="default-arrow" style="display: none;" class="arrow">⇡</div> <div id="content"> + <button id="participants-toggle" class="participants-toggle" type="button" aria-expanded="true">Hide + Participants</button> <aside id="participants-panel" class="participants-panel" aria-label="Participants"></aside> </div> <div> diff --git a/src/style.css b/src/style.css index e4ba1ae..f8a3c8a 100644 --- a/src/style.css +++ b/src/style.css @@ -158,6 +158,20 @@ a { z-index: 1; } +.participants-toggle { + position: absolute; + right: 10px; + top: 8px; + padding: 6px 10px; + border: 0; + border-radius: 8px; + background: rgba(15, 23, 42, 0.75); + color: #f8fafc; + font-size: 12px; + font-weight: 700; + z-index: 4; +} + .participants-panel { position: absolute; right: 10px; @@ -173,6 +187,10 @@ a { z-index: 3; } +.participants-panel.is-hidden { + display: none; +} + .participants-panel h3 { margin: 0 0 6px; font-size: 14px; @@ -181,7 +199,7 @@ a { .participant-row { display: grid; - grid-template-columns: auto 1fr auto; + grid-template-columns: auto 1fr; gap: 8px; align-items: center; margin-bottom: 6px; @@ -189,8 +207,12 @@ a { } .participant-name-badge { - display: inline-block; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; width: 100%; + min-width: 0; padding: 5px 8px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.35); @@ -199,13 +221,17 @@ a { } .participant-name { + flex: 1 1 auto; + min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .participant-map-link { - font-size: 12px; + font-size: 17px; + line-height: 1; + text-decoration: none; } .arrow { @@ -265,18 +291,21 @@ a { } @media (max-width: 720px) { + .participants-toggle { + right: 6px; + top: 6px; + } + .participants-panel { - width: min(52vw, 220px); + width: min(88vw, 320px); right: 6px; + top: 38px; + transform: none; + max-height: calc(100% - 44px); padding: 6px; } .participant-row { - grid-template-columns: auto 1fr; - } - - .participant-map-link { - grid-column: 2; - justify-self: start; + grid-template-columns: auto minmax(0, 1fr); } } \ No newline at end of file