diff --git a/src/renderer/src/pages/library/filter-options.scss b/src/renderer/src/pages/library/filter-options.scss index cc899d56..6f309662 100644 --- a/src/renderer/src/pages/library/filter-options.scss +++ b/src/renderer/src/pages/library/filter-options.scss @@ -15,8 +15,7 @@ padding: 8px 16px; border-radius: 6px; background: rgba(255, 255, 255, 0.05); - border: none; - color: rgba(255, 255, 255, 0.6); + color: rgba(255, 255, 255, 0.9); cursor: pointer; font-size: 13px; font-weight: 500; @@ -51,8 +50,8 @@ } &__count { - background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.8); + background: rgba(255, 255, 255, 0.16); + color: rgba(255, 255, 255, 0.95); padding: 2px 8px; border-radius: 4px; font-size: 12px; diff --git a/src/renderer/src/pages/library/filter-options.tsx b/src/renderer/src/pages/library/filter-options.tsx index 07c935d9..572ebd35 100644 --- a/src/renderer/src/pages/library/filter-options.tsx +++ b/src/renderer/src/pages/library/filter-options.tsx @@ -19,7 +19,7 @@ export function FilterOptions({ favouritedCount, newGamesCount, top10Count, -}: FilterOptionsProps) { +}: Readonly) { const { t } = useTranslation("library"); return ( diff --git a/src/renderer/src/pages/library/library-game-card-large.scss b/src/renderer/src/pages/library/library-game-card-large.scss index 700c7d0b..3fceac03 100644 --- a/src/renderer/src/pages/library/library-game-card-large.scss +++ b/src/renderer/src/pages/library/library-game-card-large.scss @@ -104,7 +104,7 @@ justify-content: center; cursor: pointer; transition: all ease 0.2s; - color: rgba(255, 255, 255, 0.8); + color: rgba(255, 255, 255, 0.95); padding: 0; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); opacity: 0; @@ -162,7 +162,7 @@ background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); - color: rgba(255, 255, 255, 0.8); + color: rgba(255, 255, 255, 0.95); border: solid 1px rgba(255, 255, 255, 0.15); border-radius: 4px; display: flex; @@ -242,7 +242,7 @@ &__achievement-percentage { font-size: 12px; - color: rgba(255, 255, 255, 0.7); + color: rgba(255, 255, 255, 0.85); white-space: nowrap; } @@ -254,7 +254,7 @@ border-radius: 6px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); - color: rgba(255, 255, 255, 0.9); + color: rgba(255, 255, 255, 0.95); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); cursor: pointer; 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 3c9285e9..e56f0ae3 100644 --- a/src/renderer/src/pages/library/library-game-card-large.tsx +++ b/src/renderer/src/pages/library/library-game-card-large.tsx @@ -30,7 +30,9 @@ const getImageWithCustomPriority = ( return customUrl || originalUrl || fallbackUrl || ""; }; -export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) { +export function LibraryGameCardLarge({ + game, +}: Readonly) { const { t } = useTranslation("library"); const { numberFormatter } = useFormat(); const navigate = useNavigate(); @@ -82,7 +84,7 @@ export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) { try { await handleCloseGame(); } catch (e) { - void e; + console.error(e); } return; } @@ -92,7 +94,7 @@ export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) { try { handleOpenDownloadOptions(); } catch (e) { - void e; + console.error(e); } } }; @@ -223,30 +225,44 @@ export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) { className="library-game-card-large__action-button" onClick={handleActionClick} > - {isGameDownloading ? ( - <> - - {t("downloading")} - - ) : isGameRunning ? ( - <> - - {t("close")} - - ) : game.executablePath ? ( - <> - - {t("play")} - - ) : ( - <> - - {t("download")} - - )} + {(() => { + if (isGameDownloading) { + return ( + <> + + {t("downloading")} + + ); + } + + if (isGameRunning) { + return ( + <> + + {t("close")} + + ); + } + + if (game.executablePath) { + return ( + <> + + {t("play")} + + ); + } + + return ( + <> + + {t("download")} + + ); + })()} diff --git a/src/renderer/src/pages/library/library-game-card.tsx b/src/renderer/src/pages/library/library-game-card.tsx index be0a5a73..a9f2aba2 100644 --- a/src/renderer/src/pages/library/library-game-card.tsx +++ b/src/renderer/src/pages/library/library-game-card.tsx @@ -25,7 +25,7 @@ export function LibraryGameCard({ game, onMouseEnter, onMouseLeave, -}: LibraryGameCardProps) { +}: Readonly) { const { t } = useTranslation("library"); const { numberFormatter } = useFormat(); const navigate = useNavigate(); diff --git a/src/renderer/src/pages/library/library.tsx b/src/renderer/src/pages/library/library.tsx index 2d58bb08..1cafc870 100644 --- a/src/renderer/src/pages/library/library.tsx +++ b/src/renderer/src/pages/library/library.tsx @@ -13,6 +13,10 @@ import "./library.scss"; export default function Library() { const { library, updateLibrary } = useLibrary(); + type ElectronAPI = { + refreshLibraryAssets?: () => Promise; + onLibraryBatchComplete?: (cb: () => void) => () => void; + }; const [viewMode, setViewMode] = useState("compact"); const [filterBy, setFilterBy] = useState("all"); @@ -22,17 +26,22 @@ export default function Library() { useEffect(() => { dispatch(setHeaderTitle(t("library"))); - - // Refresh library assets from cloud, then update library display - window.electron - .refreshLibraryAssets() - .then(() => updateLibrary()) - .catch(() => updateLibrary()); // Fallback to local cache on error - - // Listen for library sync completion to refresh cover images - const unsubscribe = window.electron.onLibraryBatchComplete(() => { + 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 { updateLibrary(); - }); + } return () => { unsubscribe(); @@ -47,31 +56,6 @@ export default function Library() { // Optional: resume animations if needed }; - // Simple fuzzy search function - const fuzzySearch = (query: string, items: typeof library) => { - if (!query.trim()) return items; - - const queryLower = query.toLowerCase(); - return items.filter((game) => { - const titleLower = game.title.toLowerCase(); - let matches = 0; - let queryIndex = 0; - - for ( - let i = 0; - i < titleLower.length && queryIndex < queryLower.length; - i++ - ) { - if (titleLower[i] === queryLower[queryIndex]) { - matches++; - queryIndex++; - } - } - - return queryIndex === queryLower.length; - }); - }; - const filteredLibrary = useMemo(() => { let filtered; @@ -98,8 +82,25 @@ export default function Library() { filtered = library; } - // Apply search filter - return fuzzySearch(searchQuery, filtered); + if (!searchQuery.trim()) return filtered; + + const queryLower = searchQuery.toLowerCase(); + return filtered.filter((game) => { + const titleLower = game.title.toLowerCase(); + let queryIndex = 0; + + for ( + let i = 0; + i < titleLower.length && queryIndex < queryLower.length; + i++ + ) { + if (titleLower[i] === queryLower[queryIndex]) { + queryIndex++; + } + } + + return queryIndex === queryLower.length; + }); }, [library, filterBy, searchQuery]); // No sorting for now — rely on filteredLibrary @@ -118,30 +119,25 @@ export default function Library() { return (
{hasGames && ( - <> -
-
-
- -
+
+
+
+ +
-
- - -
+
+ +
- +
)} {!hasGames && ( diff --git a/src/renderer/src/pages/library/search-bar.scss b/src/renderer/src/pages/library/search-bar.scss index 7c487ec5..6b09c683 100644 --- a/src/renderer/src/pages/library/search-bar.scss +++ b/src/renderer/src/pages/library/search-bar.scss @@ -22,7 +22,7 @@ } &__icon { - color: rgba(255, 255, 255, 0.5); + color: rgba(255, 255, 255, 0.75); flex-shrink: 0; transition: color 0.2s ease; } @@ -38,7 +38,7 @@ min-width: 0; &::placeholder { - color: rgba(255, 255, 255, 0.4); + color: rgba(255, 255, 255, 0.6); } &:focus ~ .search-bar__icon { @@ -50,7 +50,7 @@ flex-shrink: 0; background: transparent; border: none; - color: rgba(255, 255, 255, 0.5); + color: rgba(255, 255, 255, 0.65); font-size: 18px; font-weight: bold; cursor: pointer; diff --git a/src/renderer/src/pages/library/view-options.scss b/src/renderer/src/pages/library/view-options.scss index b8cf1484..77bfc10e 100644 --- a/src/renderer/src/pages/library/view-options.scss +++ b/src/renderer/src/pages/library/view-options.scss @@ -10,7 +10,7 @@ &__label { font-size: 14px; font-weight: 600; - color: rgba(255, 255, 255, 0.8); + color: rgba(255, 255, 255, 0.95); white-space: nowrap; } @@ -30,7 +30,7 @@ border-radius: 6px; background: rgba(255, 255, 255, 0.04); border: none; - color: rgba(255, 255, 255, 0.6); + color: rgba(255, 255, 255, 0.9); cursor: pointer; font-size: 14px; font-weight: 500; diff --git a/src/renderer/src/pages/library/view-options.tsx b/src/renderer/src/pages/library/view-options.tsx index 662bab65..905fac58 100644 --- a/src/renderer/src/pages/library/view-options.tsx +++ b/src/renderer/src/pages/library/view-options.tsx @@ -9,7 +9,10 @@ interface ViewOptionsProps { onViewModeChange: (viewMode: ViewMode) => void; } -export function ViewOptions({ viewMode, onViewModeChange }: ViewOptionsProps) { +export function ViewOptions({ + viewMode, + onViewModeChange, +}: Readonly) { const { t } = useTranslation("library"); return (