From 98c80107e339095a996fe8b86ac04416c706b292 Mon Sep 17 00:00:00 2001 From: awfufu Date: Sun, 28 Dec 2025 23:36:34 +0800 Subject: [PATCH 1/9] feat: migrate user fetch and avatar urls to new backend api endpoints --- src/config/marbleConfig.ts | 2 -- src/data/users.ts | 3 ++- src/utils/marbleFactory.ts | 12 +++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/config/marbleConfig.ts b/src/config/marbleConfig.ts index 0107e1c..f79ed28 100644 --- a/src/config/marbleConfig.ts +++ b/src/config/marbleConfig.ts @@ -56,5 +56,3 @@ export const MARBLE_CONFIG = { enable: true, }, } as const; - -export const AVATAR_BASE_URL = "https://avatar.awfufu.com/qq/"; diff --git a/src/data/users.ts b/src/data/users.ts index 25717d9..28b5d96 100644 --- a/src/data/users.ts +++ b/src/data/users.ts @@ -1,8 +1,9 @@ // User data: Fetch user list from API and store +import { BACKEND_API_BASE } from "../config/loginApiBaseUrl"; import type { UserEntry } from "../config/marbleConfig"; -export const USER_DATA_API = "https://avatar.awfufu.com/users"; +export const USER_DATA_API = `${BACKEND_API_BASE}/users`; // Fetch user data from API export async function fetchUsers(): Promise { diff --git a/src/utils/marbleFactory.ts b/src/utils/marbleFactory.ts index 1928cb7..2d642e5 100644 --- a/src/utils/marbleFactory.ts +++ b/src/utils/marbleFactory.ts @@ -1,7 +1,8 @@ // Marble factory: Responsible for creating and initializing marble instances +import { BACKEND_API_BASE } from "../config/loginApiBaseUrl"; import type { UserEntry } from "../config/marbleConfig"; -import { AVATAR_BASE_URL, MARBLE_CONFIG } from "../config/marbleConfig"; +import { MARBLE_CONFIG } from "../config/marbleConfig"; import type { Marble } from "./mouseInteraction"; export class MarbleFactory { @@ -28,7 +29,7 @@ export class MarbleFactory { // Generate avatar URL private getAvatarUrl(id: string): string { - return `${AVATAR_BASE_URL}${id}`; + return `${BACKEND_API_BASE}/user/avatar/${id}`; } // Create marble DOM node wrapper @@ -94,7 +95,7 @@ export class MarbleFactory { try { await img.decode(); - } catch (e) { + } catch (_e) { throw new Error(`Failed to load image: ${url}`); } @@ -104,8 +105,9 @@ export class MarbleFactory { const physics = this.generateRandomPhysics(radius); // Pre-set position to avoid flashing at 0,0 - wrapper.style.transform = `translate(${physics.x - radius}px, ${physics.y - radius - }px)`; + wrapper.style.transform = `translate(${physics.x - radius}px, ${ + physics.y - radius + }px)`; const { massScale, massOffset } = MARBLE_CONFIG.physics; const marble: Marble = { From 45c865cef29c39414564db1bbaa62deca965aa16 Mon Sep 17 00:00:00 2001 From: awfufu Date: Mon, 29 Dec 2025 00:16:45 +0800 Subject: [PATCH 2/9] feat: add avatar upload button to dashboard --- src/components/CentralIsland.astro | 31 ++++- .../central-island/DashboardView.astro | 107 +++++++++++++++++- src/i18n/ui.ts | 4 + 3 files changed, 137 insertions(+), 5 deletions(-) diff --git a/src/components/CentralIsland.astro b/src/components/CentralIsland.astro index 6622cb2..f661922 100644 --- a/src/components/CentralIsland.astro +++ b/src/components/CentralIsland.astro @@ -45,6 +45,7 @@ const { titleSvg } = Astro.props; // Actually, let's keep state here. let isLoggedIn = false; let currentQQ = ""; + let currentUser = ""; let isPolling = false; // DOM Helpers (Dynamic) @@ -128,7 +129,21 @@ const { titleSvg } = Astro.props; function updateDashboard() { const displays = getDisplays(); if (displays.dashAvatar) { - if (currentQQ) { + if (currentUser) { + displays.dashAvatar.src = `${BACKEND_API_BASE}/user/avatar/${currentUser}`; + // Add onerror fallback to QQ or default + displays.dashAvatar.onerror = () => { + if (currentQQ) { + displays.dashAvatar.src = `https://q.qlogo.cn/headimg_dl?dst_uin=${currentQQ}&spec=640&img_type=jpg`; + } else { + displays.dashAvatar.src = + "https://ui-avatars.com/api/?name=" + + (currentUser || "User") + + "&background=random"; + } + displays.dashAvatar.onerror = null; // Prevent infinite loop + }; + } else if (currentQQ) { displays.dashAvatar.src = `https://q.qlogo.cn/headimg_dl?dst_uin=${currentQQ}&spec=640&img_type=jpg`; } else { displays.dashAvatar.src = @@ -139,6 +154,18 @@ const { titleSvg } = Astro.props; function handleLoginSuccess(token: string, qq: string) { localStorage.setItem("token", token); + + // Extract username from token + try { + const payload = JSON.parse(atob(token.split(".")[1])); + if (payload.user_id) { + currentUser = payload.user_id; + localStorage.setItem("username", currentUser); + } + } catch (e) { + console.error("Failed to decode token", e); + } + if (qq) { localStorage.setItem("qq", String(qq)); currentQQ = String(qq); @@ -332,8 +359,10 @@ const { titleSvg } = Astro.props; function checkLoginState() { const token = localStorage.getItem("token"); const storedQQ = localStorage.getItem("qq"); + const storedUser = localStorage.getItem("username"); isLoggedIn = !!token; if (storedQQ) currentQQ = storedQQ; + if (storedUser) currentUser = storedUser; if (isLoggedIn) { updateDashboard(); diff --git a/src/components/central-island/DashboardView.astro b/src/components/central-island/DashboardView.astro index a107d74..1bf33f8 100644 --- a/src/components/central-island/DashboardView.astro +++ b/src/components/central-island/DashboardView.astro @@ -25,11 +25,110 @@ const t = getTranslations(lang);
User Avatar
+ + + +

