feat: optimize avatar update logic to perform instant refresh using returned hash

This commit is contained in:
2025-12-29 13:38:54 +08:00
parent 9875565bd7
commit 9275c4dabd
3 changed files with 86 additions and 35 deletions

View File

@@ -20,16 +20,49 @@ import Particles from "./Particles.astro";
</div>
<script>
import { BACKEND_API_BASE } from "../config/loginApiBaseUrl";
import { fetchUsers } from "../data/users";
import { MarbleSystem } from "../utils/marbleSystem";
declare global {
interface Window {
hasInitializedMarbleSystem: boolean;
marbleSystemInstance: unknown;
marbleSystemInstance: MarbleSystem; // Better typing if possible, or keep unknown and cast
}
}
// Marble Update Listener
window.addEventListener("login-success", () => {
const sys = window.marbleSystemInstance;
if (!sys) return;
const currentUsername = localStorage.getItem("username");
const newAvatar = localStorage.getItem("avatar");
if (currentUsername && newAvatar && /^[0-9a-f]{16}$/.test(newAvatar)) {
// Find marble by label (username)
// We have to access private marbles or use getMarbles() if available
// MarbleSystem has getMarbles() returning ReadonlyArray<Marble>
const marbles = sys.getMarbles();
const userMarble = marbles.find((m) => {
// We need to find the label element text
const label = m.node.querySelector(".marble-label");
return label && label.textContent === currentUsername;
});
if (userMarble) {
const innerMarble = userMarble.node.querySelector(
".marble",
) as HTMLElement;
if (innerMarble) {
innerMarble.style.backgroundImage = `url("${BACKEND_API_BASE}/user/avatar/${newAvatar}")`;
// Also update the id if the system relies on it for something else
userMarble.id = newAvatar;
}
}
}
});
// Prevent re-initialization on View Transitions
if (!window.hasInitializedMarbleSystem) {
window.hasInitializedMarbleSystem = true;

View File

@@ -40,46 +40,33 @@ const t = getTranslations(lang);
) as HTMLButtonElement;
async function updateAvatar() {
let qq = localStorage.getItem("qq");
let avatar = localStorage.getItem("avatar");
renderAvatar();
await refreshUserInfo();
renderAvatar();
}
function renderAvatar() {
const avatar = localStorage.getItem("avatar");
const token = localStorage.getItem("token");
if (token) {
// Sync user info
try {
// Fetch user info from backend if token exists
const res = await fetch(`${BACKEND_API_BASE}/me`, {
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) {
const data = (await res.json()) as {
qq?: string;
avatar?: string;
};
if (data.qq) {
qq = String(data.qq);
localStorage.setItem("qq", qq);
}
if (data.avatar) {
avatar = String(data.avatar);
localStorage.setItem("avatar", avatar);
}
}
} catch (e) {
console.error("Failed to fetch user info", e);
}
if (avatar && /^[0-9a-f]{16}$/.test(avatar)) {
avatarImg.src = `${BACKEND_API_BASE}/user/avatar/${avatar}`;
// Basic update
const newSrc = `${BACKEND_API_BASE}/user/avatar/${avatar}`;
if (avatarImg.src !== newSrc) {
avatarImg.src = newSrc;
}
avatarImg.onerror = () => {
avatarImg.src =
"https://ui-avatars.com/api/?name=User&background=random";
avatarImg.onerror = null;
};
} else {
// Default avatar or placeholder
avatarImg.src =
"https://ui-avatars.com/api/?name=User&background=random";
// Default if no valid avatar in LS
if (!avatarImg.src.includes("ui-avatars.com")) {
avatarImg.src =
"https://ui-avatars.com/api/?name=User&background=random";
}
}
userDropdown.classList.remove("hidden");
} else {
@@ -87,6 +74,31 @@ const t = getTranslations(lang);
}
}
async function refreshUserInfo() {
const token = localStorage.getItem("token");
if (!token) return;
try {
const res = await fetch(`${BACKEND_API_BASE}/me`, {
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) {
const data = (await res.json()) as {
qq?: string;
avatar?: string;
};
if (data.qq) {
localStorage.setItem("qq", String(data.qq));
}
if (data.avatar) {
localStorage.setItem("avatar", String(data.avatar));
}
}
} catch (e) {
console.error("Failed to fetch user info", e);
}
}
// Initial check
updateAvatar();

View File

@@ -80,11 +80,17 @@ const t = getTranslations(lang);
});
if (res.ok) {
// Update image source to force reload
if (img) {
const currentSrc = img.src.split("?")[0];
img.src = `${currentSrc}?t=${Date.now()}`;
const { key } = (await res.json()) as { key: string };
if (key) {
localStorage.setItem("avatar", key);
// Update image source directly with new hash
if (img) {
img.src = `${BACKEND_API_BASE}/user/avatar/${key}`;
}
}
// Dispatch event for other components (like navbar) if they listen
window.dispatchEvent(new CustomEvent("login-success"));
} else {