diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index cb2473eb..bcd774ea 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -719,9 +719,8 @@ "amount_minutes_short": "{{amount}}m", "manual_playtime_tooltip": "This playtime has been manually updated", "all_games": "All Games", - "favourited_games": "Favourited", - "new_games": "New Games", - "top_10": "Top 10" + "recently_played": "Recently Played", + "favorites": "Favorites" }, "achievement": { "achievement_unlocked": "Achievement unlocked", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index c7e9d13e..ad08777a 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -13,6 +13,7 @@ }, "sidebar": { "catalogue": "Catálogo", + "library": "Librería", "downloads": "Descargas", "settings": "Ajustes", "my_library": "Mi Librería", @@ -716,5 +717,26 @@ "hydra_cloud_feature_found": "¡Acabas de descubrir una característica de Hydra Cloud!", "learn_more": "Descubrir más", "debrid_description": "Descargas hasta x4 veces más rápidas con Nimbus" + }, + "library": { + "library": "Librería", + "play": "Jugar", + "download": "Descargar", + "downloading": "Descargando", + "game": "juego", + "games": "juegos", + "grid_view": "Vista de cuadrícula", + "compact_view": "Vista compacta", + "large_view": "Vista grande", + "no_games_title": "Tu librería está vacía", + "no_games_description": "Agregá juegos del catálogo o descargalos para comenzar", + "amount_hours": "{{amount}} horas", + "amount_minutes": "{{amount}} minutos", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "manual_playtime_tooltip": "Este tiempo de juego ha sido modificado manualmente", + "all_games": "Todos los Juegos", + "recently_played": "Jugados Recientemente", + "favorites": "Favoritos" } } diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 50049140..002ec720 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -13,6 +13,7 @@ }, "sidebar": { "catalogue": "Catálogo", + "library": "Biblioteca", "downloads": "Downloads", "settings": "Ajustes", "my_library": "Biblioteca", @@ -731,5 +732,26 @@ "hydra_cloud_feature_found": "Você descobriu uma funcionalidade Hydra Cloud!", "learn_more": "Saiba mais", "debrid_description": "Baixe até 4x mais rápido com Nimbus" + }, + "library": { + "library": "Biblioteca", + "play": "Jogar", + "download": "Baixar", + "downloading": "Baixando", + "game": "jogo", + "games": "jogos", + "grid_view": "Visualização em grade", + "compact_view": "Visualização compacta", + "large_view": "Visualização grande", + "no_games_title": "Sua biblioteca está vazia", + "no_games_description": "Adicione jogos do catálogo ou baixe-os para começar", + "amount_hours": "{{amount}} horas", + "amount_minutes": "{{amount}} minutos", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "manual_playtime_tooltip": "Este tempo de jogo foi atualizado manualmente", + "all_games": "Todos os Jogos", + "recently_played": "Jogados Recentemente", + "favorites": "Favoritos" } } diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 2e7c1504..02477701 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -13,6 +13,7 @@ }, "sidebar": { "catalogue": "Каталог", + "library": "Библиотека", "downloads": "Загрузки", "settings": "Настройки", "my_library": "Библиотека", @@ -727,5 +728,26 @@ "hydra_cloud_feature_found": "Вы только что открыли для себя функцию Hydra Cloud!", "learn_more": "Подробнее", "debrid_description": "Скачивайте в 4 раза быстрее с Nimbus" + }, + "library": { + "library": "Библиотека", + "play": "Играть", + "download": "Скачать", + "downloading": "Скачивание", + "game": "игра", + "games": "игры", + "grid_view": "Вид сетки", + "compact_view": "Компактный вид", + "large_view": "Большой вид", + "no_games_title": "Ваша библиотека пуста", + "no_games_description": "Добавьте игры из каталога или скачайте их, чтобы начать", + "amount_hours": "{{amount}} часов", + "amount_minutes": "{{amount}} минут", + "amount_hours_short": "{{amount}}ч", + "amount_minutes_short": "{{amount}}м", + "manual_playtime_tooltip": "Время игры было обновлено вручную", + "all_games": "Все игры", + "recently_played": "Недавно сыгранные", + "favorites": "Избранное" } } diff --git a/src/renderer/src/pages/library/filter-options.scss b/src/renderer/src/pages/library/filter-options.scss index 4831fd0e..25835072 100644 --- a/src/renderer/src/pages/library/filter-options.scss +++ b/src/renderer/src/pages/library/filter-options.scss @@ -1,63 +1,55 @@ @use "../../scss/globals.scss"; .library-filter-options { - &__container { + &__tabs { display: flex; - align-items: center; gap: calc(globals.$spacing-unit); - flex-wrap: wrap; + position: relative; } - &__option { - display: flex; - align-items: center; - gap: calc(globals.$spacing-unit); - padding: 8px 12px; - border-radius: 6px; - background: rgba(255, 255, 255, 0.05); - color: rgba(255, 255, 255, 0.9); + &__tab-wrapper { + position: relative; + } + + &__tab { + background: none; + border: none; + color: rgba(255, 255, 255, 0.6); + padding: calc(globals.$spacing-unit) calc(globals.$spacing-unit * 2); cursor: pointer; - font-size: 12px; + font-size: 14px; font-weight: 500; - transition: all ease 0.2s; - white-space: nowrap; /* prevent label and count from wrapping */ - border: 1px solid rgba(0, 0, 0, 0.06); + transition: color ease 0.2s; + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 0.5); - &:hover { - color: rgba(255, 255, 255, 0.9); - background: rgba(255, 255, 255, 0.08); - } - - &.active { - color: #000; - background: #fff; - svg, - svg * { - fill: currentColor; - color: currentColor; - } - - .library-filter-options__count { - background: #ebebeb; - color: rgba(0, 0, 0, 0.9); - } + &--active { + color: white; } } - &__label { - font-weight: 500; - white-space: nowrap; - } - - &__count { - background: rgba(255, 255, 255, 0.16); - color: rgba(255, 255, 255, 0.95); - padding: 2px 8px; - border-radius: 4px; - font-size: 12px; + &__tab-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 6px; + background-color: rgba(255, 255, 255, 0.15); + border-radius: 9px; + font-size: 11px; font-weight: 600; - min-width: 24px; - text-align: center; - transition: all ease 0.2s; + color: rgba(255, 255, 255, 0.9); + line-height: 1; + } + + &__tab-underline { + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 2px; + background: white; } } diff --git a/src/renderer/src/pages/library/filter-options.tsx b/src/renderer/src/pages/library/filter-options.tsx index 572ebd35..cd22368f 100644 --- a/src/renderer/src/pages/library/filter-options.tsx +++ b/src/renderer/src/pages/library/filter-options.tsx @@ -1,61 +1,103 @@ +import { motion } from "framer-motion"; import { useTranslation } from "react-i18next"; import "./filter-options.scss"; -export type FilterOption = "all" | "favourited" | "new" | "top10"; +export type FilterOption = "all" | "recently_played" | "favorites"; interface FilterOptionsProps { filterBy: FilterOption; onFilterChange: (filterBy: FilterOption) => void; allGamesCount: number; - favouritedCount: number; - newGamesCount: number; - top10Count: number; + recentlyPlayedCount: number; + favoritesCount: number; } export function FilterOptions({ filterBy, onFilterChange, allGamesCount, - favouritedCount, - newGamesCount, - top10Count, + recentlyPlayedCount, + favoritesCount, }: Readonly) { const { t } = useTranslation("library"); return ( -
- - - - +
+
+ + {filterBy === "all" && ( + + )} +
+
+ + {filterBy === "recently_played" && ( + + )} +
+
+ + {filterBy === "favorites" && ( + + )} +
); } 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 212fe80a..a06de7e9 100644 --- a/src/renderer/src/pages/library/library-game-card-large.scss +++ b/src/renderer/src/pages/library/library-game-card-large.scss @@ -84,36 +84,6 @@ gap: calc(globals.$spacing-unit); } - &__menu-button { - align-self: flex-start; - background: rgba(0, 0, 0, 0.3); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - border: solid 1px rgba(255, 255, 255, 0.15); - border-radius: 4px; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all ease 0.2s; - color: rgba(255, 255, 255, 0.95); - padding: 0; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - opacity: 0; - transform: scale(0.9); - - &:hover { - background: rgba(0, 0, 0, 0.6); - border-color: rgba(255, 255, 255, 0.25); - transform: scale(1.05); - } - - &:active { - transform: scale(0.95); - } - } &__logo-container { flex: 1; @@ -238,50 +208,4 @@ white-space: nowrap; } - &__action-button { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 20px; - 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.95); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - cursor: pointer; - font-size: 14px; - font-weight: 600; - transition: all ease 0.2s; - flex: 0 0 auto; - - &:hover { - background: rgba(255, 255, 255, 0.15); - border-color: rgba(255, 255, 255, 0.3); - transform: scale(1.05); - } - - &:active { - transform: scale(0.98); - } - } - - &:hover &__menu-button { - opacity: 1; - transform: scale(1); - } - - &__action-icon--downloading { - animation: pulse 1.5s ease-in-out infinite; - } -} - -@keyframes pulse { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } } 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 5c0e54c4..c2bf6a3b 100644 --- a/src/renderer/src/pages/library/library-game-card-large.tsx +++ b/src/renderer/src/pages/library/library-game-card-large.tsx @@ -1,18 +1,11 @@ import { LibraryGame } from "@types"; -import { useDownload, useGameCard } from "@renderer/hooks"; +import { useGameCard } from "@renderer/hooks"; import { - PlayIcon, - DownloadIcon, ClockIcon, AlertFillIcon, - ThreeBarsIcon, TrophyIcon, - XIcon, } from "@primer/octicons-react"; -import { useTranslation } from "react-i18next"; import { memo, useMemo } from "react"; -import { useGameActions } from "@renderer/components/game-context-menu/use-game-actions"; -import { logger } from "@renderer/logger"; import "./library-game-card-large.scss"; interface LibraryGameCardLargeProps { @@ -35,48 +28,12 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({ game, onContextMenu, }: Readonly) { - const { t } = useTranslation("library"); - const { lastPacket } = useDownload(); const { formatPlayTime, handleCardClick, handleContextMenuClick, - handleMenuButtonClick, } = useGameCard(game, onContextMenu); - const isGameDownloading = - game?.download?.status === "active" && lastPacket?.gameId === game?.id; - - const { - handlePlayGame, - handleOpenDownloadOptions, - handleCloseGame, - isGameRunning, - } = useGameActions(game); - - const handleActionClick = async (e: React.MouseEvent) => { - e.stopPropagation(); - - if (isGameRunning) { - try { - await handleCloseGame(); - } catch (e) { - logger.error(e); - } - return; - } - try { - await handlePlayGame(); - } catch (err) { - logger.error(err); - try { - handleOpenDownloadOptions(); - } catch (e) { - logger.error(e); - } - } - }; - const backgroundImage = useMemo( () => getImageWithCustomPriority( @@ -129,14 +86,6 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({ {formatPlayTime(game.playTimeInMilliseconds)}
-
@@ -183,51 +132,6 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({
)} - - diff --git a/src/renderer/src/pages/library/library-game-card.scss b/src/renderer/src/pages/library/library-game-card.scss index 1270e2aa..8643d23c 100644 --- a/src/renderer/src/pages/library/library-game-card.scss +++ b/src/renderer/src/pages/library/library-game-card.scss @@ -109,10 +109,10 @@ &__achievements { display: flex; flex-direction: column; - opacity: 0; - transform: translateY(8px); + opacity: 1; + transform: translateY(0); transition: all ease 0.2s; - pointer-events: none; + pointer-events: auto; width: 100%; } @@ -204,53 +204,12 @@ } } - &__menu-button { - background: rgba(0, 0, 0, 0.4); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - border: solid 1px rgba(255, 255, 255, 0.15); - border-radius: 4px; - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all ease 0.2s; - color: rgba(255, 255, 255, 0.8); - padding: 0; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - opacity: 0; - transform: scale(0.9); - - &:hover { - background: rgba(0, 0, 0, 0.6); - border-color: rgba(255, 255, 255, 0.25); - transform: scale(1.05); - } - - &:active { - transform: scale(0.95); - } - } - - &__wrapper:hover &__action-button, - &__wrapper:hover &__menu-button { + &__wrapper:hover &__action-button { opacity: 1; transform: scale(1); } - &__wrapper:hover &__achievements { - opacity: 1; - transform: translateY(0); - pointer-events: auto; - } - &__action-icon { - &--downloading { - animation: pulse 1.5s ease-in-out infinite; - } - } &__game-image { object-fit: cover; diff --git a/src/renderer/src/pages/library/library-game-card.tsx b/src/renderer/src/pages/library/library-game-card.tsx index 64053155..39cce681 100644 --- a/src/renderer/src/pages/library/library-game-card.tsx +++ b/src/renderer/src/pages/library/library-game-card.tsx @@ -4,7 +4,6 @@ import { memo } from "react"; import { ClockIcon, AlertFillIcon, - ThreeBarsIcon, TrophyIcon, } from "@primer/octicons-react"; import "./library-game-card.scss"; @@ -31,7 +30,6 @@ export const LibraryGameCard = memo(function LibraryGameCard({ formatPlayTime, handleCardClick, handleContextMenuClick, - handleMenuButtonClick, } = useGameCard(game, onContextMenu); const coverImage = @@ -69,18 +67,8 @@ export const LibraryGameCard = memo(function LibraryGameCard({ {formatPlayTime(game.playTimeInMilliseconds, true)} - - - {/* Achievements section - shown on hover */} {(game.achievementCount ?? 0) > 0 && (
diff --git a/src/renderer/src/pages/library/library.scss b/src/renderer/src/pages/library/library.scss index 40688084..ffc68b83 100644 --- a/src/renderer/src/pages/library/library.scss +++ b/src/renderer/src/pages/library/library.scss @@ -38,12 +38,17 @@ align-items: center; justify-content: space-between; width: 100%; + position: relative; } &__controls-left { display: flex; align-items: center; gap: calc(globals.$spacing-unit); + flex: 1; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + margin-right: calc(globals.$spacing-unit * 2); } &__controls-right { diff --git a/src/renderer/src/pages/library/library.tsx b/src/renderer/src/pages/library/library.tsx index 7167809d..86afb549 100644 --- a/src/renderer/src/pages/library/library.tsx +++ b/src/renderer/src/pages/library/library.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState, useCallback } from "react"; +import { AnimatePresence, motion } from "framer-motion"; import { useLibrary, useAppDispatch, useAppSelector } from "@renderer/hooks"; import { setHeaderTitle } from "@renderer/features"; import { TelescopeIcon } from "@primer/octicons-react"; @@ -77,23 +78,12 @@ export default function Library() { let filtered; switch (filterBy) { - case "favourited": + case "recently_played": + filtered = library.filter((game) => game.lastTimePlayed !== null); + break; + case "favorites": filtered = library.filter((game) => game.favorite); break; - case "new": - filtered = library.filter( - (game) => (game.playTimeInMilliseconds || 0) === 0 - ); - break; - case "top10": - filtered = library - .slice() - .sort( - (a, b) => - (b.playTimeInMilliseconds || 0) - (a.playTimeInMilliseconds || 0) - ) - .slice(0, 10); - break; case "all": default: filtered = library; @@ -124,19 +114,18 @@ export default function Library() { const filterCounts = useMemo(() => { const allGamesCount = library.length; - let favouritedCount = 0; - let newGamesCount = 0; + let recentlyPlayedCount = 0; + let favoritesCount = 0; for (const game of library) { - if (game.favorite) favouritedCount++; - if ((game.playTimeInMilliseconds || 0) === 0) newGamesCount++; + if (game.lastTimePlayed !== null) recentlyPlayedCount++; + if (game.favorite) favoritesCount++; } return { allGamesCount, - favouritedCount, - newGamesCount, - top10Count: Math.min(10, allGamesCount), + recentlyPlayedCount, + favoritesCount, }; }, [library]); @@ -152,9 +141,8 @@ export default function Library() { filterBy={filterBy} onFilterChange={setFilterBy} allGamesCount={filterCounts.allGamesCount} - favouritedCount={filterCounts.favouritedCount} - newGamesCount={filterCounts.newGamesCount} - top10Count={filterCounts.top10Count} + recentlyPlayedCount={filterCounts.recentlyPlayedCount} + favoritesCount={filterCounts.favoritesCount} />
@@ -175,34 +163,52 @@ export default function Library() {
)} - {hasGames && viewMode === "large" && ( -
- {sortedLibrary.map((game) => ( - - ))} -
- )} - - {hasGames && viewMode !== "large" && ( - + {sortedLibrary.map((game) => ( + + ))} + + )} + + {viewMode !== "large" && ( + + {sortedLibrary.map((game) => ( +
  • + +
  • + ))} +
    + )} + )} {contextMenu.game && (