From 94ebf94abce6c7259ca45da02595a509959b1688 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 12 Nov 2025 07:21:28 -0300 Subject: [PATCH 1/5] fix: use local achievement cache for unlocked achievement count --- src/main/events/library/get-library.ts | 12 +++++++++- src/renderer/src/pages/library/library.tsx | 27 +++++++--------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/events/library/get-library.ts b/src/main/events/library/get-library.ts index c434f6d3..9fb3416b 100644 --- a/src/main/events/library/get-library.ts +++ b/src/main/events/library/get-library.ts @@ -2,6 +2,7 @@ import type { LibraryGame } from "@types"; import { registerEvent } from "../register-event"; import { downloadsSublevel, + gameAchievementsSublevel, gamesShopAssetsSublevel, gamesSublevel, } from "@main/level"; @@ -18,11 +19,20 @@ const getLibrary = async (): Promise => { const download = await downloadsSublevel.get(key); const gameAssets = await gamesShopAssetsSublevel.get(key); + let unlockedAchievementCount = game.unlockedAchievementCount ?? 0; + + if (!game.unlockedAchievementCount) { + const achievements = await gameAchievementsSublevel.get(key); + + unlockedAchievementCount = + achievements?.unlockedAchievements.length ?? 0; + } + return { id: key, ...game, download: download ?? null, - unlockedAchievementCount: game.unlockedAchievementCount ?? 0, + unlockedAchievementCount, achievementCount: game.achievementCount ?? 0, // Spread gameAssets last to ensure all image URLs are properly set ...gameAssets, diff --git a/src/renderer/src/pages/library/library.tsx b/src/renderer/src/pages/library/library.tsx index 8b377f63..0efe8fb2 100644 --- a/src/renderer/src/pages/library/library.tsx +++ b/src/renderer/src/pages/library/library.tsx @@ -14,10 +14,6 @@ import "./library.scss"; export default function Library() { const { library, updateLibrary } = useLibrary(); - type ElectronAPI = { - refreshLibraryAssets?: () => Promise; - onLibraryBatchComplete?: (cb: () => void) => () => void; - }; const [viewMode, setViewMode] = useState(() => { const savedViewMode = localStorage.getItem("library-view-mode"); @@ -41,22 +37,15 @@ export default function Library() { useEffect(() => { dispatch(setHeaderTitle(t("library"))); - const electron = (globalThis as unknown as { electron?: ElectronAPI }) - .electron; - let unsubscribe: () => void = () => undefined; - if (electron?.refreshLibraryAssets) { - electron - .refreshLibraryAssets() - .then(() => updateLibrary()) - .catch(() => updateLibrary()); - if (electron.onLibraryBatchComplete) { - unsubscribe = electron.onLibraryBatchComplete(() => { - updateLibrary(); - }); - } - } else { + + const unsubscribe = window.electron.onLibraryBatchComplete(() => { updateLibrary(); - } + }); + + window.electron + .refreshLibraryAssets() + .then(() => updateLibrary()) + .catch(() => updateLibrary()); return () => { unsubscribe(); From f84917a00b7d1498648b864518e2a817893530fe Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:07:51 -0300 Subject: [PATCH 2/5] feat: get user static image on notifications --- src/main/events/profile/process-profile-image.ts | 8 ++++++-- src/main/services/notifications/index.ts | 12 ++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/events/profile/process-profile-image.ts b/src/main/events/profile/process-profile-image.ts index 6166f7f8..9407f0a2 100644 --- a/src/main/events/profile/process-profile-image.ts +++ b/src/main/events/profile/process-profile-image.ts @@ -1,10 +1,14 @@ import { registerEvent } from "../register-event"; import { PythonRPC } from "@main/services/python-rpc"; -const processProfileImage = async ( +const processProfileImageEvent = async ( _event: Electron.IpcMainInvokeEvent, path: string ) => { + return processProfileImage(path); +}; + +export const processProfileImage = async (path: string) => { return PythonRPC.rpc .post<{ imagePath: string; @@ -13,4 +17,4 @@ const processProfileImage = async ( .then((response) => response.data); }; -registerEvent("processProfileImage", processProfileImage); +registerEvent("processProfileImage", processProfileImageEvent); diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index b8ff480c..6ad93ea7 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -15,6 +15,13 @@ import { db, levelKeys, themesSublevel } from "@main/level"; import { restartAndInstallUpdate } from "@main/events/autoupdater/restart-and-install-update"; import { SystemPath } from "../system-path"; import { getThemeSoundPath } from "@main/helpers"; +import { processProfileImage } from "@main/events/profile/process-profile-image"; + +const getStaticImage = async (path: string) => { + return processProfileImage(path) + .then((response) => response.imagePath) + .catch(() => path); +}; async function downloadImage(url: string | null) { if (!url) return undefined; @@ -31,8 +38,9 @@ async function downloadImage(url: string | null) { response.data.pipe(writer); return new Promise((resolve) => { - writer.on("finish", () => { - resolve(outputPath); + writer.on("finish", async () => { + const staticImagePath = await getStaticImage(outputPath); + resolve(staticImagePath); }); writer.on("error", () => { logger.error("Failed to download image", { url }); From c2216bbf95fe5899f87e6a60514994a11cd591c1 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:17:53 -0300 Subject: [PATCH 3/5] feat: use jpg for system notifications --- python_rpc/main.py | 5 ++++- python_rpc/profile_image_processor.py | 8 ++++---- src/main/events/profile/process-profile-image.ts | 6 +++--- src/main/services/notifications/index.ts | 2 +- src/main/services/ws/events/friend-request.ts | 8 +++++--- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/python_rpc/main.py b/python_rpc/main.py index 36170025..99dd0d8c 100644 --- a/python_rpc/main.py +++ b/python_rpc/main.py @@ -153,8 +153,11 @@ def profile_image(): data = request.get_json() image_path = data.get('image_path') + # use webp as default value for target_extension + target_extension = data.get('target_extension') or 'webp' + try: - processed_image_path, mime_type = ProfileImageProcessor.process_image(image_path) + processed_image_path, mime_type = ProfileImageProcessor.process_image(image_path, target_extension) return jsonify({'imagePath': processed_image_path, 'mimeType': mime_type}), 200 except Exception as e: return jsonify({"error": str(e)}), 400 diff --git a/python_rpc/profile_image_processor.py b/python_rpc/profile_image_processor.py index 45ba5160..eac8c32a 100644 --- a/python_rpc/profile_image_processor.py +++ b/python_rpc/profile_image_processor.py @@ -4,7 +4,7 @@ import os, uuid, tempfile class ProfileImageProcessor: @staticmethod - def get_parsed_image_data(image_path): + def get_parsed_image_data(image_path, target_extension): Image.MAX_IMAGE_PIXELS = 933120000 image = Image.open(image_path) @@ -16,7 +16,7 @@ class ProfileImageProcessor: return image_path, mime_type else: new_uuid = str(uuid.uuid4()) - new_image_path = os.path.join(tempfile.gettempdir(), new_uuid) + ".webp" + new_image_path = os.path.join(tempfile.gettempdir(), new_uuid) + "." + target_extension image.save(new_image_path) new_image = Image.open(new_image_path) @@ -26,5 +26,5 @@ class ProfileImageProcessor: @staticmethod - def process_image(image_path): - return ProfileImageProcessor.get_parsed_image_data(image_path) + def process_image(image_path, target_extension): + return ProfileImageProcessor.get_parsed_image_data(image_path, target_extension) diff --git a/src/main/events/profile/process-profile-image.ts b/src/main/events/profile/process-profile-image.ts index 9407f0a2..bec17cb6 100644 --- a/src/main/events/profile/process-profile-image.ts +++ b/src/main/events/profile/process-profile-image.ts @@ -5,15 +5,15 @@ const processProfileImageEvent = async ( _event: Electron.IpcMainInvokeEvent, path: string ) => { - return processProfileImage(path); + return processProfileImage(path, "webp"); }; -export const processProfileImage = async (path: string) => { +export const processProfileImage = async (path: string, extension?: string) => { return PythonRPC.rpc .post<{ imagePath: string; mimeType: string; - }>("/profile-image", { image_path: path }) + }>("/profile-image", { image_path: path, target_extension: extension }) .then((response) => response.data); }; diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index 6ad93ea7..a925e7c7 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -18,7 +18,7 @@ import { getThemeSoundPath } from "@main/helpers"; import { processProfileImage } from "@main/events/profile/process-profile-image"; const getStaticImage = async (path: string) => { - return processProfileImage(path) + return processProfileImage(path, "jpg") .then((response) => response.imagePath) .catch(() => path); }; diff --git a/src/main/services/ws/events/friend-request.ts b/src/main/services/ws/events/friend-request.ts index 8faa38a5..efee370d 100644 --- a/src/main/services/ws/events/friend-request.ts +++ b/src/main/services/ws/events/friend-request.ts @@ -8,9 +8,11 @@ export const friendRequestEvent = async (payload: FriendRequest) => { friendRequestCount: payload.friendRequestCount, }); - const user = await HydraApi.get(`/users/${payload.senderId}`); + if (payload.senderId) { + const user = await HydraApi.get(`/users/${payload.senderId}`); - if (user) { - publishNewFriendRequestNotification(user); + if (user) { + publishNewFriendRequestNotification(user); + } } }; From 20c0d3174b7f50396bcdcec681abb717c898e333 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:37:44 -0300 Subject: [PATCH 4/5] test --- src/renderer/src/hooks/use-library.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/hooks/use-library.ts b/src/renderer/src/hooks/use-library.ts index f7310df0..0edd29ba 100644 --- a/src/renderer/src/hooks/use-library.ts +++ b/src/renderer/src/hooks/use-library.ts @@ -7,9 +7,25 @@ export function useLibrary() { const library = useAppSelector((state) => state.library.value); const updateLibrary = useCallback(async () => { - return window.electron - .getLibrary() - .then((updatedLibrary) => dispatch(setLibrary(updatedLibrary))); + return window.electron.getLibrary().then(async (updatedLibrary) => { + const libraryWithAchievements = await Promise.all( + updatedLibrary.map(async (game) => { + const unlockedAchievements = + await window.electron.getUnlockedAchievements( + game.objectId, + game.shop + ); + + return { + ...game, + unlockedAchievementCount: + game.unlockedAchievementCount || unlockedAchievements.length, + }; + }) + ); + + dispatch(setLibrary(libraryWithAchievements)); + }); }, [dispatch]); return { library, updateLibrary }; From f594cd298afd470f76df5bf5f6d032e936ccc9e6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:52:38 -0300 Subject: [PATCH 5/5] fix: performance --- src/renderer/src/hooks/use-library.ts | 22 +++------------- .../pages/library/library-game-card-large.tsx | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/renderer/src/hooks/use-library.ts b/src/renderer/src/hooks/use-library.ts index 0edd29ba..f7310df0 100644 --- a/src/renderer/src/hooks/use-library.ts +++ b/src/renderer/src/hooks/use-library.ts @@ -7,25 +7,9 @@ export function useLibrary() { const library = useAppSelector((state) => state.library.value); const updateLibrary = useCallback(async () => { - return window.electron.getLibrary().then(async (updatedLibrary) => { - const libraryWithAchievements = await Promise.all( - updatedLibrary.map(async (game) => { - const unlockedAchievements = - await window.electron.getUnlockedAchievements( - game.objectId, - game.shop - ); - - return { - ...game, - unlockedAchievementCount: - game.unlockedAchievementCount || unlockedAchievements.length, - }; - }) - ); - - dispatch(setLibrary(libraryWithAchievements)); - }); + return window.electron + .getLibrary() + .then((updatedLibrary) => dispatch(setLibrary(updatedLibrary))); }, [dispatch]); return { library, updateLibrary }; diff --git a/src/renderer/src/pages/library/library-game-card-large.tsx b/src/renderer/src/pages/library/library-game-card-large.tsx index 292290ca..9aba4b08 100644 --- a/src/renderer/src/pages/library/library-game-card-large.tsx +++ b/src/renderer/src/pages/library/library-game-card-large.tsx @@ -1,7 +1,7 @@ import { LibraryGame } from "@types"; import { useGameCard } from "@renderer/hooks"; import { ClockIcon, AlertFillIcon, TrophyIcon } from "@primer/octicons-react"; -import { memo, useMemo } from "react"; +import { memo, useEffect, useMemo, useState } from "react"; import "./library-game-card-large.scss"; interface LibraryGameCardLargeProps { @@ -48,6 +48,20 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({ ] ); + const [unlockedAchievementsCount, setUnlockedAchievementsCount] = useState( + game.unlockedAchievementCount ?? 0 + ); + + useEffect(() => { + if (game.unlockedAchievementCount) return; + + window.electron + .getUnlockedAchievements(game.objectId, game.shop) + .then((achievements) => { + setUnlockedAchievementsCount(achievements.length); + }); + }, [game]); + const backgroundStyle = useMemo( () => backgroundImage ? { backgroundImage: `url(${backgroundImage})` } : {}, @@ -56,9 +70,9 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({ const achievementBarStyle = useMemo( () => ({ - width: `${((game.unlockedAchievementCount ?? 0) / (game.achievementCount ?? 1)) * 100}%`, + width: `${(unlockedAchievementsCount / (game.achievementCount ?? 1)) * 100}%`, }), - [game.unlockedAchievementCount, game.achievementCount] + [unlockedAchievementsCount, game.achievementCount] ); const logoImage = game.customLogoImageUrl ?? game.logoImageUrl; @@ -116,14 +130,12 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({ className="library-game-card-large__achievement-trophy" /> - {game.unlockedAchievementCount ?? 0} /{" "} - {game.achievementCount ?? 0} + {unlockedAchievementsCount} / {game.achievementCount ?? 0} {Math.round( - ((game.unlockedAchievementCount ?? 0) / - (game.achievementCount ?? 1)) * + (unlockedAchievementsCount / (game.achievementCount ?? 1)) * 100 )} %