north-star / commit
room links and trying to fix heading issue
src/app.js | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/index.html | 5 ++- 2 files changed, 111 insertions(+), 7 deletions(-) diff --git a/src/app.js b/src/app.js index 9abc076..488f6da 100644 --- a/src/app.js +++ b/src/app.js @@ -13,6 +13,7 @@ const selfNameInput = document.getElementById('self-name-input'); const selfColorInput = document.getElementById('self-color-input'); const roomCurrent = document.getElementById('room-current'); const inviteRoomLink = document.getElementById('invite-room-link'); +const joinRoomLink = document.getElementById('join-room-link'); const inviteRoomStatus = document.getElementById('invite-room-status'); const createRoomLink = document.getElementById('create-room-link'); const logDebug = (message) => { @@ -22,6 +23,7 @@ const logDebug = (message) => { const arrow = document.getElementById('default-arrow'); document.head.appendChild(Object.assign(document.createElement("link"), { rel: "icon", href: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>" + arrow.innerText + "</text></svg>" })) const getShortestDelta = (fromAngle, toAngle) => ((toAngle - fromAngle + 540) % 360) - 180; +const normalizeAngle = (angle) => ((angle % 360) + 360) % 360; /** * @typedef {Object} Position @@ -93,6 +95,7 @@ if (roomCurrent) { } const BASE_ROOM_URL = `${window.location.origin}${window.location.pathname}`; +const ROOM_ID_PATTERN = /^[a-f0-9]{8}$/i; if (inviteRoomLink) { inviteRoomLink.href = `${BASE_ROOM_URL}#room=${encodeURIComponent(roomId)}`; } @@ -139,6 +142,30 @@ const copyTextToClipboard = async (text) => { } }; +const parseRoomIdFromInput = (value) => { + const input = (value || '').trim(); + if (!input) return null; + + if (ROOM_ID_PATTERN.test(input)) { + return input.toLowerCase(); + } + + try { + const parsedUrl = new URL(input, window.location.href); + const hashValue = parsedUrl.hash.startsWith('#') ? parsedUrl.hash.slice(1) : parsedUrl.hash; + const hashParams = new URLSearchParams(hashValue); + const fromHash = (hashParams.get('room') || '').trim(); + + if (ROOM_ID_PATTERN.test(fromHash)) { + return fromHash.toLowerCase(); + } + } catch (err) { + return null; + } + + return null; +}; + if (inviteRoomLink) { inviteRoomLink.addEventListener('click', async (event) => { event.preventDefault(); @@ -153,6 +180,30 @@ if (inviteRoomLink) { }); } +if (joinRoomLink) { + joinRoomLink.addEventListener('click', (event) => { + event.preventDefault(); + + const roomInput = window.prompt('Enter room ID or room URL'); + if (roomInput === null) { + return; + } + + const parsedRoomId = parseRoomIdFromInput(roomInput); + if (!parsedRoomId) { + alert('Invalid room ID or URL. Please enter an 8-character hexadecimal room ID or a URL containing it.'); + return; + } + + if (parsedRoomId === roomId.toLowerCase()) { + alert('You are already in this room!'); + return; + } + + window.location.assign(`${BASE_ROOM_URL}#room=${encodeURIComponent(parsedRoomId)}`); + }); +} + const NAME_STORAGE_KEY = 'north-star-self-name'; const COLOR_STORAGE_KEY = 'north-star-self-color'; const normalizeName = (value) => (value || '').trim().slice(0, 32); @@ -272,10 +323,62 @@ navigator.geolocation.watchPosition(async (pos) => { console.error('Error getting geolocation', err); }); window.addEventListener('deviceorientationabsolute', (event) => { - if (event.absolute && event.alpha !== null) { - selfHeading = event.alpha; - render(); - } + // Some browsers expose absolute compass heading as webkitCompassHeading. + // Others expose alpha that needs conversion and screen-orientation compensation. + const getScreenOrientationAngle = () => { + if (window.screen?.orientation && typeof window.screen.orientation.angle === 'number') { + return window.screen.orientation.angle; + } + if (typeof window.orientation === 'number') { + return window.orientation; + } + return 0; + }; + + const getHeadingFromEvent = (orientationEvent) => { + if (typeof orientationEvent.webkitCompassHeading === 'number') { + return normalizeAngle(orientationEvent.webkitCompassHeading); + } + if (orientationEvent.alpha === null) { + return null; + } + // Convert alpha to compass-style heading (clockwise from North). + return normalizeAngle(360 - orientationEvent.alpha); + }; + + const heading = getHeadingFromEvent(event); + if (heading === null) return; + + selfHeading = normalizeAngle(heading + getScreenOrientationAngle()); + render(); +}); + +window.addEventListener('deviceorientation', (event) => { + const getScreenOrientationAngle = () => { + if (window.screen?.orientation && typeof window.screen.orientation.angle === 'number') { + return window.screen.orientation.angle; + } + if (typeof window.orientation === 'number') { + return window.orientation; + } + return 0; + }; + + const getHeadingFromEvent = (orientationEvent) => { + if (typeof orientationEvent.webkitCompassHeading === 'number') { + return normalizeAngle(orientationEvent.webkitCompassHeading); + } + if (orientationEvent.alpha === null) { + return null; + } + return normalizeAngle(360 - orientationEvent.alpha); + }; + + const heading = getHeadingFromEvent(event); + if (heading === null) return; + + selfHeading = normalizeAngle(heading + getScreenOrientationAngle()); + render(); }); const participantsRef = ref(db, `rooms/${roomId}/participants`); @@ -372,7 +475,7 @@ const render = () => { const rawAngle = Math.atan2(dy, dx) * (180 / Math.PI) + 90; // Consider the heading of the device to rotate the arrow accordingly - const targetAngle = (rawAngle - selfHeading + 360) % 360; + const targetAngle = normalizeAngle(rawAngle - selfHeading); let currentArrowAngle = existingArrow.currentAngle; if (currentArrowAngle === null) { diff --git a/src/index.html b/src/index.html index b5bee25..8b8a0bf 100644 --- a/src/index.html +++ b/src/index.html @@ -14,8 +14,9 @@ <p id="room-current" class="room-current"></p> <p class="room-links"> <a id="invite-room-link" href="#">Invite to Room</a> - <span id="invite-room-status" class="room-link-status" aria-live="polite"></span> - + <span aria-hidden="true">·</span> + <a id="join-room-link" href="#">Join Room</a> + <span aria-hidden="true">·</span> <a id="create-room-link" href="https://pavelsafronov.gitlab.io/north-star/">Create New Room</a> </p> <form id="name-form" class="name-form" autocomplete="off">