fix(rmaps): join form overlay + authenticated user auto-join
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
676e29902e
commit
358965cb61
|
|
@ -287,16 +287,108 @@ class FolkMapViewer extends HTMLElement {
|
|||
if (this.userName) return true;
|
||||
// Use EncryptID username if authenticated
|
||||
const identityName = getUsername();
|
||||
const name = identityName || prompt("Your display name for this room:");
|
||||
if (!name?.trim()) return false;
|
||||
this.userName = name.trim();
|
||||
localStorage.setItem("rmaps_user", JSON.stringify({
|
||||
id: this.participantId,
|
||||
name: this.userName,
|
||||
emoji: this.userEmoji,
|
||||
color: this.userColor,
|
||||
}));
|
||||
return true;
|
||||
if (identityName) {
|
||||
this.userName = identityName;
|
||||
localStorage.setItem("rmaps_user", JSON.stringify({
|
||||
id: this.participantId,
|
||||
name: this.userName,
|
||||
emoji: this.userEmoji,
|
||||
color: this.userColor,
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private pendingRoomSlug = "";
|
||||
|
||||
private showJoinForm(slug: string) {
|
||||
this.pendingRoomSlug = slug;
|
||||
const saved = JSON.parse(localStorage.getItem("rmaps_user") || "null");
|
||||
const savedName = saved?.name || "";
|
||||
const savedEmoji = saved?.emoji || this.userEmoji;
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.id = "join-form-overlay";
|
||||
overlay.style.cssText = `
|
||||
position:fixed;inset:0;z-index:50;background:rgba(0,0,0,0.6);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
backdrop-filter:blur(4px);
|
||||
`;
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.style.cssText = `
|
||||
background:var(--rs-bg-surface);border:1px solid var(--rs-border-strong);
|
||||
border-radius:16px;padding:28px;width:340px;max-width:90vw;
|
||||
box-shadow:0 16px 48px rgba(0,0,0,0.4);text-align:center;
|
||||
font-family:system-ui,-apple-system,sans-serif;
|
||||
`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div style="font-size:48px;margin-bottom:4px;" id="join-avatar-preview">${savedEmoji}</div>
|
||||
<div style="font-size:18px;font-weight:700;color:var(--rs-text-primary);margin-bottom:4px;">Join ${this.esc(slug)}</div>
|
||||
<div style="font-size:13px;color:var(--rs-text-muted);margin-bottom:16px;">Choose your avatar and display name</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-bottom:16px;">
|
||||
${EMOJIS.map(e => `<button class="join-emoji-opt" data-e="${e}" style="width:36px;height:36px;border-radius:8px;border:2px solid ${e === savedEmoji ? "#4f46e5" : "transparent"};background:${e === savedEmoji ? "#4f46e520" : "var(--rs-bg-surface-sunken)"};font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:border-color 0.15s;">${e}</button>`).join("")}
|
||||
</div>
|
||||
<input id="join-name-input" type="text" placeholder="Your display name" value="${this.esc(savedName)}" style="
|
||||
width:100%;padding:10px 14px;border-radius:8px;border:1px solid var(--rs-border);
|
||||
background:var(--rs-input-bg);color:var(--rs-text-primary);font-size:15px;
|
||||
outline:none;margin-bottom:12px;box-sizing:border-box;
|
||||
">
|
||||
${savedName ? `<button id="join-quick-btn" style="
|
||||
width:100%;padding:10px;border-radius:8px;border:none;
|
||||
background:#4f46e5;color:#fff;font-weight:600;cursor:pointer;font-size:14px;
|
||||
margin-bottom:8px;
|
||||
">Join as ${this.esc(savedEmoji)} ${this.esc(savedName)}</button>` : ""}
|
||||
<button id="join-submit-btn" style="
|
||||
width:100%;padding:10px;border-radius:8px;border:1px solid var(--rs-border);
|
||||
background:var(--rs-bg-surface);color:var(--rs-text-primary);font-weight:600;cursor:pointer;font-size:14px;
|
||||
">${savedName ? "Join with new name" : "Join Room"}</button>
|
||||
<button id="join-cancel-btn" style="
|
||||
margin-top:8px;background:none;border:none;color:var(--rs-text-muted);
|
||||
cursor:pointer;font-size:12px;padding:4px;
|
||||
">Cancel</button>
|
||||
`;
|
||||
|
||||
overlay.appendChild(card);
|
||||
this.shadow.appendChild(overlay);
|
||||
|
||||
let selectedEmoji = savedEmoji;
|
||||
const preview = card.querySelector("#join-avatar-preview") as HTMLElement;
|
||||
const nameInput = card.querySelector("#join-name-input") as HTMLInputElement;
|
||||
|
||||
card.querySelectorAll(".join-emoji-opt").forEach(btn => {
|
||||
btn.addEventListener("click", () => {
|
||||
selectedEmoji = (btn as HTMLElement).dataset.e!;
|
||||
preview.textContent = selectedEmoji;
|
||||
card.querySelectorAll(".join-emoji-opt").forEach(b => {
|
||||
const isSelected = (b as HTMLElement).dataset.e === selectedEmoji;
|
||||
(b as HTMLElement).style.borderColor = isSelected ? "#4f46e5" : "transparent";
|
||||
(b as HTMLElement).style.background = isSelected ? "#4f46e520" : "var(--rs-bg-surface-sunken)";
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const doJoin = (name: string, emoji: string) => {
|
||||
if (!name.trim()) { nameInput.style.borderColor = "#ef4444"; return; }
|
||||
this.userName = name.trim();
|
||||
this.userEmoji = emoji;
|
||||
localStorage.setItem("rmaps_user", JSON.stringify({
|
||||
id: this.participantId, name: this.userName,
|
||||
emoji: this.userEmoji, color: this.userColor,
|
||||
}));
|
||||
overlay.remove();
|
||||
this.joinRoom(this.pendingRoomSlug);
|
||||
};
|
||||
|
||||
card.querySelector("#join-quick-btn")?.addEventListener("click", () => doJoin(savedName, savedEmoji));
|
||||
card.querySelector("#join-submit-btn")?.addEventListener("click", () => doJoin(nameInput.value, selectedEmoji));
|
||||
nameInput.addEventListener("keydown", (e) => { if (e.key === "Enter") doJoin(nameInput.value, selectedEmoji); });
|
||||
card.querySelector("#join-cancel-btn")?.addEventListener("click", () => overlay.remove());
|
||||
overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
|
||||
nameInput.focus();
|
||||
}
|
||||
|
||||
// ─── Demo mode ───────────────────────────────────────────────
|
||||
|
|
@ -1071,7 +1163,10 @@ class FolkMapViewer extends HTMLElement {
|
|||
// ─── Room mode: join / leave / create ────────────────────────
|
||||
|
||||
private joinRoom(slug: string) {
|
||||
if (!this.ensureUserProfile()) return;
|
||||
if (!this.ensureUserProfile()) {
|
||||
this.showJoinForm(slug);
|
||||
return;
|
||||
}
|
||||
this.room = slug;
|
||||
this.view = "map";
|
||||
this.render();
|
||||
|
|
@ -1110,10 +1205,57 @@ class FolkMapViewer extends HTMLElement {
|
|||
|
||||
private createRoom() {
|
||||
if (!requireAuth("create map room")) return;
|
||||
const name = prompt("Room name (slug):");
|
||||
if (!name?.trim()) return;
|
||||
const slug = name.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
||||
this.joinRoom(slug);
|
||||
this.showCreateRoomForm();
|
||||
}
|
||||
|
||||
private showCreateRoomForm() {
|
||||
const overlay = document.createElement("div");
|
||||
overlay.id = "create-room-overlay";
|
||||
overlay.style.cssText = `
|
||||
position:fixed;inset:0;z-index:50;background:rgba(0,0,0,0.6);
|
||||
display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);
|
||||
`;
|
||||
const card = document.createElement("div");
|
||||
card.style.cssText = `
|
||||
background:var(--rs-bg-surface);border:1px solid var(--rs-border-strong);
|
||||
border-radius:16px;padding:28px;width:340px;max-width:90vw;
|
||||
box-shadow:0 16px 48px rgba(0,0,0,0.4);text-align:center;
|
||||
font-family:system-ui,-apple-system,sans-serif;
|
||||
`;
|
||||
card.innerHTML = `
|
||||
<div style="font-size:32px;margin-bottom:8px;">🌐</div>
|
||||
<div style="font-size:18px;font-weight:700;color:var(--rs-text-primary);margin-bottom:4px;">Create Room</div>
|
||||
<div style="font-size:13px;color:var(--rs-text-muted);margin-bottom:16px;">Enter a name for your new map room</div>
|
||||
<input id="room-name-input" type="text" placeholder="Room name (e.g. berlin-meetup)" style="
|
||||
width:100%;padding:10px 14px;border-radius:8px;border:1px solid var(--rs-border);
|
||||
background:var(--rs-input-bg);color:var(--rs-text-primary);font-size:15px;
|
||||
outline:none;margin-bottom:12px;box-sizing:border-box;
|
||||
">
|
||||
<button id="room-create-btn" style="
|
||||
width:100%;padding:10px;border-radius:8px;border:none;
|
||||
background:#4f46e5;color:#fff;font-weight:600;cursor:pointer;font-size:14px;
|
||||
">Create & Join</button>
|
||||
<button id="room-cancel-btn" style="
|
||||
margin-top:8px;background:none;border:none;color:var(--rs-text-muted);
|
||||
cursor:pointer;font-size:12px;padding:4px;
|
||||
">Cancel</button>
|
||||
`;
|
||||
overlay.appendChild(card);
|
||||
this.shadow.appendChild(overlay);
|
||||
|
||||
const input = card.querySelector("#room-name-input") as HTMLInputElement;
|
||||
const doCreate = () => {
|
||||
const name = input.value.trim();
|
||||
if (!name) { input.style.borderColor = "#ef4444"; return; }
|
||||
const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
||||
overlay.remove();
|
||||
this.joinRoom(slug);
|
||||
};
|
||||
card.querySelector("#room-create-btn")?.addEventListener("click", doCreate);
|
||||
input.addEventListener("keydown", (e) => { if (e.key === "Enter") doCreate(); });
|
||||
card.querySelector("#room-cancel-btn")?.addEventListener("click", () => overlay.remove());
|
||||
overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
input.focus();
|
||||
}
|
||||
|
||||
private leaveRoom() {
|
||||
|
|
@ -1258,6 +1400,7 @@ class FolkMapViewer extends HTMLElement {
|
|||
});
|
||||
|
||||
saveRoomVisit(this.room, this.room);
|
||||
try { localStorage.setItem("rmaps_last_room", this.room); } catch {}
|
||||
|
||||
// Listen for SW-forwarded location request pushes
|
||||
if ("serviceWorker" in navigator) {
|
||||
|
|
@ -1379,6 +1522,21 @@ class FolkMapViewer extends HTMLElement {
|
|||
emojiSpan.className = "marker-emoji";
|
||||
emojiSpan.textContent = p.emoji;
|
||||
el.appendChild(emojiSpan);
|
||||
|
||||
// Status dot overlay (bottom-right corner)
|
||||
const statusDot = document.createElement("div");
|
||||
statusDot.className = "marker-status-dot";
|
||||
const dotColor = isStale ? "#6b7280" : (
|
||||
p.status === "online" ? "#22c55e" : p.status === "away" ? "#f59e0b" :
|
||||
p.status === "ghost" ? "#64748b" : "#ef4444"
|
||||
);
|
||||
statusDot.style.cssText = `
|
||||
position:absolute;bottom:-1px;right:-1px;
|
||||
width:10px;height:10px;border-radius:50%;
|
||||
background:${dotColor};border:2px solid ${markerBg};
|
||||
box-shadow:0 0 6px ${dotColor};
|
||||
`;
|
||||
el.appendChild(statusDot);
|
||||
}
|
||||
|
||||
// Staleness tooltip
|
||||
|
|
@ -1477,6 +1635,13 @@ class FolkMapViewer extends HTMLElement {
|
|||
this.indoorView.updateParticipants(state.participants);
|
||||
}
|
||||
|
||||
// Update participant counts (header + mobile)
|
||||
const pCount = String(Object.keys(state.participants).length);
|
||||
const headerCount = this.shadow.getElementById("header-participant-count");
|
||||
if (headerCount) headerCount.textContent = pCount;
|
||||
const mobileCount = this.shadow.getElementById("mobile-friends-count");
|
||||
if (mobileCount) mobileCount.textContent = pCount;
|
||||
|
||||
// Update participant list sidebar
|
||||
this.updateParticipantList(state);
|
||||
}
|
||||
|
|
@ -1508,11 +1673,12 @@ class FolkMapViewer extends HTMLElement {
|
|||
const ageSec = Math.floor(ageMs / 1000);
|
||||
ageLabel = ageSec < 60 ? `${ageSec}s ago` : ageSec < 3600 ? `${Math.floor(ageSec / 60)}m ago` : "stale";
|
||||
}
|
||||
const glowColor = isStale ? "#6b7280" : statusColor;
|
||||
return `
|
||||
<div class="participant-entry" style="display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--rs-border);${isStale ? "opacity:0.6;" : ""}">
|
||||
<div style="position:relative;">
|
||||
<span style="font-size:18px">${this.esc(p.emoji)}</span>
|
||||
<span style="position:absolute;bottom:-2px;right:-2px;width:8px;height:8px;border-radius:50%;background:${isStale ? "#6b7280" : statusColor};border:2px solid var(--rs-bg-surface);"></span>
|
||||
<div class="participant-entry" style="display:flex;align-items:center;gap:8px;padding:8px 4px;border-bottom:1px solid var(--rs-border);${isStale ? "opacity:0.6;" : ""}">
|
||||
<div style="position:relative;flex-shrink:0;">
|
||||
<span style="font-size:20px;display:block;width:28px;text-align:center;">${this.esc(p.emoji)}</span>
|
||||
<span style="position:absolute;bottom:-1px;right:-1px;width:10px;height:10px;border-radius:50%;background:${glowColor};border:2px solid var(--rs-bg-surface);box-shadow:0 0 6px ${glowColor};"></span>
|
||||
</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-size:13px;font-weight:600;color:${isStale ? "#6b7280" : p.color};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${this.esc(p.name)}</div>
|
||||
|
|
@ -1521,8 +1687,8 @@ class FolkMapViewer extends HTMLElement {
|
|||
${distLabel ? ` \u2022 ${distLabel}` : ""}
|
||||
</div>
|
||||
</div>
|
||||
${p.id !== this.participantId && p.location ? `<button class="nav-to-btn" data-nav-participant="${p.id}" title="Navigate" style="background:none;border:1px solid var(--rs-border);border-radius:4px;color:var(--rs-text-muted);cursor:pointer;padding:2px 6px;font-size:11px;">\u{1F9ED}</button>` : ""}
|
||||
${p.id !== this.participantId ? `<button class="ping-btn-inline" data-ping="${p.id}" title="Ping" style="background:none;border:1px solid var(--rs-border);border-radius:4px;color:var(--rs-text-muted);cursor:pointer;padding:2px 6px;font-size:11px;">\u{1F514}</button>` : ""}
|
||||
${p.id !== this.participantId && p.location ? `<button class="nav-to-btn" data-nav-participant="${p.id}" title="Navigate" style="background:none;border:1px solid var(--rs-border);border-radius:6px;color:var(--rs-text-muted);cursor:pointer;padding:3px 8px;font-size:12px;">🧭</button>` : ""}
|
||||
${p.id !== this.participantId ? `<button class="ping-btn-inline" data-ping="${p.id}" title="Ping" style="background:none;border:1px solid var(--rs-border);border-radius:6px;color:var(--rs-text-muted);cursor:pointer;padding:3px 8px;font-size:12px;">🔔</button>` : ""}
|
||||
</div>`;
|
||||
}).join("");
|
||||
}
|
||||
|
|
@ -1562,29 +1728,33 @@ class FolkMapViewer extends HTMLElement {
|
|||
const list = this.shadow.getElementById("participant-list");
|
||||
const mobileList = this.shadow.getElementById("participant-list-mobile");
|
||||
|
||||
const count = Object.keys(state.participants).length;
|
||||
const html = this.buildParticipantHTML(state);
|
||||
const headerHTML = `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
||||
<span style="font-size:12px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.06em;">Friends (${count})</span>
|
||||
<button id="sidebar-ping-all-btn" style="padding:3px 8px;border-radius:6px;border:1px solid var(--rs-border);background:var(--rs-bg-surface);color:var(--rs-text-secondary);cursor:pointer;font-size:10px;white-space:nowrap;">📢 Ping All</button>
|
||||
</div>
|
||||
`;
|
||||
const footerHTML = `
|
||||
<div style="display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;">
|
||||
<button id="sidebar-meeting-btn" style="flex:1;padding:6px;border-radius:6px;border:1px solid var(--rs-border);background:var(--rs-bg-surface);color:var(--rs-text-secondary);cursor:pointer;font-size:11px;white-space:nowrap;">\u{1F4CD} Meeting Point</button>
|
||||
<button id="sidebar-import-btn" style="flex:1;padding:6px;border-radius:6px;border:1px solid var(--rs-border);background:var(--rs-bg-surface);color:var(--rs-text-secondary);cursor:pointer;font-size:11px;white-space:nowrap;">\u{1F4E5} Import Places</button>
|
||||
<button id="sidebar-ping-all-btn" style="flex:1;padding:6px;border-radius:6px;border:1px solid var(--rs-border);background:var(--rs-bg-surface);color:var(--rs-text-secondary);cursor:pointer;font-size:11px;white-space:nowrap;">\u{1F4E2} Ping All</button>
|
||||
<button id="sidebar-meeting-btn" style="flex:1;padding:6px;border-radius:6px;border:1px solid var(--rs-border);background:var(--rs-bg-surface);color:var(--rs-text-secondary);cursor:pointer;font-size:11px;white-space:nowrap;">📍 Meeting Point</button>
|
||||
<button id="sidebar-import-btn" style="flex:1;padding:6px;border-radius:6px;border:1px solid var(--rs-border);background:var(--rs-bg-surface);color:var(--rs-text-secondary);cursor:pointer;font-size:11px;white-space:nowrap;">📥 Import Places</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Desktop sidebar
|
||||
if (list) {
|
||||
list.innerHTML = html + footerHTML;
|
||||
list.innerHTML = headerHTML + html + footerHTML;
|
||||
this.attachParticipantListeners(list);
|
||||
}
|
||||
|
||||
// Mobile bottom sheet
|
||||
if (mobileList) {
|
||||
mobileList.innerHTML = html;
|
||||
mobileList.innerHTML = html + footerHTML;
|
||||
this.attachParticipantListeners(mobileList);
|
||||
// Update sheet header count
|
||||
const header = this.shadow.querySelector(".sheet-header span:first-child");
|
||||
const count = Object.keys(state.participants).length;
|
||||
if (header) header.textContent = `Participants (${count})`;
|
||||
const sheetCount = this.shadow.getElementById("sheet-participant-count");
|
||||
if (sheetCount) sheetCount.textContent = String(count);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1622,6 +1792,16 @@ class FolkMapViewer extends HTMLElement {
|
|||
el.title = `${p.name} - ${p.status} (${ageLabel})`;
|
||||
const arrow = el.querySelector(".heading-arrow") as HTMLElement | null;
|
||||
if (arrow) arrow.style.borderBottomColor = isStale ? "#6b7280" : p.color;
|
||||
// Update status dot
|
||||
const statusDot = el.querySelector(".marker-status-dot") as HTMLElement | null;
|
||||
if (statusDot) {
|
||||
const dotColor = isStale ? "#6b7280" : (
|
||||
p.status === "online" ? "#22c55e" : p.status === "away" ? "#f59e0b" :
|
||||
p.status === "ghost" ? "#64748b" : "#ef4444"
|
||||
);
|
||||
statusDot.style.background = dotColor;
|
||||
statusDot.style.boxShadow = `0 0 6px ${dotColor}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1786,19 +1966,35 @@ class FolkMapViewer extends HTMLElement {
|
|||
|
||||
private updateShareButton() {
|
||||
const btn = this.shadow.getElementById("share-location");
|
||||
if (!btn) return;
|
||||
if (this.privacySettings.precision === "hidden") {
|
||||
btn.textContent = "\u{1F47B} Hidden";
|
||||
btn.classList.remove("sharing");
|
||||
btn.classList.add("ghost");
|
||||
} else if (this.sharingLocation) {
|
||||
btn.textContent = "\u{1F4CD} Stop Sharing";
|
||||
btn.classList.add("sharing");
|
||||
btn.classList.remove("ghost");
|
||||
} else {
|
||||
btn.textContent = "\u{1F4CD} Share Location";
|
||||
btn.classList.remove("sharing");
|
||||
btn.classList.remove("ghost");
|
||||
if (btn) {
|
||||
if (this.privacySettings.precision === "hidden") {
|
||||
btn.textContent = "\u{1F47B} Hidden";
|
||||
btn.classList.remove("sharing");
|
||||
btn.classList.add("ghost");
|
||||
} else if (this.sharingLocation) {
|
||||
btn.textContent = "\u{1F4CD} Stop Sharing";
|
||||
btn.classList.add("sharing");
|
||||
btn.classList.remove("ghost");
|
||||
} else {
|
||||
btn.textContent = "\u{1F4CD} Share Location";
|
||||
btn.classList.remove("sharing");
|
||||
btn.classList.remove("ghost");
|
||||
}
|
||||
}
|
||||
|
||||
// Floating share button
|
||||
const floatBtn = this.shadow.getElementById("map-share-float");
|
||||
if (floatBtn) {
|
||||
if (this.privacySettings.precision === "hidden") {
|
||||
floatBtn.innerHTML = "👻 Hidden";
|
||||
floatBtn.className = "map-share-float ghost";
|
||||
} else if (this.sharingLocation) {
|
||||
floatBtn.innerHTML = "📍 Sharing";
|
||||
floatBtn.className = "map-share-float active";
|
||||
} else {
|
||||
floatBtn.innerHTML = "📍 Share Location";
|
||||
floatBtn.className = "map-share-float";
|
||||
}
|
||||
}
|
||||
// Update permission indicator
|
||||
const permIndicator = this.shadow.getElementById("geo-perm-indicator");
|
||||
|
|
@ -1807,6 +2003,11 @@ class FolkMapViewer extends HTMLElement {
|
|||
permIndicator.style.background = colors[this.geoPermissionState] || "#64748b";
|
||||
permIndicator.title = `Geolocation: ${this.geoPermissionState || "unknown"}`;
|
||||
}
|
||||
// Update header share toggle
|
||||
const headerToggle = this.shadow.getElementById("header-share-toggle");
|
||||
if (headerToggle) {
|
||||
headerToggle.className = `map-header__share-toggle ${this.sharingLocation ? "active" : ""}`;
|
||||
}
|
||||
// Also update mobile FAB
|
||||
this.updateMobileFab();
|
||||
}
|
||||
|
|
@ -2369,11 +2570,49 @@ class FolkMapViewer extends HTMLElement {
|
|||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
/* Lobby nav */
|
||||
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
|
||||
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px; }
|
||||
.rapp-nav__back:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
|
||||
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
/* Map room header — dark bar */
|
||||
.map-header {
|
||||
display:flex;align-items:center;gap:8px;padding:8px 14px;
|
||||
background:rgba(15,23,42,0.95);backdrop-filter:blur(8px);
|
||||
border-radius:10px;margin-bottom:12px;min-height:44px;
|
||||
border:1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.map-header__left { display:flex;align-items:center;gap:6px;flex:1;min-width:0; }
|
||||
.map-header__back {
|
||||
background:none;border:none;color:#94a3b8;cursor:pointer;
|
||||
font-size:16px;padding:4px 6px;border-radius:4px;
|
||||
}
|
||||
.map-header__back:hover { color:#e2e8f0; }
|
||||
.map-header__logo { font-size:13px;font-weight:700;color:#e2e8f0;white-space:nowrap; }
|
||||
.map-header__slug { font-size:12px;color:#64748b;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
||||
.map-header__participants {
|
||||
display:flex;align-items:center;gap:4px;
|
||||
padding:4px 12px;border-radius:20px;
|
||||
background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.12);
|
||||
color:#e2e8f0;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;
|
||||
}
|
||||
.map-header__participants:hover { background:rgba(255,255,255,0.14); }
|
||||
.map-header__right { display:flex;align-items:center;gap:4px; }
|
||||
.map-header__icon-btn {
|
||||
background:none;border:none;color:#94a3b8;cursor:pointer;
|
||||
font-size:15px;padding:4px 6px;border-radius:4px;
|
||||
}
|
||||
.map-header__icon-btn:hover { color:#e2e8f0; }
|
||||
.map-header__share-toggle {
|
||||
width:32px;height:32px;border-radius:50%;
|
||||
background:transparent;border:1px solid rgba(255,255,255,0.15);
|
||||
color:#94a3b8;cursor:pointer;font-size:14px;
|
||||
display:flex;align-items:center;justify-content:center;transition:all 0.2s;
|
||||
}
|
||||
.map-header__share-toggle.active { background:#10b981;border-color:#10b981;color:#fff; }
|
||||
.map-header__share-toggle:hover { border-color:#10b981;color:#10b981; }
|
||||
|
||||
.status-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%; display: inline-block;
|
||||
}
|
||||
|
|
@ -2448,8 +2687,21 @@ class FolkMapViewer extends HTMLElement {
|
|||
}
|
||||
.map-locate-fab:hover { border-color: #4285f4; color: #4285f4; }
|
||||
|
||||
/* Mobile FAB menu — hidden on desktop */
|
||||
.mobile-fab-container { display: none; }
|
||||
/* Floating share-location button on map */
|
||||
.map-share-float {
|
||||
position:absolute;bottom:20px;right:16px;z-index:6;
|
||||
padding:10px 18px;border-radius:24px;
|
||||
background:#fff;border:1px solid #e2e8f0;
|
||||
color:#1e293b;font-weight:600;font-size:13px;cursor:pointer;
|
||||
box-shadow:0 2px 10px rgba(0,0,0,0.15);transition:all 0.2s;
|
||||
font-family:system-ui,-apple-system,sans-serif;
|
||||
}
|
||||
.map-share-float.active { background:#10b981;border-color:#10b981;color:#fff; }
|
||||
.map-share-float.ghost { background:#8b5cf6;border-color:#8b5cf6;color:#fff; }
|
||||
.map-share-float:hover { box-shadow:0 4px 16px rgba(0,0,0,0.2); }
|
||||
|
||||
/* Mobile floating buttons — hidden on desktop */
|
||||
.mobile-float-btns { display: none; }
|
||||
.mobile-bottom-sheet { display: none; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
|
@ -2458,52 +2710,20 @@ class FolkMapViewer extends HTMLElement {
|
|||
.map-sidebar { display: none; }
|
||||
.controls { display: none; }
|
||||
#privacy-panel { display: none !important; }
|
||||
.rapp-nav { position: absolute; top: 0; left: 0; right: 0; z-index: 7; background: var(--rs-bg-surface); border-bottom: 1px solid var(--rs-border); margin: 0; padding: 6px 12px; min-height: 48px; }
|
||||
.map-header { position:absolute;top:0;left:0;right:0;z-index:7;margin:0;border-radius:0;border-bottom:1px solid rgba(255,255,255,0.08); }
|
||||
.map-locate-fab { bottom: 100px; }
|
||||
|
||||
/* Mobile FAB menu */
|
||||
.mobile-fab-container {
|
||||
display: block; position: fixed; bottom: 24px; right: 16px; z-index: 8;
|
||||
/* Mobile floating pill buttons */
|
||||
.mobile-float-btns { display:flex;position:fixed;bottom:16px;left:0;right:0;z-index:8;padding:0 16px;justify-content:space-between;pointer-events:none; }
|
||||
.mobile-pill-btn {
|
||||
padding:10px 16px;border-radius:24px;border:none;
|
||||
background:rgba(15,23,42,0.92);color:#e2e8f0;font-weight:600;
|
||||
font-size:13px;cursor:pointer;pointer-events:auto;
|
||||
box-shadow:0 4px 16px rgba(0,0,0,0.3);backdrop-filter:blur(4px);
|
||||
font-family:system-ui,-apple-system,sans-serif;
|
||||
}
|
||||
.fab-main {
|
||||
width: 52px; height: 52px; border-radius: 50%;
|
||||
background: #4f46e5; border: none; color: #fff; cursor: pointer;
|
||||
font-size: 22px; display: flex; align-items: center; justify-content: center;
|
||||
box-shadow: 0 4px 16px rgba(79,70,229,0.5); transition: transform 0.2s;
|
||||
}
|
||||
.fab-main.open { transform: rotate(45deg); }
|
||||
.fab-mini-list {
|
||||
position: absolute; bottom: 62px; right: 2px;
|
||||
display: flex; flex-direction: column-reverse; gap: 10px;
|
||||
opacity: 0; pointer-events: none; transition: opacity 0.2s;
|
||||
}
|
||||
.fab-mini-list.open { opacity: 1; pointer-events: auto; }
|
||||
.fab-mini {
|
||||
display: flex; align-items: center; gap: 8px; flex-direction: row-reverse;
|
||||
}
|
||||
.fab-mini-btn {
|
||||
width: 40px; height: 40px; border-radius: 50%;
|
||||
border: 1px solid var(--rs-border); background: var(--rs-bg-surface);
|
||||
color: var(--rs-text-primary); cursor: pointer; font-size: 16px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
transform: scale(0); transition: transform 0.15s;
|
||||
}
|
||||
.fab-mini-list.open .fab-mini-btn { transform: scale(1); }
|
||||
.fab-mini-list.open .fab-mini:nth-child(1) .fab-mini-btn { transition-delay: 0s; }
|
||||
.fab-mini-list.open .fab-mini:nth-child(2) .fab-mini-btn { transition-delay: 0.04s; }
|
||||
.fab-mini-list.open .fab-mini:nth-child(3) .fab-mini-btn { transition-delay: 0.08s; }
|
||||
.fab-mini-list.open .fab-mini:nth-child(4) .fab-mini-btn { transition-delay: 0.12s; }
|
||||
.fab-mini-list.open .fab-mini:nth-child(5) .fab-mini-btn { transition-delay: 0.16s; }
|
||||
.fab-mini-list.open .fab-mini:nth-child(6) .fab-mini-btn { transition-delay: 0.20s; }
|
||||
.fab-mini-list.open .fab-mini:nth-child(7) .fab-mini-btn { transition-delay: 0.24s; }
|
||||
.fab-mini-label {
|
||||
font-size: 11px; background: var(--rs-bg-surface); color: var(--rs-text-secondary);
|
||||
padding: 4px 8px; border-radius: 6px; white-space: nowrap;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.15); border: 1px solid var(--rs-border);
|
||||
}
|
||||
.fab-mini-btn.sharing { border-color: #22c55e; color: #22c55e; }
|
||||
.fab-mini-btn.ghost { border-color: #8b5cf6; color: #8b5cf6; }
|
||||
.mobile-pill-btn:active { opacity:0.8; }
|
||||
.map-share-float { bottom:70px; }
|
||||
|
||||
/* Mobile bottom sheet */
|
||||
.mobile-bottom-sheet {
|
||||
|
|
@ -2611,6 +2831,37 @@ class FolkMapViewer extends HTMLElement {
|
|||
startTour() { this._tour.start(); }
|
||||
|
||||
private renderLobby(): string {
|
||||
const saved = JSON.parse(localStorage.getItem("rmaps_user") || "null");
|
||||
const lastRoom = localStorage.getItem("rmaps_last_room") || "";
|
||||
|
||||
const profileSection = `
|
||||
<div style="background:var(--rs-glass-bg);border:1px solid var(--rs-border);border-radius:12px;padding:16px;margin-bottom:16px;">
|
||||
<div style="font-size:12px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:10px;">Get Started</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
|
||||
<div style="position:relative;">
|
||||
<span id="lobby-avatar-preview" style="font-size:32px;display:block;cursor:pointer;" title="Click to change emoji">${this.userEmoji}</span>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<input id="lobby-name-input" type="text" placeholder="Your display name" value="${this.esc(saved?.name || this.userName || "")}" style="
|
||||
width:100%;padding:8px 12px;border-radius:8px;border:1px solid var(--rs-border);
|
||||
background:var(--rs-input-bg);color:var(--rs-text-primary);font-size:14px;outline:none;box-sizing:border-box;
|
||||
">
|
||||
</div>
|
||||
</div>
|
||||
<div id="lobby-emoji-grid" style="display:none;flex-wrap:wrap;gap:4px;margin-bottom:10px;justify-content:center;">
|
||||
${EMOJIS.map(e => `<button class="lobby-emoji-pick" data-lobby-emoji="${e}" style="width:34px;height:34px;border-radius:6px;border:2px solid ${e === this.userEmoji ? "#4f46e5" : "transparent"};background:${e === this.userEmoji ? "#4f46e520" : "var(--rs-bg-surface-sunken)"};font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;">${e}</button>`).join("")}
|
||||
</div>
|
||||
${lastRoom ? `
|
||||
<button id="lobby-rejoin-btn" style="
|
||||
width:100%;padding:10px;border-radius:8px;border:none;
|
||||
background:#4f46e5;color:#fff;font-weight:600;cursor:pointer;font-size:13px;
|
||||
margin-bottom:6px;
|
||||
">Rejoin 🌐 ${this.esc(lastRoom)}</button>
|
||||
` : ""}
|
||||
<div style="font-size:11px;color:var(--rs-text-muted);text-align:center;">Profile is saved locally for quick rejoins</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const history = loadRoomHistory();
|
||||
const historyCards = history.length > 0 ? `
|
||||
<div class="section-label">Recent Rooms</div>
|
||||
|
|
@ -2640,6 +2891,8 @@ class FolkMapViewer extends HTMLElement {
|
|||
<button class="rapp-nav__btn" id="btn-tour" style="background:transparent;border:1px solid var(--rs-border,#334155);color:var(--rs-text-secondary,#94a3b8);font-weight:500">Tour</button>
|
||||
</div>
|
||||
|
||||
${profileSection}
|
||||
|
||||
${this.rooms.length > 0 ? `
|
||||
<div class="section-label">Active Rooms</div>
|
||||
${this.rooms.map((r) => `
|
||||
|
|
@ -2661,12 +2914,24 @@ class FolkMapViewer extends HTMLElement {
|
|||
|
||||
private renderMap(): string {
|
||||
return `
|
||||
<div class="rapp-nav">
|
||||
${this._history.canGoBack ? '<button class="rapp-nav__back" data-back="lobby">← Rooms</button>' : ''}
|
||||
<span class="rapp-nav__title">\u{1F5FA} ${this.esc(this.room)}</span>
|
||||
<span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}"></span>
|
||||
<span id="geo-perm-indicator" style="width:6px;height:6px;border-radius:50%;background:#64748b;" title="Geolocation: unknown"></span>
|
||||
<button id="bell-toggle" style="background:none;border:none;color:var(--rs-text-muted);cursor:pointer;font-size:16px;padding:4px;" title="Notifications">\u{1F514}</button>
|
||||
<div class="map-header">
|
||||
<div class="map-header__left">
|
||||
${this._history.canGoBack ? '<button class="map-header__back" data-back="lobby">←</button>' : ''}
|
||||
<span class="map-header__logo">🌐 rMaps</span>
|
||||
<span class="map-header__slug">/${this.esc(this.room)}</span>
|
||||
<span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}" style="margin-left:6px;"></span>
|
||||
</div>
|
||||
<button class="map-header__participants" id="header-participants-toggle" title="Show participants">
|
||||
👥 <span id="header-participant-count">0</span>
|
||||
</button>
|
||||
<div class="map-header__right">
|
||||
<span id="geo-perm-indicator" style="width:6px;height:6px;border-radius:50%;background:#64748b;" title="Geolocation: unknown"></span>
|
||||
<button class="map-header__icon-btn" id="bell-toggle" title="Notifications">🔔</button>
|
||||
<button class="map-header__icon-btn" id="share-room-btn" title="Share rMap">📤</button>
|
||||
<button class="map-header__share-toggle ${this.sharingLocation ? "active" : ""}" id="header-share-toggle" title="${this.sharingLocation ? "Sharing location" : "Share location"}">
|
||||
${this.sharingLocation ? "📍" : "📍"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="map-layout">
|
||||
|
|
@ -2705,54 +2970,30 @@ class FolkMapViewer extends HTMLElement {
|
|||
<button class="map-locate-fab" id="locate-me-fab" title="Center on my location">\u{1F3AF}</button>
|
||||
<button class="map-locate-fab" id="fit-all-fab" title="Show all participants" style="bottom:80px;">\u{1F465}</button>
|
||||
|
||||
<!-- Mobile FAB menu -->
|
||||
<div class="mobile-fab-container" id="mobile-fab-container">
|
||||
<div class="fab-mini-list" id="fab-mini-list">
|
||||
<div class="fab-mini">
|
||||
<button class="fab-mini-btn ${this.sharingLocation ? "sharing" : ""} ${this.privacySettings.ghostMode ? "ghost" : ""}" id="fab-share-loc" title="Share Location">\u{1F4CD}</button>
|
||||
<span class="fab-mini-label">${this.privacySettings.ghostMode ? "Ghost" : this.sharingLocation ? "Stop" : "Share"}</span>
|
||||
</div>
|
||||
<div class="fab-mini">
|
||||
<button class="fab-mini-btn" id="fab-privacy" title="Privacy">\u{1F6E1}</button>
|
||||
<span class="fab-mini-label">Privacy</span>
|
||||
</div>
|
||||
<div class="fab-mini">
|
||||
<button class="fab-mini-btn" id="fab-drop-pin" title="Drop Pin">\u{1F4CC}</button>
|
||||
<span class="fab-mini-label">Drop Pin</span>
|
||||
</div>
|
||||
<div class="fab-mini">
|
||||
<button class="fab-mini-btn" id="fab-share-map" title="Share rMap">\u{1F4E4}</button>
|
||||
<span class="fab-mini-label">Share rMap</span>
|
||||
</div>
|
||||
<div class="fab-mini">
|
||||
<button class="fab-mini-btn" id="fab-emoji" title="Emoji">${this.userEmoji}</button>
|
||||
<span class="fab-mini-label">Emoji</span>
|
||||
</div>
|
||||
<div class="fab-mini">
|
||||
<button class="fab-mini-btn" id="fab-chat" title="Chat" style="position:relative;">\u{1F4AC}<span id="fab-chat-badge" style="display:${this.unreadCount > 0 ? "flex" : "none"};position:absolute;top:-4px;right:-4px;min-width:14px;height:14px;border-radius:7px;background:#ef4444;color:#fff;font-size:8px;align-items:center;justify-content:center;padding:0 3px;">${this.unreadCount || ""}</span></button>
|
||||
<span class="fab-mini-label">Chat</span>
|
||||
</div>
|
||||
<div class="fab-mini">
|
||||
<button class="fab-mini-btn" id="fab-indoor" title="Indoor">${this.mapMode === "indoor" ? "\u{1F30D}" : "\u{1F3E2}"}</button>
|
||||
<span class="fab-mini-label">${this.mapMode === "indoor" ? "Outdoor" : "Indoor"}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="fab-main" id="fab-main" title="Menu">\u2699</button>
|
||||
<!-- Mobile floating buttons (rmaps-online style) -->
|
||||
<div class="mobile-float-btns">
|
||||
<button class="mobile-pill-btn mobile-pill-left" id="mobile-friends-btn">👥 See Friends (<span id="mobile-friends-count">0</span>)</button>
|
||||
<button class="mobile-pill-btn mobile-pill-right" id="mobile-qr-btn">▩ QR</button>
|
||||
</div>
|
||||
|
||||
<!-- Floating share-location button on map -->
|
||||
<button class="map-share-float ${this.sharingLocation ? "active" : ""} ${this.privacySettings.precision === "hidden" ? "ghost" : ""}" id="map-share-float">
|
||||
${this.privacySettings.precision === "hidden" ? "👻 Hidden" : this.sharingLocation ? "📍 Sharing" : "📍 Share Location"}
|
||||
</button>
|
||||
|
||||
<!-- Mobile bottom sheet (participants) -->
|
||||
<div class="mobile-bottom-sheet" id="mobile-bottom-sheet">
|
||||
<div class="sheet-handle" id="sheet-handle">
|
||||
<div class="sheet-handle-bar"></div>
|
||||
</div>
|
||||
<div class="sheet-header">
|
||||
<span>Participants</span>
|
||||
<span style="font-size:10px;font-weight:400;color:var(--rs-text-muted);text-transform:none;letter-spacing:0;">tap to expand</span>
|
||||
<span>Friends (<span id="sheet-participant-count">0</span>)</span>
|
||||
<button id="sheet-close-btn" style="background:none;border:none;color:var(--rs-text-muted);cursor:pointer;font-size:14px;padding:2px 6px;">✕</button>
|
||||
</div>
|
||||
<div class="sheet-content" id="participant-list-mobile"></div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile privacy popup (hidden by default) -->
|
||||
<!-- Mobile privacy popup (hidden by default, triggered from bottom sheet) -->
|
||||
<div class="mobile-privacy-popup" id="mobile-privacy-popup" style="display:none;"></div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -2761,6 +3002,60 @@ class FolkMapViewer extends HTMLElement {
|
|||
this.shadow.getElementById("btn-tour")?.addEventListener("click", () => this.startTour());
|
||||
this.shadow.getElementById("create-room")?.addEventListener("click", () => this.createRoom());
|
||||
|
||||
// Lobby profile section
|
||||
const lobbyNameInput = this.shadow.getElementById("lobby-name-input") as HTMLInputElement;
|
||||
if (lobbyNameInput) {
|
||||
lobbyNameInput.addEventListener("change", () => {
|
||||
const name = lobbyNameInput.value.trim();
|
||||
if (name) {
|
||||
this.userName = name;
|
||||
localStorage.setItem("rmaps_user", JSON.stringify({
|
||||
id: this.participantId, name: this.userName,
|
||||
emoji: this.userEmoji, color: this.userColor,
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
const lobbyAvatar = this.shadow.getElementById("lobby-avatar-preview");
|
||||
const lobbyEmojiGrid = this.shadow.getElementById("lobby-emoji-grid");
|
||||
if (lobbyAvatar && lobbyEmojiGrid) {
|
||||
lobbyAvatar.addEventListener("click", () => {
|
||||
lobbyEmojiGrid.style.display = lobbyEmojiGrid.style.display === "none" ? "flex" : "none";
|
||||
});
|
||||
}
|
||||
this.shadow.querySelectorAll("[data-lobby-emoji]").forEach(btn => {
|
||||
btn.addEventListener("click", () => {
|
||||
const emoji = (btn as HTMLElement).dataset.lobbyEmoji!;
|
||||
this.userEmoji = emoji;
|
||||
if (lobbyAvatar) lobbyAvatar.textContent = emoji;
|
||||
this.shadow.querySelectorAll("[data-lobby-emoji]").forEach(b => {
|
||||
const sel = (b as HTMLElement).dataset.lobbyEmoji === emoji;
|
||||
(b as HTMLElement).style.borderColor = sel ? "#4f46e5" : "transparent";
|
||||
(b as HTMLElement).style.background = sel ? "#4f46e520" : "var(--rs-bg-surface-sunken)";
|
||||
});
|
||||
localStorage.setItem("rmaps_user", JSON.stringify({
|
||||
id: this.participantId, name: this.userName,
|
||||
emoji: this.userEmoji, color: this.userColor,
|
||||
}));
|
||||
});
|
||||
});
|
||||
this.shadow.getElementById("lobby-rejoin-btn")?.addEventListener("click", () => {
|
||||
const lastRoom = localStorage.getItem("rmaps_last_room");
|
||||
if (lastRoom) {
|
||||
// Save name from input if provided
|
||||
if (lobbyNameInput?.value.trim()) {
|
||||
this.userName = lobbyNameInput.value.trim();
|
||||
localStorage.setItem("rmaps_user", JSON.stringify({
|
||||
id: this.participantId, name: this.userName,
|
||||
emoji: this.userEmoji, color: this.userColor,
|
||||
}));
|
||||
}
|
||||
this._history.push("lobby");
|
||||
this._history.push("map", { room: lastRoom });
|
||||
this.joinRoom(lastRoom);
|
||||
}
|
||||
});
|
||||
|
||||
this.shadow.querySelectorAll("[data-room]").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
const room = (el as HTMLElement).dataset.room!;
|
||||
|
|
@ -2780,6 +3075,22 @@ class FolkMapViewer extends HTMLElement {
|
|||
this.toggleLocationSharing();
|
||||
});
|
||||
|
||||
// Header share toggle
|
||||
this.shadow.getElementById("header-share-toggle")?.addEventListener("click", () => {
|
||||
this.toggleLocationSharing();
|
||||
});
|
||||
|
||||
// Header participants toggle
|
||||
this.shadow.getElementById("header-participants-toggle")?.addEventListener("click", () => {
|
||||
// Desktop: toggle sidebar visibility, Mobile: toggle bottom sheet
|
||||
const sidebar = this.shadow.querySelector(".map-sidebar") as HTMLElement;
|
||||
if (sidebar && window.innerWidth > 768) {
|
||||
sidebar.style.display = sidebar.style.display === "none" ? "" : "none";
|
||||
}
|
||||
const sheet = this.shadow.getElementById("mobile-bottom-sheet");
|
||||
if (sheet) sheet.classList.toggle("expanded");
|
||||
});
|
||||
|
||||
this.shadow.getElementById("drop-waypoint")?.addEventListener("click", () => {
|
||||
this.dropWaypoint();
|
||||
});
|
||||
|
|
@ -2865,75 +3176,26 @@ class FolkMapViewer extends HTMLElement {
|
|||
this.fitToParticipants();
|
||||
});
|
||||
|
||||
// Mobile FAB menu
|
||||
this.shadow.getElementById("fab-main")?.addEventListener("click", () => {
|
||||
const main = this.shadow.getElementById("fab-main");
|
||||
const list = this.shadow.getElementById("fab-mini-list");
|
||||
if (main && list) {
|
||||
const isOpen = list.classList.contains("open");
|
||||
list.classList.toggle("open");
|
||||
main.classList.toggle("open");
|
||||
// Close mobile privacy popup when closing FAB
|
||||
if (isOpen) {
|
||||
const popup = this.shadow.getElementById("mobile-privacy-popup");
|
||||
if (popup) popup.style.display = "none";
|
||||
}
|
||||
}
|
||||
// Mobile floating buttons
|
||||
this.shadow.getElementById("mobile-friends-btn")?.addEventListener("click", () => {
|
||||
const sheet = this.shadow.getElementById("mobile-bottom-sheet");
|
||||
if (sheet) sheet.classList.toggle("expanded");
|
||||
});
|
||||
|
||||
this.shadow.getElementById("fab-share-loc")?.addEventListener("click", () => {
|
||||
this.toggleLocationSharing();
|
||||
this.updateMobileFab();
|
||||
});
|
||||
|
||||
this.shadow.getElementById("fab-privacy")?.addEventListener("click", () => {
|
||||
const popup = this.shadow.getElementById("mobile-privacy-popup");
|
||||
if (popup) {
|
||||
const isVisible = popup.style.display !== "none";
|
||||
popup.style.display = isVisible ? "none" : "block";
|
||||
if (!isVisible) this.renderPrivacyPanel(popup);
|
||||
}
|
||||
});
|
||||
|
||||
this.shadow.getElementById("fab-drop-pin")?.addEventListener("click", () => {
|
||||
this.closeMobileFab();
|
||||
this.dropWaypoint();
|
||||
});
|
||||
|
||||
this.shadow.getElementById("fab-share-map")?.addEventListener("click", () => {
|
||||
this.closeMobileFab();
|
||||
this.shadow.getElementById("mobile-qr-btn")?.addEventListener("click", () => {
|
||||
this.showShareModal = true;
|
||||
this.renderShareModal();
|
||||
});
|
||||
|
||||
this.shadow.getElementById("fab-emoji")?.addEventListener("click", () => {
|
||||
this.showEmojiPicker = !this.showEmojiPicker;
|
||||
this.updateEmojiButton();
|
||||
// Floating share-location button on map
|
||||
this.shadow.getElementById("map-share-float")?.addEventListener("click", () => {
|
||||
this.toggleLocationSharing();
|
||||
});
|
||||
|
||||
this.shadow.getElementById("fab-chat")?.addEventListener("click", () => {
|
||||
this.closeMobileFab();
|
||||
// Toggle mobile bottom sheet to chat view
|
||||
const sheet = this.shadow.getElementById("mobile-bottom-sheet");
|
||||
if (sheet) {
|
||||
sheet.classList.add("expanded");
|
||||
const content = this.shadow.getElementById("participant-list-mobile");
|
||||
if (content && !content.querySelector("map-chat-panel")) {
|
||||
this.mountChatPanel(content);
|
||||
}
|
||||
}
|
||||
this.unreadCount = 0;
|
||||
this.updateChatBadge();
|
||||
});
|
||||
|
||||
this.shadow.getElementById("fab-indoor")?.addEventListener("click", () => {
|
||||
this.closeMobileFab();
|
||||
if (this.mapMode === "outdoor") {
|
||||
const event = prompt("c3nav event code (e.g. 39c3):", "39c3");
|
||||
if (event?.trim()) this.switchToIndoor(event.trim());
|
||||
} else {
|
||||
this.switchToOutdoor();
|
||||
}
|
||||
// Sheet close button
|
||||
this.shadow.getElementById("sheet-close-btn")?.addEventListener("click", () => {
|
||||
const s = this.shadow.getElementById("mobile-bottom-sheet");
|
||||
if (s) s.classList.remove("expanded");
|
||||
});
|
||||
|
||||
// Mobile bottom sheet
|
||||
|
|
@ -2974,21 +3236,8 @@ class FolkMapViewer extends HTMLElement {
|
|||
});
|
||||
}
|
||||
|
||||
private closeMobileFab() {
|
||||
const main = this.shadow.getElementById("fab-main");
|
||||
const list = this.shadow.getElementById("fab-mini-list");
|
||||
if (main) main.classList.remove("open");
|
||||
if (list) list.classList.remove("open");
|
||||
const popup = this.shadow.getElementById("mobile-privacy-popup");
|
||||
if (popup) popup.style.display = "none";
|
||||
}
|
||||
|
||||
private updateMobileFab() {
|
||||
const btn = this.shadow.getElementById("fab-share-loc");
|
||||
if (!btn) return;
|
||||
btn.className = `fab-mini-btn ${this.sharingLocation ? "sharing" : ""} ${this.privacySettings.ghostMode ? "ghost" : ""}`;
|
||||
const label = btn.parentElement?.querySelector(".fab-mini-label");
|
||||
if (label) label.textContent = this.privacySettings.ghostMode ? "Ghost" : this.sharingLocation ? "Stop" : "Share";
|
||||
// No-op — legacy FAB removed; share state updated via updateShareButton()
|
||||
}
|
||||
|
||||
private goBack() {
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ routes.get("/", (c) => {
|
|||
body: `<folk-map-viewer space="${space}"></folk-map-viewer>`,
|
||||
scripts: `<link rel="preload" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4.1.2/dist/maplibre-gl.js" as="script">
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4.1.2/dist/maplibre-gl.css" as="style">
|
||||
<script type="module" src="/modules/rmaps/folk-map-viewer.js?v=5"></script>`,
|
||||
<script type="module" src="/modules/rmaps/folk-map-viewer.js?v=6"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rmaps/maps.css?v=3">`,
|
||||
}));
|
||||
});
|
||||
|
|
@ -295,7 +295,7 @@ routes.get("/:room", (c) => {
|
|||
body: `<folk-map-viewer space="${space}" room="${room}"></folk-map-viewer>`,
|
||||
scripts: `<link rel="preload" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4.1.2/dist/maplibre-gl.js" as="script">
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4.1.2/dist/maplibre-gl.css" as="style">
|
||||
<script type="module" src="/modules/rmaps/folk-map-viewer.js?v=5"></script>`,
|
||||
<script type="module" src="/modules/rmaps/folk-map-viewer.js?v=6"></script>`,
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue