diff --git a/package.json b/package.json index 8ce99075..312ca662 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,9 @@ "react-dom": "^18.2.0", "sass-embedded": "^1.80.6", "ts-node": "^10.9.2", - "typescript": "^5.3.3", + "typescript": "^5.9.3", + "@typescript-eslint/eslint-plugin": "^8.48.0", + "@typescript-eslint/parser": "^8.48.0", "vite": "5.4.21", "vite-plugin-svgr": "^4.5.0" }, diff --git a/src/main/events/auth/get-session-hash.ts b/src/main/events/auth/get-session-hash.ts deleted file mode 100644 index 02edebdc..00000000 --- a/src/main/events/auth/get-session-hash.ts +++ /dev/null @@ -1,20 +0,0 @@ -import jwt from "jsonwebtoken"; - -import { registerEvent } from "../register-event"; -import { db, levelKeys } from "@main/level"; -import type { Auth } from "@types"; - -const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { - const auth = await db.get(levelKeys.auth, { - valueEncoding: "json", - }); - - if (!auth) return null; - const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload; - - if (!payload) return null; - - return payload.sessionId; -}; - -registerEvent("getSessionHash", getSessionHash); diff --git a/src/main/events/auth/index.ts b/src/main/events/auth/index.ts index e94e9bc5..cecffcb4 100644 --- a/src/main/events/auth/index.ts +++ b/src/main/events/auth/index.ts @@ -1,3 +1,2 @@ -import "./get-session-hash"; import "./open-auth-window"; import "./sign-out"; diff --git a/src/main/events/download-sources/get-download-sources-check-baseline.ts b/src/main/events/download-sources/get-download-sources-check-baseline.ts deleted file mode 100644 index 2f3ab377..00000000 --- a/src/main/events/download-sources/get-download-sources-check-baseline.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { getDownloadSourcesCheckBaseline } from "@main/level"; -import { registerEvent } from "../register-event"; - -const getDownloadSourcesCheckBaselineHandler = async ( - _event: Electron.IpcMainInvokeEvent -) => { - return await getDownloadSourcesCheckBaseline(); -}; - -registerEvent( - "getDownloadSourcesCheckBaseline", - getDownloadSourcesCheckBaselineHandler -); diff --git a/src/main/events/download-sources/get-download-sources-since-value.ts b/src/main/events/download-sources/get-download-sources-since-value.ts deleted file mode 100644 index cbd06faf..00000000 --- a/src/main/events/download-sources/get-download-sources-since-value.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { getDownloadSourcesSinceValue } from "@main/level"; -import { registerEvent } from "../register-event"; - -const getDownloadSourcesSinceValueHandler = async ( - _event: Electron.IpcMainInvokeEvent -) => { - return await getDownloadSourcesSinceValue(); -}; - -registerEvent( - "getDownloadSourcesSinceValue", - getDownloadSourcesSinceValueHandler -); diff --git a/src/main/events/download-sources/get-download-sources.ts b/src/main/events/download-sources/get-download-sources.ts deleted file mode 100644 index 48583d9e..00000000 --- a/src/main/events/download-sources/get-download-sources.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { downloadSourcesSublevel } from "@main/level"; -import { registerEvent } from "../register-event"; -import { orderBy } from "lodash-es"; - -const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => { - const allSources = await downloadSourcesSublevel.values().all(); - return orderBy(allSources, "createdAt", "desc"); -}; - -registerEvent("getDownloadSources", getDownloadSources); diff --git a/src/main/events/download-sources/index.ts b/src/main/events/download-sources/index.ts index 325d5570..454bc3c9 100644 --- a/src/main/events/download-sources/index.ts +++ b/src/main/events/download-sources/index.ts @@ -1,6 +1,3 @@ import "./add-download-source"; -import "./get-download-sources-check-baseline"; -import "./get-download-sources-since-value"; -import "./get-download-sources"; import "./remove-download-source"; import "./sync-download-sources"; diff --git a/src/main/events/library/clear-new-download-options.ts b/src/main/events/library/clear-new-download-options.ts deleted file mode 100644 index 55ebfd8f..00000000 --- a/src/main/events/library/clear-new-download-options.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { registerEvent } from "../register-event"; -import { gamesSublevel, levelKeys } from "@main/level"; -import { logger } from "@main/services"; -import type { GameShop } from "@types"; - -const clearNewDownloadOptions = async ( - _event: Electron.IpcMainInvokeEvent, - shop: GameShop, - objectId: string -) => { - const gameKey = levelKeys.game(shop, objectId); - - const game = await gamesSublevel.get(gameKey); - if (!game) return; - - try { - await gamesSublevel.put(gameKey, { - ...game, - newDownloadOptionsCount: undefined, - }); - logger.info(`Cleared newDownloadOptionsCount for game ${gameKey}`); - } catch (error) { - logger.error(`Failed to clear newDownloadOptionsCount: ${error}`); - } -}; - -registerEvent("clearNewDownloadOptions", clearNewDownloadOptions); diff --git a/src/main/events/library/get-game-by-object-id.ts b/src/main/events/library/get-game-by-object-id.ts deleted file mode 100644 index 239bcb8d..00000000 --- a/src/main/events/library/get-game-by-object-id.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { registerEvent } from "../register-event"; -import { gamesSublevel, downloadsSublevel, levelKeys } from "@main/level"; -import type { GameShop } from "@types"; - -const getGameByObjectId = async ( - _event: Electron.IpcMainInvokeEvent, - shop: GameShop, - objectId: string -) => { - const gameKey = levelKeys.game(shop, objectId); - const [game, download] = await Promise.all([ - gamesSublevel.get(gameKey), - downloadsSublevel.get(gameKey), - ]); - - if (!game || game.isDeleted) return null; - - return { id: gameKey, ...game, download }; -}; - -registerEvent("getGameByObjectId", getGameByObjectId); diff --git a/src/main/events/library/get-library.ts b/src/main/events/library/get-library.ts deleted file mode 100644 index 9fb3416b..00000000 --- a/src/main/events/library/get-library.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { LibraryGame } from "@types"; -import { registerEvent } from "../register-event"; -import { - downloadsSublevel, - gameAchievementsSublevel, - gamesShopAssetsSublevel, - gamesSublevel, -} from "@main/level"; - -const getLibrary = async (): Promise => { - return gamesSublevel - .iterator() - .all() - .then((results) => { - return Promise.all( - results - .filter(([_key, game]) => game.isDeleted === false) - .map(async ([key, game]) => { - 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, - achievementCount: game.achievementCount ?? 0, - // Spread gameAssets last to ensure all image URLs are properly set - ...gameAssets, - // Preserve custom image URLs from game if they exist - customIconUrl: game.customIconUrl, - customLogoImageUrl: game.customLogoImageUrl, - customHeroImageUrl: game.customHeroImageUrl, - }; - }) - ); - }); -}; - -registerEvent("getLibrary", getLibrary); diff --git a/src/main/events/library/index.ts b/src/main/events/library/index.ts index d9d628d0..7f360a47 100644 --- a/src/main/events/library/index.ts +++ b/src/main/events/library/index.ts @@ -3,7 +3,6 @@ import "./add-game-to-favorites"; import "./add-game-to-library"; import "./change-game-playtime"; import "./cleanup-unused-assets"; -import "./clear-new-download-options"; import "./close-game"; import "./copy-custom-game-asset"; import "./create-game-shortcut"; @@ -11,8 +10,6 @@ import "./create-steam-shortcut"; import "./delete-game-folder"; import "./extract-game-download"; import "./get-default-wine-prefix-selection-path"; -import "./get-game-by-object-id"; -import "./get-library"; import "./open-game-executable-path"; import "./open-game-installer-path"; import "./open-game-installer"; diff --git a/src/main/events/themes/add-custom-theme.ts b/src/main/events/themes/add-custom-theme.ts deleted file mode 100644 index 95f526d9..00000000 --- a/src/main/events/themes/add-custom-theme.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Theme } from "@types"; -import { registerEvent } from "../register-event"; -import { themesSublevel } from "@main/level"; - -const addCustomTheme = async ( - _event: Electron.IpcMainInvokeEvent, - theme: Theme -) => { - await themesSublevel.put(theme.id, theme); -}; - -registerEvent("addCustomTheme", addCustomTheme); diff --git a/src/main/events/themes/delete-all-custom-themes.ts b/src/main/events/themes/delete-all-custom-themes.ts deleted file mode 100644 index d7a42d39..00000000 --- a/src/main/events/themes/delete-all-custom-themes.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { themesSublevel } from "@main/level"; -import { registerEvent } from "../register-event"; - -const deleteAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => { - await themesSublevel.clear(); -}; - -registerEvent("deleteAllCustomThemes", deleteAllCustomThemes); diff --git a/src/main/events/themes/delete-custom-theme.ts b/src/main/events/themes/delete-custom-theme.ts deleted file mode 100644 index d47c43fb..00000000 --- a/src/main/events/themes/delete-custom-theme.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { themesSublevel } from "@main/level"; -import { registerEvent } from "../register-event"; - -const deleteCustomTheme = async ( - _event: Electron.IpcMainInvokeEvent, - themeId: string -) => { - await themesSublevel.del(themeId); -}; - -registerEvent("deleteCustomTheme", deleteCustomTheme); diff --git a/src/main/events/themes/get-active-custom-theme.ts b/src/main/events/themes/get-active-custom-theme.ts deleted file mode 100644 index b117f758..00000000 --- a/src/main/events/themes/get-active-custom-theme.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { themesSublevel } from "@main/level"; -import { registerEvent } from "../register-event"; - -const getActiveCustomTheme = async () => { - const allThemes = await themesSublevel.values().all(); - return allThemes.find((theme) => theme.isActive); -}; - -registerEvent("getActiveCustomTheme", getActiveCustomTheme); diff --git a/src/main/events/themes/get-all-custom-themes.ts b/src/main/events/themes/get-all-custom-themes.ts deleted file mode 100644 index f59a87cd..00000000 --- a/src/main/events/themes/get-all-custom-themes.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { themesSublevel } from "@main/level"; -import { registerEvent } from "../register-event"; - -const getAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => { - return themesSublevel.values().all(); -}; - -registerEvent("getAllCustomThemes", getAllCustomThemes); diff --git a/src/main/events/themes/get-custom-theme-by-id.ts b/src/main/events/themes/get-custom-theme-by-id.ts deleted file mode 100644 index 4ec5dc03..00000000 --- a/src/main/events/themes/get-custom-theme-by-id.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { themesSublevel } from "@main/level"; -import { registerEvent } from "../register-event"; - -const getCustomThemeById = async ( - _event: Electron.IpcMainInvokeEvent, - themeId: string -) => { - return themesSublevel.get(themeId); -}; - -registerEvent("getCustomThemeById", getCustomThemeById); diff --git a/src/main/events/themes/index.ts b/src/main/events/themes/index.ts index 5f4d4a02..0856efd9 100644 --- a/src/main/events/themes/index.ts +++ b/src/main/events/themes/index.ts @@ -1,11 +1,5 @@ -import "./add-custom-theme"; import "./close-editor-window"; import "./copy-theme-achievement-sound"; -import "./delete-all-custom-themes"; -import "./delete-custom-theme"; -import "./get-active-custom-theme"; -import "./get-all-custom-themes"; -import "./get-custom-theme-by-id"; import "./get-theme-sound-data-url"; import "./get-theme-sound-path"; import "./import-theme-sound-from-store"; diff --git a/src/main/events/user-preferences/get-user-preferences.ts b/src/main/events/user-preferences/get-user-preferences.ts deleted file mode 100644 index ba01d077..00000000 --- a/src/main/events/user-preferences/get-user-preferences.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { registerEvent } from "../register-event"; -import { db, levelKeys } from "@main/level"; -import type { UserPreferences } from "@types"; - -const getUserPreferences = async () => - db.get(levelKeys.userPreferences, { - valueEncoding: "json", - }); - -registerEvent("getUserPreferences", getUserPreferences); diff --git a/src/main/events/user-preferences/index.ts b/src/main/events/user-preferences/index.ts index aab898e6..ab284ea3 100644 --- a/src/main/events/user-preferences/index.ts +++ b/src/main/events/user-preferences/index.ts @@ -1,5 +1,4 @@ import "./authenticate-real-debrid"; import "./authenticate-torbox"; import "./auto-launch"; -import "./get-user-preferences"; import "./update-user-preferences"; diff --git a/src/main/events/user/get-auth.ts b/src/main/events/user/get-auth.ts deleted file mode 100644 index 7c2a9301..00000000 --- a/src/main/events/user/get-auth.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { db, levelKeys } from "@main/level"; -import type { Auth } from "@types"; - -import { registerEvent } from "../register-event"; - -const getAuth = async (_event: Electron.IpcMainInvokeEvent) => - db.get(levelKeys.auth, { - valueEncoding: "json", - }); - -registerEvent("getAuth", getAuth); diff --git a/src/main/events/user/index.ts b/src/main/events/user/index.ts index cf63116f..46abcae3 100644 --- a/src/main/events/user/index.ts +++ b/src/main/events/user/index.ts @@ -1,3 +1,2 @@ -import "./get-auth"; import "./get-compared-unlocked-achievements"; import "./get-unlocked-achievements"; diff --git a/src/preload/index.ts b/src/preload/index.ts index f7c062cb..1c40837b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -13,7 +13,6 @@ import type { UpdateProfileRequest, SeedingStatus, GameAchievement, - Theme, FriendRequestSync, ShortcutLocation, AchievementCustomNotificationPosition, @@ -86,7 +85,8 @@ contextBridge.exposeInMainWorld("electron", { }, /* User preferences */ - getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), + getUserPreferences: () => + ipcRenderer.invoke("leveldbGet", "userPreferences", null, "json"), updateUserPreferences: (preferences: UserPreferences) => ipcRenderer.invoke("updateUserPreferences", preferences), autoLaunch: (autoLaunchProps: { enabled: boolean; minimized: boolean }) => @@ -101,12 +101,7 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("addDownloadSource", url), removeDownloadSource: (url: string, removeAll?: boolean) => ipcRenderer.invoke("removeDownloadSource", url, removeAll), - getDownloadSources: () => ipcRenderer.invoke("getDownloadSources"), syncDownloadSources: () => ipcRenderer.invoke("syncDownloadSources"), - getDownloadSourcesCheckBaseline: () => - ipcRenderer.invoke("getDownloadSourcesCheckBaseline"), - getDownloadSourcesSinceValue: () => - ipcRenderer.invoke("getDownloadSourcesSinceValue"), /* Library */ toggleAutomaticCloudSync: ( @@ -183,8 +178,6 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("addGameToFavorites", shop, objectId), removeGameFromFavorites: (shop: GameShop, objectId: string) => ipcRenderer.invoke("removeGameFromFavorites", shop, objectId), - clearNewDownloadOptions: (shop: GameShop, objectId: string) => - ipcRenderer.invoke("clearNewDownloadOptions", shop, objectId), toggleGamePin: (shop: GameShop, objectId: string, pinned: boolean) => ipcRenderer.invoke("toggleGamePin", shop, objectId, pinned), updateLaunchOptions: ( @@ -201,7 +194,6 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("selectGameWinePrefix", shop, objectId, winePrefixPath), verifyExecutablePathInUse: (executablePath: string) => ipcRenderer.invoke("verifyExecutablePathInUse", executablePath), - getLibrary: () => ipcRenderer.invoke("getLibrary"), refreshLibraryAssets: () => ipcRenderer.invoke("refreshLibraryAssets"), openGameInstaller: (shop: GameShop, objectId: string) => ipcRenderer.invoke("openGameInstaller", shop, objectId), @@ -230,8 +222,6 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("removeGame", shop, objectId), deleteGameFolder: (shop: GameShop, objectId: string) => ipcRenderer.invoke("deleteGameFolder", shop, objectId), - getGameByObjectId: (shop: GameShop, objectId: string) => - ipcRenderer.invoke("getGameByObjectId", shop, objectId), resetGameAchievements: (shop: GameShop, objectId: string) => ipcRenderer.invoke("resetGameAchievements", shop, objectId), changeGamePlayTime: (shop: GameShop, objectId: string, playtime: number) => @@ -287,8 +277,6 @@ contextBridge.exposeInMainWorld("electron", { gameArtifactId: string ) => ipcRenderer.invoke("downloadGameArtifact", objectId, shop, gameArtifactId), - getGameArtifacts: (objectId: string, shop: GameShop) => - ipcRenderer.invoke("getGameArtifacts", objectId, shop), getGameBackupPreview: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameBackupPreview", objectId, shop), selectGameBackupPath: ( @@ -503,11 +491,10 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("getUnlockedAchievements", objectId, shop), /* Auth */ - getAuth: () => ipcRenderer.invoke("getAuth"), + getAuth: () => ipcRenderer.invoke("leveldbGet", "auth", null, "json"), signOut: () => ipcRenderer.invoke("signOut"), openAuthWindow: (page: AuthPage) => ipcRenderer.invoke("openAuthWindow", page), - getSessionHash: () => ipcRenderer.invoke("getSessionHash"), onSignIn: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-signin", listener); @@ -565,16 +552,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("showAchievementTestNotification"), /* Themes */ - addCustomTheme: (theme: Theme) => ipcRenderer.invoke("addCustomTheme", theme), - getAllCustomThemes: () => ipcRenderer.invoke("getAllCustomThemes"), - deleteAllCustomThemes: () => ipcRenderer.invoke("deleteAllCustomThemes"), - deleteCustomTheme: (themeId: string) => - ipcRenderer.invoke("deleteCustomTheme", themeId), updateCustomTheme: (themeId: string, code: string) => ipcRenderer.invoke("updateCustomTheme", themeId, code), - getCustomThemeById: (themeId: string) => - ipcRenderer.invoke("getCustomThemeById", themeId), - getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"), toggleCustomTheme: (themeId: string, isActive: boolean) => ipcRenderer.invoke("toggleCustomTheme", themeId, isActive), copyThemeAchievementSound: (themeId: string, sourcePath: string) => diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index 2c32c5da..f8e4fde8 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -7,11 +7,13 @@ import { useToast, useUserDetails, } from "@renderer/hooks"; +import { levelDBService } from "@renderer/services/leveldb.service"; import "./bottom-panel.scss"; import { useNavigate } from "react-router-dom"; import { VERSION_CODENAME } from "@renderer/constants"; +import type jwt from "jsonwebtoken"; export function BottomPanel() { const { t } = useTranslation("bottom_panel"); @@ -60,7 +62,28 @@ export function BottomPanel() { }, [t, showSuccessToast]); useEffect(() => { - window.electron.getSessionHash().then((result) => setSessionHash(result)); + const getSessionHash = async () => { + const auth = (await levelDBService.get("auth", null, "json")) as { + accessToken?: string; + } | null; + + if (!auth?.accessToken) { + setSessionHash(null); + return; + } + + try { + const jwtModule = await import("jsonwebtoken"); + const payload = jwtModule.decode( + auth.accessToken + ) as jwt.JwtPayload | null; + setSessionHash(payload?.sessionId ?? null); + } catch { + setSessionHash(null); + } + }; + + getSessionHash(); }, [userDetails?.id]); const status = useMemo(() => { diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 29feabf5..e41046e8 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -12,6 +12,7 @@ import { } from "@renderer/hooks"; import type { + Download, DownloadSource, GameRepack, GameShop, @@ -95,9 +96,19 @@ export function GameDetailsContextProvider({ ); const updateGame = useCallback(async () => { - return window.electron - .getGameByObjectId(shop, objectId) - .then((result) => setGame(result)); + const gameKey = `${shop}:${objectId}`; + const [game, download] = await Promise.all([ + levelDBService.get(gameKey, "games") as Promise, + levelDBService.get(gameKey, "downloads") as Promise, + ]); + + if (!game || game.isDeleted) { + setGame(null); + return; + } + + const { id: _id, ...gameWithoutId } = game; + setGame({ id: gameKey, ...gameWithoutId, download: download ?? null }); }, [shop, objectId]); const isGameDownloading = diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 56205b2f..940741f9 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -18,10 +18,8 @@ import type { LudusaviBackup, UserAchievement, ComparedAchievements, - LibraryGame, GameRunning, TorBoxUser, - Theme, Auth, ShortcutLocation, ShopAssets, @@ -142,10 +140,6 @@ declare global { shop: GameShop, objectId: string ) => Promise; - clearNewDownloadOptions: ( - shop: GameShop, - objectId: string - ) => Promise; toggleGamePin: ( shop: GameShop, objectId: string, @@ -162,7 +156,6 @@ declare global { winePrefixPath: string | null ) => Promise; verifyExecutablePathInUse: (executablePath: string) => Promise; - getLibrary: () => Promise; refreshLibraryAssets: () => Promise; openGameInstaller: (shop: GameShop, objectId: string) => Promise; openGameInstallerPath: (shop: GameShop, objectId: string) => Promise; @@ -177,10 +170,6 @@ declare global { removeGameFromLibrary: (shop: GameShop, objectId: string) => Promise; removeGame: (shop: GameShop, objectId: string) => Promise; deleteGameFolder: (shop: GameShop, objectId: string) => Promise; - getGameByObjectId: ( - shop: GameShop, - objectId: string - ) => Promise; onGamesRunning: ( cb: ( gamesRunning: Pick[] @@ -194,9 +183,9 @@ declare global { playtimeInSeconds: number ) => Promise; /* User preferences */ + getUserPreferences: () => Promise; authenticateRealDebrid: (apiToken: string) => Promise; authenticateTorBox: (apiToken: string) => Promise; - getUserPreferences: () => Promise; updateUserPreferences: ( preferences: Partial ) => Promise; @@ -217,10 +206,7 @@ declare global { removeAll = false, downloadSourceId?: string ) => Promise; - getDownloadSources: () => Promise; syncDownloadSources: () => Promise; - getDownloadSourcesCheckBaseline: () => Promise; - getDownloadSourcesSinceValue: () => Promise; /* Hardware */ getDiskFreeSpace: (path: string) => Promise; @@ -237,10 +223,6 @@ declare global { shop: GameShop, gameArtifactId: string ) => Promise; - getGameArtifacts: ( - objectId: string, - shop: GameShop - ) => Promise; getGameBackupPreview: ( objectId: string, shop: GameShop @@ -355,7 +337,6 @@ declare global { getAuth: () => Promise; signOut: () => Promise; openAuthWindow: (page: AuthPage) => Promise; - getSessionHash: () => Promise; onSignIn: (cb: () => void) => () => Electron.IpcRenderer; onAccountUpdated: (cb: () => void) => () => Electron.IpcRenderer; onSignOut: (cb: () => void) => () => Electron.IpcRenderer; @@ -408,13 +389,7 @@ declare global { showAchievementTestNotification: () => Promise; /* Themes */ - addCustomTheme: (theme: Theme) => Promise; - getAllCustomThemes: () => Promise; - deleteAllCustomThemes: () => Promise; - deleteCustomTheme: (themeId: string) => Promise; updateCustomTheme: (themeId: string, code: string) => Promise; - getCustomThemeById: (themeId: string) => Promise; - getActiveCustomTheme: () => Promise; toggleCustomTheme: (themeId: string, isActive: boolean) => Promise; copyThemeAchievementSound: ( themeId: string, diff --git a/src/renderer/src/hooks/use-library.ts b/src/renderer/src/hooks/use-library.ts index f7310df0..23e5177b 100644 --- a/src/renderer/src/hooks/use-library.ts +++ b/src/renderer/src/hooks/use-library.ts @@ -1,15 +1,65 @@ import { useCallback } from "react"; import { useAppDispatch, useAppSelector } from "./redux"; import { setLibrary } from "@renderer/features"; +import { levelDBService } from "@renderer/services/leveldb.service"; +import type { + LibraryGame, + Game, + Download, + ShopAssets, + GameAchievement, +} from "@types"; export function useLibrary() { const dispatch = useAppDispatch(); const library = useAppSelector((state) => state.library.value); const updateLibrary = useCallback(async () => { - return window.electron - .getLibrary() - .then((updatedLibrary) => dispatch(setLibrary(updatedLibrary))); + const results = await levelDBService.iterator("games"); + + const libraryGames = await Promise.all( + results + .filter(([_key, game]) => (game as Game).isDeleted === false) + .map(async ([key, game]) => { + const gameData = game as Game; + const download = (await levelDBService.get( + key, + "downloads" + )) as Download | null; + const gameAssets = (await levelDBService.get( + key, + "gameShopAssets" + )) as (ShopAssets & { updatedAt: number }) | null; + + let unlockedAchievementCount = gameData.unlockedAchievementCount ?? 0; + + if (!gameData.unlockedAchievementCount) { + const achievements = (await levelDBService.get( + key, + "gameAchievements" + )) as GameAchievement | null; + + unlockedAchievementCount = + achievements?.unlockedAchievements.length ?? 0; + } + + return { + id: key, + ...gameData, + download: download ?? null, + unlockedAchievementCount, + achievementCount: gameData.achievementCount ?? 0, + // Spread gameAssets last to ensure all image URLs are properly set + ...gameAssets, + // Preserve custom image URLs from game if they exist + customIconUrl: gameData.customIconUrl, + customLogoImageUrl: gameData.customLogoImageUrl, + customHeroImageUrl: gameData.customHeroImageUrl, + } as LibraryGame; + }) + ); + + dispatch(setLibrary(libraryGames)); }, [dispatch]); return { library, updateLibrary }; diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index e09226b5..5b32677a 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -126,10 +126,17 @@ export function UserLibraryGameCard({ return ( <> - +