north-star / commit
some refactoring
src/app.ts | 2 -- src/friends-panel.ts | 44 ++++++++++++++++++++++--- src/public-settings.ts | 4 +++ src/renderer.ts | 88 +------------------------------------------------- 4 files changed, 44 insertions(+), 94 deletions(-) diff --git a/src/app.ts b/src/app.ts index 5a072f6..2ff9dde 100644 --- a/src/app.ts +++ b/src/app.ts @@ -46,7 +46,6 @@ const FRIEND_RECENTLY_OFFLINE_MS = (() => { })(); const HEADING_CORRECTION_STORAGE_KEY = 'friend-compass-heading-correction-deg'; const LAST_ROOM_HASH_STORAGE_KEY = 'friend-compass-last-room-hash'; -const NORTH_MARKER_ID = 'north-marker'; let headingCorrectionDeg = (() => { const parsed = Number(localStorage.getItem(HEADING_CORRECTION_STORAGE_KEY)); @@ -240,7 +239,6 @@ const friendsController = createFriendsPanelController({ friendsToggle, requestRender, getReadableTextColor, - northMarkerId: NORTH_MARKER_ID, selfFriend, selfPosition, presenceThresholds: { diff --git a/src/friends-panel.ts b/src/friends-panel.ts index a05aa0e..0f72d66 100644 --- a/src/friends-panel.ts +++ b/src/friends-panel.ts @@ -1,12 +1,12 @@ import { getFriendPresenceState, PRESENCE_EMOJI } from './utils'; import type { PresenceThresholds } from './utils'; +import { PUBLIC_SETTINGS } from './public-settings'; export const createFriendsPanelController = ({ friendsPanel, friendsToggle, requestRender, getReadableTextColor, - northMarkerId, selfFriend, selfPosition, presenceThresholds @@ -15,11 +15,16 @@ export const createFriendsPanelController = ({ friendsToggle: HTMLElement | null; requestRender: () => void; getReadableTextColor: (color: string) => string; - northMarkerId: string; selfFriend: any; selfPosition: { lat: number; lon: number }; presenceThresholds: PresenceThresholds; }) => { + const guardianAngelEnabled = PUBLIC_SETTINGS.features.guardianAngelEnabled; + const northMarkerId = 'north-marker'; + const guardianAngelId = 'guardian-angel'; + const GUARDIAN_ORBIT_RADIUS_METERS = 75; + const GUARDIAN_ORBIT_PERIOD_MS = 90 * 1000; + const PARTICIPANT_VISIBILITY_STORAGE_KEY = 'friend-compass-friend-visibility'; const PARTICIPANTS_PANEL_VISIBLE_STORAGE_KEY = 'friend-compass-friends-panel-visible'; const PARTICIPANT_ORDER_STORAGE_KEY = 'friend-compass-friend-order'; @@ -49,11 +54,27 @@ export const createFriendsPanelController = ({ const getFriendDefaultRank = (friendId) => { if (friendId === northMarkerId) return 0; - return 1; + if (guardianAngelEnabled && friendId === guardianAngelId) return 1; + return 2; }; const shouldShowMapLink = (friendId) => { - return friendId !== northMarkerId; + return friendId !== northMarkerId && (!guardianAngelEnabled || friendId !== guardianAngelId); + }; + + const getGuardianAngelPosition = (nowMs: number) => { + const latRad = selfPosition.lat * (Math.PI / 180); + const metersPerDegLat = 111320; + const metersPerDegLon = Math.max(111320 * Math.cos(latRad), 1); + const theta = (nowMs / GUARDIAN_ORBIT_PERIOD_MS) * (2 * Math.PI); + + const latOffsetDeg = (GUARDIAN_ORBIT_RADIUS_METERS * Math.cos(theta)) / metersPerDegLat; + const lonOffsetDeg = (GUARDIAN_ORBIT_RADIUS_METERS * Math.sin(theta)) / metersPerDegLon; + + return { + lat: selfPosition.lat + latOffsetDeg, + lon: selfPosition.lon + lonOffsetDeg, + }; }; const ensureFriendOrder = (friendsToRender) => { @@ -272,6 +293,17 @@ export const createFriendsPanelController = ({ }); } + if (guardianAngelEnabled && friendsToRender.findIndex((p) => p.id === guardianAngelId) === -1) { + const guardianPosition = getGuardianAngelPosition(Date.now()); + friendsToRender.push({ + id: guardianAngelId, + name: 'Guardian Angel', + color: '#f59e0b', + position: guardianPosition, + timestamp: Date.now() + }); + } + return sortFriendsForDisplay(friendsToRender); }; @@ -304,8 +336,10 @@ export const createFriendsPanelController = ({ nameWrapper.style.color = getReadableTextColor(friend.color); const isNorth = friend.id === northMarkerId; + const isGuardianAngel = guardianAngelEnabled && friend.id === guardianAngelId; + const isSpecialMarker = isNorth || isGuardianAngel; - if (!isNorth) { + if (!isSpecialMarker) { const presenceStatus = document.createElement('span'); presenceStatus.className = 'friend-presence-status'; presenceStatus.dataset.friendId = friend.id; diff --git a/src/public-settings.ts b/src/public-settings.ts index 6664542..50bf610 100644 --- a/src/public-settings.ts +++ b/src/public-settings.ts @@ -31,6 +31,10 @@ export const PUBLIC_SETTINGS = { /** Delay before showing "no compass data" status. */ probeTimeoutMs: 2500 }, + features: { + /** Whether to render the synthetic Guardian Angel marker. */ + guardianAngelEnabled: false + }, room: { /** Length of generated room IDs. */ idLength: 8 diff --git a/src/renderer.ts b/src/renderer.ts index 7e9d7aa..88bc29a 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -3,81 +3,6 @@ import type { PresenceThresholds } from './utils'; type ArrowElement = HTMLElement & { currentAngle: number | null }; -const OVERLAP_CLUSTER_DEG = 8; -const OVERLAP_SPACING_PX = 16; - -const getAngleDistance = (a: number, b: number) => Math.abs(getShortestDelta(a, b)); - -const buildOverlapOffsets = ({ - friendsToRender, - selfFriend, - selfHeading, - isFriendVisible, -}: { - friendsToRender: any[]; - selfFriend: any; - selfHeading: number; - isFriendVisible: (friendId: string) => boolean; -}) => { - const angleEntries = friendsToRender - .filter((target) => target.id !== selfFriend.id && isFriendVisible(target.id)) - .map((target) => { - const dx = target.position.lon - selfFriend.position.lon; - const dy = target.position.lat - selfFriend.position.lat; - const rawAngle = Math.atan2(dx, dy) * (180 / Math.PI); - return { - id: target.id, - angle: normalizeAngle(rawAngle - selfHeading), - }; - }) - .sort((a, b) => a.angle - b.angle); - - if (angleEntries.length === 0) return new Map<string, number>(); - - const clusters: Array<Array<{ id: string; angle: number }>> = []; - for (const entry of angleEntries) { - const currentCluster = clusters[clusters.length - 1]; - if (!currentCluster) { - clusters.push([entry]); - continue; - } - - const lastInCluster = currentCluster[currentCluster.length - 1]; - if (getAngleDistance(lastInCluster.angle, entry.angle) <= OVERLAP_CLUSTER_DEG) { - currentCluster.push(entry); - } else { - clusters.push([entry]); - } - } - - if (clusters.length > 1) { - const firstCluster = clusters[0]; - const lastCluster = clusters[clusters.length - 1]; - const firstAngle = firstCluster[0].angle; - const lastAngle = lastCluster[lastCluster.length - 1].angle; - if (getAngleDistance(firstAngle, lastAngle) <= OVERLAP_CLUSTER_DEG) { - clusters[0] = [...lastCluster, ...firstCluster]; - clusters.pop(); - } - } - - const offsetById = new Map<string, number>(); - for (const cluster of clusters) { - if (cluster.length === 1) { - offsetById.set(cluster[0].id, 0); - continue; - } - - const stableCluster = [...cluster].sort((a, b) => a.id.localeCompare(b.id)); - const midpoint = (stableCluster.length - 1) / 2; - stableCluster.forEach((entry, index) => { - offsetById.set(entry.id, (index - midpoint) * OVERLAP_SPACING_PX); - }); - } - - return offsetById; -}; - export const renderArrows = ({ content, arrow, @@ -89,12 +14,6 @@ export const renderArrows = ({ presenceThresholds, }) => { const now = Date.now(); - const overlapOffsetById = buildOverlapOffsets({ - friendsToRender, - selfFriend, - selfHeading, - isFriendVisible, - }); const activeArrowIds = new Set( friendsToRender .filter((target) => target.id !== selfFriend.id) @@ -148,12 +67,7 @@ export const renderArrows = ({ existingArrow.currentAngle = currentArrowAngle; existingArrow.style.transform = `rotate(${currentArrowAngle}deg)`; existingArrow.style.color = getArrowDisplayColor(target.color); - - const overlapOffset = overlapOffsetById.get(target.id) ?? 0; - const angleRad = currentArrowAngle * (Math.PI / 180); - const offsetX = overlapOffset * Math.cos(angleRad); - const offsetY = overlapOffset * Math.sin(angleRad); - existingArrow.style.translate = `calc(-50% + ${offsetX.toFixed(2)}px) calc(-50% + ${offsetY.toFixed(2)}px)`; + existingArrow.style.translate = ''; const presenceState = getFriendPresenceState(target, now, presenceThresholds); existingArrow.classList.toggle('is-online', presenceState === 'online');