From 516bdf51862843c012d56f7903fbe09dbb97be98 Mon Sep 17 00:00:00 2001 From: olcxja Date: Fri, 19 Jun 2026 20:04:23 +0200 Subject: [PATCH] Add profile context menu & test profile popup --- .../src/main/assets/public/blah/en-cat.json | 7 +- .../src/main/assets/public/blah/en-us.json | 7 +- android/app/src/main/assets/public/index.html | 12 +- android/app/src/main/assets/public/main.js | 108 +++++++++++- android/app/src/main/assets/public/screens.js | 14 ++ android/app/src/main/assets/public/style.css | 160 +++++++++++++++++- webroot/blah/en-cat.json | 7 +- webroot/blah/en-us.json | 7 +- webroot/index.html | 12 +- webroot/main.js | 108 +++++++++++- webroot/screens.js | 14 ++ webroot/style.css | 160 +++++++++++++++++- 12 files changed, 602 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/assets/public/blah/en-cat.json b/android/app/src/main/assets/public/blah/en-cat.json index 3317c7c7..01daccfb 100644 --- a/android/app/src/main/assets/public/blah/en-cat.json +++ b/android/app/src/main/assets/public/blah/en-cat.json @@ -139,5 +139,10 @@ "larp.redacted": "this meow was hissed away.", "placeholder.invitation.code": "invitation code meow", "info.messages.loading.older": "Loading older meowsages...", - "error:message.not.found": "Meowsage not found" + "error:message.not.found": "Meowsage not found", + "title.profile": "cat profile", + "title.settings": "cat settings", + "title.logout": "log out", + "action.edit.profile": "edit cat profile", + "action.open.dm": "open direct meowchat" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json index 4e52514b..7b26bfb5 100644 --- a/android/app/src/main/assets/public/blah/en-us.json +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -139,5 +139,10 @@ "larp.redacted": "This message was deleted.", "placeholder.invitation.code": "invitation code", "info.messages.loading.older": "Loading older messages...", - "error:message.not.found": "Message not found" + "error:message.not.found": "Message not found", + "title.profile": "Profile", + "title.settings": "Settings", + "title.logout": "Log out", + "action.edit.profile": "Edit profile", + "action.open.dm": "Open DM" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/index.html b/android/app/src/main/assets/public/index.html index 21ec1198..1aa1684a 100644 --- a/android/app/src/main/assets/public/index.html +++ b/android/app/src/main/assets/public/index.html @@ -49,7 +49,7 @@ - @@ -79,6 +79,16 @@
+ + diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index 3884c14f..3c769c62 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -1411,6 +1411,10 @@ if (App) { } function popstate() { + if (isProfileOpen) { + closeProfile(); + return; + } if (!mainScreen.classList.contains('mobile-content') && !mainScreen.classList.contains('mobile-details')) { history.back(); if (App) { @@ -1421,8 +1425,108 @@ function popstate() { } -function gotoSideProfilePopup() { +sidebarProfileButton.addEventListener("click", (e) => { + e.stopPropagation(); + history.pushState({trap: true}, "", location.href); + + if (fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + setActiveSidebarIndicator(currentMainIndicator); + } else { + setActiveSidebarIndicator(sidebarProfileIndicator, true); + const rect = sidebarProfileButton.getBoundingClientRect(); + showFixedContextMenu({top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left}, profileContextMenu); + } +}); + +function logout() { + localStorage.clear(); + window.location.href = "login/index.html"; +} + +function gotoSettings() { + if (fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + setActiveSidebarIndicator(currentMainIndicator); + } +} + +let isProfileOpen = false; + +async function openProfile(accountId) { + if (fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + setActiveSidebarIndicator(currentMainIndicator); + } + + let popupContainer = document.getElementById("profile-popup-container"); + let popupInner = document.getElementById("profile-popup-inner"); + + //show loading + popupInner.innerHTML = '

Loading profile...

'; + popupContainer.style.display = "flex"; + + //force reflow + popupContainer.offsetHeight; + + popupContainer.classList.add("show"); + isProfileOpen = true; + + try { + if (!accountId) accountId = window.id; //var fallback + let nameWithHost = await fetchAsync(`${url}/idtoname?id=${accountId}`); + if (nameWithHost.startsWith("error:")) nameWithHost = accountId; //api fallback + + let nameParts = nameWithHost.split(":"); + let profileName = nameParts[0] || accountId; + let profileHost = nameParts.slice(1).join(":") || host; + + let pfpUrl = await getAvatarUrl(accountId, nameWithHost); + + let bannerUrl = `${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.banner`; + let bannerHtml = ``; + + let bio = await fetchAsync(`${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.bio`); + if (bio.startsWith("error:")) bio = ""; + + let bioHtml = bio && bio.trim() !== "" ? `
${bio.replace(//g, ">")}
` : ""; + + let isOwnProfile = accountId === id; + let actionBtnHtml = isOwnProfile ? + `` : + ``; + + popupInner.innerHTML = ` + ${bannerHtml} +
+ +
+
+
+
${profileName}
+
${profileName}:${profileHost} (${accountId})
+
+ ${bioHtml} + ${actionBtnHtml} +
+ `; + } catch (e) { + popupInner.innerHTML = '

Failed to load profile.

'; + console.error(e); + } +} + +function closeProfile() { + let popupContainer = document.getElementById("profile-popup-container"); + popupContainer.classList.remove("show"); + isProfileOpen = false; + setTimeout(() => { + popupContainer.style.display = "none"; + }, 300); } function setActiveRoombarItem(itemId) { @@ -1820,7 +1924,7 @@ async function renderDms(res) { let dms = parsed.dms || {}; let dmEntries = Object.entries(dms); - // Sort by timestamp descending + //Sort by timestamp descending dmEntries.sort((a, b) => (parseInt(b[1].string2) || 0) - (parseInt(a[1].string2) || 0)); let html = ""; diff --git a/android/app/src/main/assets/public/screens.js b/android/app/src/main/assets/public/screens.js index 1a15ab27..d553490b 100644 --- a/android/app/src/main/assets/public/screens.js +++ b/android/app/src/main/assets/public/screens.js @@ -217,6 +217,20 @@ var inboxRoomBar = ` //context menus +var profileContextMenu = ` + + + +`; var addSpaceMenu = ` @@ -79,6 +79,16 @@
+ + diff --git a/webroot/main.js b/webroot/main.js index 3884c14f..3c769c62 100644 --- a/webroot/main.js +++ b/webroot/main.js @@ -1411,6 +1411,10 @@ if (App) { } function popstate() { + if (isProfileOpen) { + closeProfile(); + return; + } if (!mainScreen.classList.contains('mobile-content') && !mainScreen.classList.contains('mobile-details')) { history.back(); if (App) { @@ -1421,8 +1425,108 @@ function popstate() { } -function gotoSideProfilePopup() { +sidebarProfileButton.addEventListener("click", (e) => { + e.stopPropagation(); + history.pushState({trap: true}, "", location.href); + + if (fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + setActiveSidebarIndicator(currentMainIndicator); + } else { + setActiveSidebarIndicator(sidebarProfileIndicator, true); + const rect = sidebarProfileButton.getBoundingClientRect(); + showFixedContextMenu({top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left}, profileContextMenu); + } +}); + +function logout() { + localStorage.clear(); + window.location.href = "login/index.html"; +} + +function gotoSettings() { + if (fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + setActiveSidebarIndicator(currentMainIndicator); + } +} + +let isProfileOpen = false; + +async function openProfile(accountId) { + if (fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + setActiveSidebarIndicator(currentMainIndicator); + } + + let popupContainer = document.getElementById("profile-popup-container"); + let popupInner = document.getElementById("profile-popup-inner"); + + //show loading + popupInner.innerHTML = '

Loading profile...

'; + popupContainer.style.display = "flex"; + + //force reflow + popupContainer.offsetHeight; + + popupContainer.classList.add("show"); + isProfileOpen = true; + + try { + if (!accountId) accountId = window.id; //var fallback + let nameWithHost = await fetchAsync(`${url}/idtoname?id=${accountId}`); + if (nameWithHost.startsWith("error:")) nameWithHost = accountId; //api fallback + + let nameParts = nameWithHost.split(":"); + let profileName = nameParts[0] || accountId; + let profileHost = nameParts.slice(1).join(":") || host; + + let pfpUrl = await getAvatarUrl(accountId, nameWithHost); + + let bannerUrl = `${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.banner`; + let bannerHtml = ``; + + let bio = await fetchAsync(`${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.bio`); + if (bio.startsWith("error:")) bio = ""; + + let bioHtml = bio && bio.trim() !== "" ? `
${bio.replace(//g, ">")}
` : ""; + + let isOwnProfile = accountId === id; + let actionBtnHtml = isOwnProfile ? + `` : + ``; + + popupInner.innerHTML = ` + ${bannerHtml} +
+ +
+
+
+
${profileName}
+
${profileName}:${profileHost} (${accountId})
+
+ ${bioHtml} + ${actionBtnHtml} +
+ `; + } catch (e) { + popupInner.innerHTML = '

Failed to load profile.

'; + console.error(e); + } +} + +function closeProfile() { + let popupContainer = document.getElementById("profile-popup-container"); + popupContainer.classList.remove("show"); + isProfileOpen = false; + setTimeout(() => { + popupContainer.style.display = "none"; + }, 300); } function setActiveRoombarItem(itemId) { @@ -1820,7 +1924,7 @@ async function renderDms(res) { let dms = parsed.dms || {}; let dmEntries = Object.entries(dms); - // Sort by timestamp descending + //Sort by timestamp descending dmEntries.sort((a, b) => (parseInt(b[1].string2) || 0) - (parseInt(a[1].string2) || 0)); let html = ""; diff --git a/webroot/screens.js b/webroot/screens.js index 1a15ab27..d553490b 100644 --- a/webroot/screens.js +++ b/webroot/screens.js @@ -217,6 +217,20 @@ var inboxRoomBar = ` //context menus +var profileContextMenu = ` + + + +`; var addSpaceMenu = `