{t("dashboard.logged_in")}

{t("dashboard.instruction")}

+ + diff --git a/src/components/UserDropdown.astro b/src/components/UserDropdown.astro index d5c6f7d..059d919 100644 --- a/src/components/UserDropdown.astro +++ b/src/components/UserDropdown.astro @@ -41,31 +41,43 @@ const t = getTranslations(lang); async function updateAvatar() { let qq = localStorage.getItem("qq"); + let avatar = localStorage.getItem("avatar"); const token = localStorage.getItem("token"); if (token) { - if (!qq) { - try { - // Fetch user info from backend if token exists but QQ is missing - const res = await fetch(`${BACKEND_API_BASE}/me`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (res.ok) { - const data = await res.json(); - if (data.qq) { - qq = String(data.qq); - localStorage.setItem("qq", qq); - } + // 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); } + } catch (e) { + console.error("Failed to fetch user info", e); } - if (qq) { - avatarImg.src = `https://q.qlogo.cn/headimg_dl?dst_uin=${qq}&spec=640&img_type=jpg`; + if (avatar && /^[0-9a-f]{16}$/.test(avatar)) { + avatarImg.src = `${BACKEND_API_BASE}/user/avatar/${avatar}`; + avatarImg.onerror = () => { + avatarImg.src = + "https://ui-avatars.com/api/?name=User&background=random"; + avatarImg.onerror = null; + }; } else { - // Default avatar or placeholder if QQ is missing and fetch failed + // Default avatar or placeholder avatarImg.src = "https://ui-avatars.com/api/?name=User&background=random"; } @@ -101,6 +113,7 @@ const t = getTranslations(lang); logoutBtn.addEventListener("click", () => { localStorage.removeItem("token"); localStorage.removeItem("qq"); + localStorage.removeItem("avatar"); userDropdown.classList.add("hidden"); dropdownMenu.classList.remove("active"); // Notify other components if necessary, e.g. show login button again From 9275c4dabd0e147712af01bb18d8f78ad9d1a4e9 Mon Sep 17 00:00:00 2001 From: awfufu Date: Mon, 29 Dec 2025 13:38:54 +0800 Subject: [PATCH 5/9] feat: optimize avatar update logic to perform instant refresh using returned hash --- src/components/MainView.astro | 35 ++++++++- src/components/UserDropdown.astro | 72 +++++++++++-------- .../central-island/DashboardView.astro | 14 ++-- 3 files changed, 86 insertions(+), 35 deletions(-) diff --git a/src/components/MainView.astro b/src/components/MainView.astro index ec01b46..8b58731 100644 --- a/src/components/MainView.astro +++ b/src/components/MainView.astro @@ -20,16 +20,49 @@ import Particles from "./Particles.astro"; diff --git a/src/i18n/ui.ts b/src/i18n/ui.ts index 0791461..081b468 100644 --- a/src/i18n/ui.ts +++ b/src/i18n/ui.ts @@ -62,7 +62,11 @@ export const ui = { "settings.motion": "Motion", "settings.orientation": "Orientation", "settings.background": "Background", + "common.cancel": "Cancel", + "common.confirm": "Confirm", "user.logout": "Logout", + "common.loading": "Loading...", + "common.processing": "Processing...", }, "zh-cn": { "site.title": "Lolisland", @@ -118,6 +122,10 @@ export const ui = { "settings.orientation": "方向感应", "settings.background": "背景", "user.logout": "退出登录", + "common.cancel": "取消", + "common.confirm": "确认", + "common.loading": "加载中...", + "common.processing": "处理中...", }, "zh-hk": { "site.title": "Lolisland", @@ -173,6 +181,10 @@ export const ui = { "settings.orientation": "方向感應", "settings.background": "背景", "user.logout": "登出", + "common.cancel": "取消", + "common.confirm": "確認", + "common.loading": "載入中...", + "common.processing": "處理中...", }, ja: { "site.title": "Lolisland", @@ -229,5 +241,9 @@ export const ui = { "settings.orientation": "オリエンテーション", "settings.background": "背景", "user.logout": "ログアウト", + "common.cancel": "キャンセル", + "common.confirm": "確認", + "common.loading": "読み込み中...", + "common.processing": "処理中...", }, } as const; From e985c0ee252cef70c91e282dff1d9ef35e0847e1 Mon Sep 17 00:00:00 2001 From: awfufu Date: Mon, 29 Dec 2025 15:35:15 +0800 Subject: [PATCH 7/9] fix: restore settings menu functionality for marble zoom and title card toggle --- src/components/MainView.astro | 52 +++++++++++++++++-------------- src/components/SettingsMenu.astro | 41 ++++++++++-------------- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/components/MainView.astro b/src/components/MainView.astro index 8b58731..f2ce01b 100644 --- a/src/components/MainView.astro +++ b/src/components/MainView.astro @@ -6,17 +6,17 @@ import Navbar from "./Navbar.astro"; import Particles from "./Particles.astro"; --- - +
- +
-