From ac0d0efe0f7625ebfb6dbceae806b70e4de6ebc3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:13:28 -0300 Subject: [PATCH 01/62] feat: update interfaces --- src/types/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/index.ts b/src/types/index.ts index bd9293e8e..cb430c0fb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -41,6 +41,7 @@ export interface UserAchievement { name: string; hidden: boolean; displayName: string; + points?: number; description?: string; unlocked: boolean; unlockTime: number | null; @@ -322,6 +323,7 @@ export interface TrendingGame { export interface UserStats { libraryCount: number; friendsCount: number; + achievementsPointsEarnedSum?: number; } export interface UnlockedAchievement { From 23f9b5228c7087d231a34427ea37a63f200deb41 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:40:30 -0300 Subject: [PATCH 02/62] feat: adjustments --- .../src/pages/profile/profile-content/profile-content.tsx | 1 + src/types/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index 269756479..1792ed55a 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -135,6 +135,7 @@ export function ProfileContent() { position: "relative", display: "flex", }} + title={game.title} className={styles.game} > - - onSearch(event.target.value)} - onFocus={() => setIsFocused(true)} - onBlur={handleBlur} - /> - - {search && ( - - )} - + 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 70da45248..b28b9e6dd 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -1,7 +1,6 @@ import { createContext, useCallback, - useContext, useEffect, useMemo, useRef, @@ -14,12 +13,12 @@ import { useAppDispatch, useAppSelector, useDownload, + useRepacks, useUserDetails, } from "@renderer/hooks"; import type { Game, - GameRepack, GameShop, GameStats, ShopDetails, @@ -29,7 +28,6 @@ import type { import { useTranslation } from "react-i18next"; import { GameDetailsContext } from "./game-details.context.types"; import { SteamContentDescriptor } from "@shared"; -import { repacksContext } from "../repacks/repacks.context"; export const gameDetailsContext = createContext({ game: null, @@ -88,17 +86,8 @@ export function GameDetailsContextProvider({ const [showRepacksModal, setShowRepacksModal] = useState(false); const [showGameOptionsModal, setShowGameOptionsModal] = useState(false); - const [repacks, setRepacks] = useState([]); - - const { searchRepacks, isIndexingRepacks } = useContext(repacksContext); - - useEffect(() => { - if (!isIndexingRepacks) { - searchRepacks(gameTitle).then((repacks) => { - setRepacks(repacks); - }); - } - }, [game, gameTitle, isIndexingRepacks, searchRepacks]); + const { getRepacksForObjectId } = useRepacks(); + const repacks = getRepacksForObjectId(objectId); const { i18n } = useTranslation("game_details"); diff --git a/src/renderer/src/context/index.ts b/src/renderer/src/context/index.ts index 948b90b2b..8d9c5d1a6 100644 --- a/src/renderer/src/context/index.ts +++ b/src/renderer/src/context/index.ts @@ -1,5 +1,4 @@ export * from "./game-details/game-details.context"; export * from "./settings/settings.context"; export * from "./user-profile/user-profile.context"; -export * from "./repacks/repacks.context"; export * from "./cloud-sync/cloud-sync.context"; diff --git a/src/renderer/src/context/repacks/repacks.context.tsx b/src/renderer/src/context/repacks/repacks.context.tsx deleted file mode 100644 index cddbb2099..000000000 --- a/src/renderer/src/context/repacks/repacks.context.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import type { GameRepack } from "@types"; -import { createContext, useCallback, useEffect, useState } from "react"; - -import { repacksWorker } from "@renderer/workers"; - -export interface RepacksContext { - searchRepacks: (query: string) => Promise; - indexRepacks: () => void; - isIndexingRepacks: boolean; -} - -export const repacksContext = createContext({ - searchRepacks: async () => [] as GameRepack[], - indexRepacks: () => {}, - isIndexingRepacks: false, -}); - -const { Provider } = repacksContext; -export const { Consumer: RepacksContextConsumer } = repacksContext; - -export interface RepacksContextProps { - children: React.ReactNode; -} - -export function RepacksContextProvider({ children }: RepacksContextProps) { - const [isIndexingRepacks, setIsIndexingRepacks] = useState(true); - - const searchRepacks = useCallback(async (query: string) => { - return new Promise((resolve) => { - const channelId = crypto.randomUUID(); - repacksWorker.postMessage([channelId, query]); - - const channel = new BroadcastChannel(`repacks:search:${channelId}`); - channel.onmessage = (event: MessageEvent) => { - resolve(event.data); - channel.close(); - }; - - return []; - }); - }, []); - - const indexRepacks = useCallback(() => { - setIsIndexingRepacks(true); - repacksWorker.postMessage("INDEX_REPACKS"); - - repacksWorker.onmessage = () => { - setIsIndexingRepacks(false); - }; - }, []); - - useEffect(() => { - indexRepacks(); - }, [indexRepacks]); - - return ( - - {children} - - ); -} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 93c423e0d..c809b4b3e 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -1,10 +1,8 @@ import type { CatalogueCategory } from "@shared"; import type { AppUpdaterEvent, - CatalogueEntry, Game, LibraryGame, - GameRepack, GameShop, HowLongToBeatCategory, ShopDetails, @@ -13,7 +11,6 @@ import type { UserPreferences, StartGameDownloadPayload, RealDebridUser, - DownloadSource, UserProfile, FriendRequest, FriendRequestAction, @@ -30,6 +27,7 @@ import type { LudusaviBackup, UserAchievement, ComparedAchievements, + CatalogueSearchPayload, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type { DiskSpace } from "check-disk-space"; @@ -51,8 +49,8 @@ declare global { ) => () => Electron.IpcRenderer; /* Catalogue */ - searchGames: (query: string) => Promise; - getCatalogue: (category: CatalogueCategory) => Promise; + searchGames: (payload: CatalogueSearchPayload) => Promise; + getCatalogue: (category: CatalogueCategory) => Promise; getGameShopDetails: ( objectId: string, shop: GameShop, @@ -63,8 +61,6 @@ declare global { objectId: string, shop: GameShop ) => Promise; - getGames: (take?: number, skip?: number) => Promise; - searchGameRepacks: (query: string) => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; getTrendingGames: () => Promise; onUpdateAchievements: ( @@ -118,8 +114,9 @@ declare global { authenticateRealDebrid: (apiToken: string) => Promise; /* Download sources */ - getDownloadSources: () => Promise; - deleteDownloadSource: (id: number) => Promise; + putDownloadSource: ( + objectIds: string[] + ) => Promise<{ fingerprint: string }>; /* Hardware */ getDiskFreeSpace: (path: string) => Promise; diff --git a/src/renderer/src/dexie.ts b/src/renderer/src/dexie.ts index f940e13bd..78e3a92e9 100644 --- a/src/renderer/src/dexie.ts +++ b/src/renderer/src/dexie.ts @@ -21,11 +21,10 @@ export interface CatalogueCache { export const db = new Dexie("Hydra"); -db.version(5).stores({ - repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`, - downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`, +db.version(8).stores({ + repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, objectIds, createdAt, updatedAt`, + downloadSources: `++id, url, name, etag, objectIds, downloadCount, status, fingerprint, createdAt, updatedAt`, howLongToBeatEntries: `++id, categories, [shop+objectId], createdAt, updatedAt`, - catalogueCache: `++id, category, games, createdAt, updatedAt, expiresAt`, }); export const downloadSourcesTable = db.table("downloadSources"); @@ -34,6 +33,4 @@ export const howLongToBeatEntriesTable = db.table( "howLongToBeatEntries" ); -export const catalogueCacheTable = db.table("catalogueCache"); - db.open(); diff --git a/src/renderer/src/features/catalogue-search.ts b/src/renderer/src/features/catalogue-search.ts new file mode 100644 index 000000000..90fd5051a --- /dev/null +++ b/src/renderer/src/features/catalogue-search.ts @@ -0,0 +1,37 @@ +import { createSlice } from "@reduxjs/toolkit"; +import type { PayloadAction } from "@reduxjs/toolkit"; + +import type { CatalogueSearchPayload } from "@types"; + +export interface CatalogueSearchState { + value: CatalogueSearchPayload; +} + +const initialState: CatalogueSearchState = { + value: { + title: "", + downloadSourceFingerprints: [], + tags: [], + publishers: [], + genres: [], + developers: [], + }, +}; + +export const catalogueSearchSlice = createSlice({ + name: "catalogueSearch", + initialState, + reducers: { + setSearch: ( + state, + action: PayloadAction> + ) => { + state.value = { ...state.value, ...action.payload }; + }, + clearSearch: (state) => { + state.value = initialState.value; + }, + }, +}); + +export const { setSearch } = catalogueSearchSlice.actions; diff --git a/src/renderer/src/features/index.ts b/src/renderer/src/features/index.ts index fdc23e682..e3c1c7a6a 100644 --- a/src/renderer/src/features/index.ts +++ b/src/renderer/src/features/index.ts @@ -1,4 +1,3 @@ -export * from "./search-slice"; export * from "./library-slice"; export * from "./use-preferences-slice"; export * from "./download-slice"; @@ -6,3 +5,5 @@ export * from "./window-slice"; export * from "./toast-slice"; export * from "./user-details-slice"; export * from "./running-game-slice"; +export * from "./repacks-slice"; +export * from "./catalogue-search"; diff --git a/src/renderer/src/features/repacks-slice.ts b/src/renderer/src/features/repacks-slice.ts new file mode 100644 index 000000000..cb96ba69d --- /dev/null +++ b/src/renderer/src/features/repacks-slice.ts @@ -0,0 +1,24 @@ +import { createSlice } from "@reduxjs/toolkit"; +import type { PayloadAction } from "@reduxjs/toolkit"; + +import type { GameRepack } from "@types"; + +export interface RepacksState { + value: GameRepack[]; +} + +const initialState: RepacksState = { + value: [], +}; + +export const repacksSlice = createSlice({ + name: "repacks", + initialState, + reducers: { + setRepacks: (state, action: PayloadAction) => { + state.value = action.payload; + }, + }, +}); + +export const { setRepacks } = repacksSlice.actions; diff --git a/src/renderer/src/features/search-slice.ts b/src/renderer/src/features/search-slice.ts deleted file mode 100644 index 2817a1e12..000000000 --- a/src/renderer/src/features/search-slice.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import type { PayloadAction } from "@reduxjs/toolkit"; - -export interface SearchState { - value: string; -} - -const initialState: SearchState = { - value: "", -}; - -export const searchSlice = createSlice({ - name: "search", - initialState, - reducers: { - setSearch: (state, action: PayloadAction) => { - state.value = action.payload; - }, - clearSearch: (state) => { - state.value = ""; - }, - }, -}); - -export const { setSearch, clearSearch } = searchSlice.actions; diff --git a/src/renderer/src/hooks/index.ts b/src/renderer/src/hooks/index.ts index 910e7a3cb..97f519efd 100644 --- a/src/renderer/src/hooks/index.ts +++ b/src/renderer/src/hooks/index.ts @@ -5,3 +5,4 @@ export * from "./use-toast"; export * from "./redux"; export * from "./use-user-details"; export * from "./use-format"; +export * from "./use-repacks"; diff --git a/src/renderer/src/hooks/use-format.ts b/src/renderer/src/hooks/use-format.ts index 75e3a78bd..dbf5f0dc4 100644 --- a/src/renderer/src/hooks/use-format.ts +++ b/src/renderer/src/hooks/use-format.ts @@ -10,5 +10,5 @@ export function useFormat() { }); }, [i18n.language]); - return { numberFormatter }; + return { numberFormatter, formatNumber: numberFormatter.format }; } diff --git a/src/renderer/src/hooks/use-repacks.ts b/src/renderer/src/hooks/use-repacks.ts new file mode 100644 index 000000000..e55a2036c --- /dev/null +++ b/src/renderer/src/hooks/use-repacks.ts @@ -0,0 +1,26 @@ +import { repacksTable } from "@renderer/dexie"; +import { setRepacks } from "@renderer/features"; +import { useCallback } from "react"; +import { RootState } from "@renderer/store"; +import { useSelector } from "react-redux"; +import { useAppDispatch } from "./redux"; + +export function useRepacks() { + const dispatch = useAppDispatch(); + const repacks = useSelector((state: RootState) => state.repacks.value); + + const getRepacksForObjectId = useCallback( + (objectId: string) => { + return repacks.filter((repack) => repack.objectIds.includes(objectId)); + }, + [repacks] + ); + + const updateRepacks = useCallback(() => { + repacksTable.toArray().then((repacks) => { + dispatch(setRepacks(repacks)); + }); + }, [dispatch]); + + return { getRepacksForObjectId, updateRepacks }; +} diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index f2e326c36..221c27269 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -18,7 +18,6 @@ import { store } from "./store"; import resources from "@locales"; -import { RepacksContextProvider } from "./context"; import { SuspenseWrapper } from "./components"; import { logger } from "./logger"; import { addCookieInterceptor } from "./cookies"; @@ -28,7 +27,6 @@ const GameDetails = React.lazy( () => import("./pages/game-details/game-details") ); const Downloads = React.lazy(() => import("./pages/downloads/downloads")); -const SearchResults = React.lazy(() => import("./pages/home/search-results")); const Settings = React.lazy(() => import("./pages/settings/settings")); const Catalogue = React.lazy(() => import("./pages/catalogue/catalogue")); const Profile = React.lazy(() => import("./pages/profile/profile")); @@ -64,43 +62,37 @@ i18n ReactDOM.createRoot(document.getElementById("root")!).render( - - - - }> - } /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - - - - + + + }> + } /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + + + ); diff --git a/src/renderer/src/pages/catalogue/catalogue.scss b/src/renderer/src/pages/catalogue/catalogue.scss new file mode 100644 index 000000000..ecf99931a --- /dev/null +++ b/src/renderer/src/pages/catalogue/catalogue.scss @@ -0,0 +1,114 @@ +@use "../../scss/globals.scss"; + +@keyframes gradientBorder { + 0% { + border-image-source: linear-gradient(0deg, #16b195 50%, #3e62c0 100%); + } + 50% { + border-image-source: linear-gradient(90deg, #3e62c0 50%, #16b195 100%); + } + 100% { + border-image-source: linear-gradient(180deg, #16b195 50%, #3e62c0 100%); + } +} + +.catalogue { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + width: 100%; + padding: 16px; + + &__search-container { + background-color: globals.$dark-background-color; + display: inline-flex; + transition: all ease 0.2s; + width: 200px; + align-items: center; + border-radius: 8px; + border: 1px solid globals.$border-color; + height: 40px; + + &:hover { + border-color: rgba(255, 255, 255, 0.5); + } + + &--focused { + width: 250px; + border-color: #dadbe1; + } + } + + &__search-icon-button { + color: inherit; + cursor: pointer; + transition: all ease 0.2s; + padding: 8px; + + &:hover { + color: #dadbe1; + } + } + + &__search-clear-button { + color: inherit; + cursor: pointer; + transition: all ease 0.2s; + padding: 8px; + + &:hover { + color: #dadbe1; + } + } + + &__search-input { + background-color: transparent; + border: none; + width: 100%; + height: 100%; + outline: none; + color: #dadbe1; + cursor: default; + font-family: inherit; + text-overflow: ellipsis; + } + + &__game-item { + background-color: globals.$dark-background-color; + width: 100%; + color: #fff; + display: flex; + align-items: center; + overflow: hidden; + position: relative; + border-radius: 4px; + border: 1px solid globals.$border-color; + cursor: pointer; + gap: 12px; + transition: all ease 0.2s; + + &:hover { + background-color: rgba(255, 255, 255, 0.05); + } + } + + &__filters-container { + width: 250px; + min-width: 250px; + max-width: 250px; + background-color: globals.$dark-background-color; + border-radius: 4px; + padding: 16px; + border: 1px solid globals.$border-color; + } + + &__ai-recommendations-button-text { + font-weight: 400; + color: #fff; + background: linear-gradient(0deg, #16b195 50%, #3e62c0 100%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: bold; + } +} diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index ff8f5c001..b2e4588a0 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -1,114 +1,374 @@ -import { Button, GameCard } from "@renderer/components"; -import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; -import { useTranslation } from "react-i18next"; +import { Badge, Button } from "@renderer/components"; -import type { CatalogueEntry } from "@types"; +import type { DownloadSource } from "@types"; -import { clearSearch } from "@renderer/features"; -import { useAppDispatch } from "@renderer/hooks"; -import { useEffect, useRef, useState } from "react"; -import { useNavigate, useSearchParams } from "react-router-dom"; -import * as styles from "../home/home.css"; -import { ArrowLeftIcon, ArrowRightIcon } from "@primer/octicons-react"; -import { buildGameDetailsPath } from "@renderer/helpers"; +import cn from "classnames"; + +import { useAppDispatch, useAppSelector, useRepacks } from "@renderer/hooks"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { LightBulbIcon, SearchIcon, XIcon } from "@primer/octicons-react"; + +import "./catalogue.scss"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; +import { downloadSourcesTable } from "@renderer/dexie"; +import { steamUrlBuilder } from "@shared"; +import { buildGameDetailsPath } from "@renderer/helpers"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { FilterSection } from "./filter-section"; +import { setSearch } from "@renderer/features"; +import { useTranslation } from "react-i18next"; export default function Catalogue() { + const inputRef = useRef(null); + + const [focused, setFocused] = useState(false); + + const [searchParams] = useSearchParams(); + const search = searchParams.get("search"); + + const navigate = useNavigate(); + + const [downloadSources, setDownloadSources] = useState([]); + const [games, setGames] = useState([]); + + const filters = useAppSelector((state) => state.catalogueSearch.value); + const dispatch = useAppDispatch(); const { t } = useTranslation("catalogue"); - const [searchResults, setSearchResults] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - const contentRef = useRef(null); + const { getRepacksForObjectId } = useRepacks(); - const navigate = useNavigate(); + useEffect(() => { + setGames([]); - const [searchParams] = useSearchParams(); - const skip = Number(searchParams.get("skip") ?? 0); + window.electron.searchGames(filters).then((games) => { + setGames(games); + }); + }, [filters]); - const handleGameClick = (game: CatalogueEntry) => { - dispatch(clearSearch()); - navigate(buildGameDetailsPath(game)); - }; + const gamesWithRepacks = useMemo(() => { + return games.map((game) => { + const repacks = getRepacksForObjectId(game.objectId); + const uniqueRepackers = Array.from( + new Set(repacks.map((repack) => repack.repacker)) + ); + return { ...game, repacks: uniqueRepackers }; + }); + }, [games, getRepacksForObjectId]); useEffect(() => { - if (contentRef.current) contentRef.current.scrollTop = 0; - setIsLoading(true); - setSearchResults([]); - - window.electron - .getGames(24, skip) - .then((results) => { - return new Promise((resolve) => { - setTimeout(() => { - setSearchResults(results); - resolve(null); - }, 500); - }); - }) - .finally(() => { - setIsLoading(false); - }); - }, [dispatch, skip, searchParams]); - - const handleNextPage = () => { - const params = new URLSearchParams({ - skip: String(skip + 24), + downloadSourcesTable.toArray().then((sources) => { + setDownloadSources(sources); }); + }, [getRepacksForObjectId]); + + const focusInput = useCallback(() => { + setFocused(true); + inputRef.current?.focus(); + }, []); - navigate(`/catalogue?${params.toString()}`); - }; + const onSearch = useCallback( + (value: string) => { + dispatch(setSearch({ title: value })); + }, + [dispatch] + ); + + useEffect(() => { + if (search) { + focusInput(); + } + }, [search, focusInput]); return ( - -
+
- - - -
- -
-
- {isLoading && - Array.from({ length: 12 }).map((_, index) => ( - - ))} - - {!isLoading && searchResults.length > 0 && ( - <> - {searchResults.map((game) => ( - handleGameClick(game)} - /> - ))} - + + + onSearch(event.target.value)} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} + /> + + {filters.title && ( + )} -
-
-
+ + + +
+
+ {filters.downloadSourceFingerprints.map((fingerprint) => ( + + { + downloadSources.find( + (source) => source.fingerprint === fingerprint + )!.name + } + + ))} +
+ + {/* */} +
+ +
+
+ {gamesWithRepacks.map((game, i) => ( + + ))} + +
+ + +
+
+ +
+ + +
+ { + if (filters.genres.includes(value)) { + dispatch( + setSearch({ + genres: filters.genres.filter((genre) => genre !== value), + }) + ); + } else { + dispatch(setSearch({ genres: [...filters.genres, value] })); + } + }} + items={[ + "Action", + "Strategy", + "RPG", + "Casual", + "Racing", + "Sports", + "Indie", + "Adventure", + "Simulation", + "Massively Multiplayer", + "Free to Play", + "Accounting", + "Animation & Modeling", + "Audio Production", + "Design & Illustration", + "Education", + "Photo Editing", + "Software Training", + "Utilities", + "Video Production", + "Web Publishing", + "Game Development", + "Early Access", + "Sexual Content", + "Nudity", + "Violent", + "Gore", + "Documentary", + "Tutorial", + ].map((genre) => ({ + label: genre, + value: genre, + checked: filters.genres.includes(genre), + }))} + /> + + { + if (filters.tags.includes(value)) { + dispatch( + setSearch({ + tags: filters.tags.filter((tag) => tag !== value), + }) + ); + } else { + dispatch(setSearch({ tags: [...filters.tags, value] })); + } + }} + items={[ + "Action", + "Strategy", + "RPG", + "Casual", + "Racing", + "Sports", + "Indie", + "Adventure", + "Simulation", + "Massively Multiplayer", + "Free to Play", + "Accounting", + "Animation & Modeling", + "Audio Production", + "Design & Illustration", + "Education", + "Photo Editing", + "Software Training", + "Utilities", + "Video Production", + "Web Publishing", + "Game Development", + "Early Access", + "Sexual Content", + "Nudity", + "Violent", + "Gore", + "Documentary", + "Tutorial", + ].map((genre) => ({ + label: genre, + value: genre, + checked: filters.tags.includes(genre), + }))} + /> + + { + if (filters.downloadSourceFingerprints.includes(value)) { + dispatch( + setSearch({ + downloadSourceFingerprints: + filters.downloadSourceFingerprints.filter( + (fingerprint) => fingerprint !== value + ), + }) + ); + } else { + dispatch( + setSearch({ + downloadSourceFingerprints: [ + ...filters.downloadSourceFingerprints, + value, + ], + }) + ); + } + }} + items={downloadSources.map((downloadSource) => ({ + label: `${downloadSource.name} (${downloadSource.objectIds.length})`, + value: downloadSource.fingerprint, + checked: filters.downloadSourceFingerprints.includes( + downloadSource.fingerprint + ), + }))} + /> +
+
+
+ ); } diff --git a/src/renderer/src/pages/catalogue/filter-section.tsx b/src/renderer/src/pages/catalogue/filter-section.tsx new file mode 100644 index 000000000..e56f1dd63 --- /dev/null +++ b/src/renderer/src/pages/catalogue/filter-section.tsx @@ -0,0 +1,85 @@ +import { CheckboxField, TextField } from "@renderer/components"; +import { useFormat } from "@renderer/hooks"; +import { useCallback, useMemo, useState } from "react"; + +export interface FilterSectionProps { + title: string; + items: { + label: string; + value: string; + checked: boolean; + }[]; + onSelect: (value: string) => void; +} + +export function FilterSection({ title, items, onSelect }: FilterSectionProps) { + const [search, setSearch] = useState(""); + + const filteredItems = useMemo(() => { + if (items.length > 10 && search.length > 0) { + return items.filter((item) => + item.label.toLowerCase().includes(search.toLowerCase()) + ); + } + + return items.slice(0, 10); + }, [items, search]); + + const onSearch = useCallback((value: string) => { + setSearch(value); + }, []); + + const { formatNumber } = useFormat(); + + return ( +
+

+ {title} +

+ + + {formatNumber(items.length)} disponíveis + + + onSearch(e.target.value)} + value={search} + containerProps={{ style: { marginBottom: 16 } }} + theme="dark" + /> + +
+ {filteredItems.map((item) => ( +
+ onSelect(item.value)} + /> +
+ ))} + + +
+
+ ); +} diff --git a/src/renderer/src/pages/home/home.tsx b/src/renderer/src/pages/home/home.tsx index e04f53c8b..abaaba84c 100644 --- a/src/renderer/src/pages/home/home.tsx +++ b/src/renderer/src/pages/home/home.tsx @@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom"; import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; import { Button, GameCard, Hero } from "@renderer/components"; -import type { Steam250Game, CatalogueEntry } from "@types"; +import type { Steam250Game } from "@types"; import flameIconStatic from "@renderer/assets/icons/flame-static.png"; import flameIconAnimated from "@renderer/assets/icons/flame-animated.gif"; @@ -15,14 +15,6 @@ import * as styles from "./home.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { buildGameDetailsPath } from "@renderer/helpers"; import { CatalogueCategory } from "@shared"; -import { catalogueCacheTable, db } from "@renderer/dexie"; -import { add } from "date-fns"; - -const categoryCacheDurationInSeconds = { - [CatalogueCategory.Hot]: 60 * 60 * 2, - [CatalogueCategory.Weekly]: 60 * 60 * 24, - [CatalogueCategory.Achievements]: 60 * 60 * 24, -}; export default function Home() { const { t } = useTranslation("home"); @@ -36,9 +28,7 @@ export default function Home() { CatalogueCategory.Hot ); - const [catalogue, setCatalogue] = useState< - Record - >({ + const [catalogue, setCatalogue] = useState>({ [CatalogueCategory.Hot]: [], [CatalogueCategory.Weekly]: [], [CatalogueCategory.Achievements]: [], @@ -46,37 +36,11 @@ export default function Home() { const getCatalogue = useCallback(async (category: CatalogueCategory) => { try { - const catalogueCache = await catalogueCacheTable - .where("expiresAt") - .above(new Date()) - .and((cache) => cache.category === category) - .first(); - setCurrentCatalogueCategory(category); setIsLoading(true); - if (catalogueCache) - return setCatalogue((prev) => ({ - ...prev, - [category]: catalogueCache.games, - })); - const catalogue = await window.electron.getCatalogue(category); - db.transaction("rw", catalogueCacheTable, async () => { - await catalogueCacheTable.where("category").equals(category).delete(); - - await catalogueCacheTable.add({ - category, - games: catalogue, - createdAt: new Date(), - updatedAt: new Date(), - expiresAt: add(new Date(), { - seconds: categoryCacheDurationInSeconds[category], - }), - }); - }); - setCatalogue((prev) => ({ ...prev, [category]: catalogue })); } finally { setIsLoading(false); diff --git a/src/renderer/src/pages/home/search-results.tsx b/src/renderer/src/pages/home/search-results.tsx deleted file mode 100644 index d86a362a0..000000000 --- a/src/renderer/src/pages/home/search-results.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { GameCard } from "@renderer/components"; -import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; - -import type { CatalogueEntry } from "@types"; - -import type { DebouncedFunc } from "lodash"; -import { debounce } from "lodash"; - -import { InboxIcon, SearchIcon } from "@primer/octicons-react"; -import { clearSearch, setSearch } from "@renderer/features"; -import { useAppDispatch } from "@renderer/hooks"; -import { useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate, useSearchParams } from "react-router-dom"; -import * as styles from "./home.css"; -import { buildGameDetailsPath } from "@renderer/helpers"; - -import { vars } from "@renderer/theme.css"; - -export default function SearchResults() { - const dispatch = useAppDispatch(); - - const { t } = useTranslation("home"); - const [searchParams] = useSearchParams(); - - const [searchResults, setSearchResults] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [showTypingMessage, setShowTypingMessage] = useState(false); - - const debouncedFunc = useRef void> | null>(null); - const abortControllerRef = useRef(null); - - const navigate = useNavigate(); - - const handleGameClick = (game: CatalogueEntry) => { - dispatch(clearSearch()); - navigate(buildGameDetailsPath(game)); - }; - - useEffect(() => { - dispatch(setSearch(searchParams.get("query") ?? "")); - }, [dispatch, searchParams]); - - useEffect(() => { - setIsLoading(true); - if (debouncedFunc.current) debouncedFunc.current.cancel(); - if (abortControllerRef.current) abortControllerRef.current.abort(); - - const abortController = new AbortController(); - abortControllerRef.current = abortController; - - debouncedFunc.current = debounce(() => { - const query = searchParams.get("query") ?? ""; - - if (query.length < 3) { - setIsLoading(false); - setShowTypingMessage(true); - setSearchResults([]); - return; - } - - setShowTypingMessage(false); - window.electron - .searchGames(query) - .then((results) => { - if (abortController.signal.aborted) return; - - setSearchResults(results); - setIsLoading(false); - }) - .catch(() => { - setIsLoading(false); - }); - }, 500); - - debouncedFunc.current(); - }, [searchParams, dispatch]); - - const noResultsContent = () => { - if (isLoading) return null; - - if (showTypingMessage) { - return ( -
- - -

{t("start_typing")}

-
- ); - } - - if (searchResults.length === 0) { - return ( -
- - -

{t("no_results")}

-
- ); - } - - return null; - }; - - return ( - -
-
- {isLoading && - Array.from({ length: 12 }).map((_, index) => ( - - ))} - - {!isLoading && searchResults.length > 0 && ( - <> - {searchResults.map((game) => ( - handleGameClick(game)} - /> - ))} - - )} -
- - {noResultsContent()} -
-
- ); -} diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index 5b05d5b8c..c2b94b7f5 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -100,17 +100,30 @@ export function AddDownloadSourceModal({ } }, [visible, clearErrors, handleSubmit, onSubmit, setValue, sourceUrl]); - const handleAddDownloadSource = async () => { - setIsLoading(true); + const putDownloadSource = async () => { + const downloadSource = await downloadSourcesTable.where({ url }).first(); + if (!downloadSource) return; + + window.electron + .putDownloadSource(downloadSource.objectIds) + .then(({ fingerprint }) => { + downloadSourcesTable.update(downloadSource.id, { fingerprint }); + }); + }; + const handleAddDownloadSource = async () => { if (validationResult) { + setIsLoading(true); + const channel = new BroadcastChannel(`download_sources:import:${url}`); downloadSourcesWorker.postMessage(["IMPORT_DOWNLOAD_SOURCE", url]); - channel.onmessage = () => { + channel.onmessage = async () => { setIsLoading(false); + putDownloadSource(); + onClose(); onAddDownloadSource(); channel.close(); diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index d2f45329f..94846fc57 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -7,10 +7,10 @@ import * as styles from "./settings-download-sources.css"; import type { DownloadSource } from "@types"; import { NoEntryIcon, PlusCircleIcon, SyncIcon } from "@primer/octicons-react"; import { AddDownloadSourceModal } from "./add-download-source-modal"; -import { useToast } from "@renderer/hooks"; +import { useRepacks, useToast } from "@renderer/hooks"; import { DownloadSourceStatus } from "@shared"; import { SPACING_UNIT } from "@renderer/theme.css"; -import { repacksContext, settingsContext } from "@renderer/context"; +import { settingsContext } from "@renderer/context"; import { downloadSourcesTable } from "@renderer/dexie"; import { downloadSourcesWorker } from "@renderer/workers"; @@ -28,7 +28,7 @@ export function SettingsDownloadSources() { const { t } = useTranslation("settings"); const { showSuccessToast } = useToast(); - const { indexRepacks } = useContext(repacksContext); + const { updateRepacks } = useRepacks(); const getDownloadSources = async () => { await downloadSourcesTable @@ -57,16 +57,16 @@ export function SettingsDownloadSources() { showSuccessToast(t("removed_download_source")); getDownloadSources(); - indexRepacks(); setIsRemovingDownloadSource(false); channel.close(); + updateRepacks(); }; }; const handleAddDownloadSource = async () => { - indexRepacks(); await getDownloadSources(); showSuccessToast(t("added_download_source")); + updateRepacks(); }; const syncDownloadSources = async () => { @@ -82,6 +82,7 @@ export function SettingsDownloadSources() { getDownloadSources(); setIsSyncingDownloadSources(false); channel.close(); + updateRepacks(); }; }; diff --git a/src/renderer/src/scss/globals.scss b/src/renderer/src/scss/globals.scss index 66478981c..1dc4144a2 100644 --- a/src/renderer/src/scss/globals.scss +++ b/src/renderer/src/scss/globals.scss @@ -6,7 +6,7 @@ $body-color: #8e919b; $border-color: #424244; $success-color: #1c9749; -$danger-color: #e11d48; +$danger-color: #801d1e; $warning-color: #ffc107; $disabled-opacity: 0.5; diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts index 0f2bee9f8..36b60309e 100644 --- a/src/renderer/src/store.ts +++ b/src/renderer/src/store.ts @@ -3,16 +3,16 @@ import { downloadSlice, windowSlice, librarySlice, - searchSlice, userPreferencesSlice, toastSlice, userDetailsSlice, gameRunningSlice, + repacksSlice, + catalogueSearchSlice, } from "@renderer/features"; export const store = configureStore({ reducer: { - search: searchSlice.reducer, window: windowSlice.reducer, library: librarySlice.reducer, userPreferences: userPreferencesSlice.reducer, @@ -20,6 +20,8 @@ export const store = configureStore({ toast: toastSlice.reducer, userDetails: userDetailsSlice.reducer, gameRunning: gameRunningSlice.reducer, + repacks: repacksSlice.reducer, + catalogueSearch: catalogueSearchSlice.reducer, }, }); diff --git a/src/renderer/src/workers/download-sources.worker.ts b/src/renderer/src/workers/download-sources.worker.ts index 29ab7d87f..a847520a8 100644 --- a/src/renderer/src/workers/download-sources.worker.ts +++ b/src/renderer/src/workers/download-sources.worker.ts @@ -2,7 +2,10 @@ import { db, downloadSourcesTable, repacksTable } from "@renderer/dexie"; import { z } from "zod"; import axios, { AxiosError, AxiosHeaders } from "axios"; -import { DownloadSourceStatus } from "@shared"; +import { DownloadSourceStatus, formatName, pipe } from "@shared"; +import { GameRepack } from "@types"; + +const formatRepackName = pipe((name) => name.replace("[DL]", ""), formatName); export const downloadSourceSchema = z.object({ name: z.string().max(255), @@ -22,6 +25,64 @@ type Payload = | ["VALIDATE_DOWNLOAD_SOURCE", string] | ["SYNC_DOWNLOAD_SOURCES", string]; +export type SteamGamesByLetter = Record; + +const addNewDownloads = async ( + downloadSource: { id: number; name: string }, + downloads: z.infer["downloads"], + steamGames: SteamGamesByLetter +) => { + const now = new Date(); + + const results = [] as (Omit & { + downloadSourceId: number; + })[]; + + const objectIdsOnSource = new Set(); + + for (const download of downloads) { + const formattedTitle = formatRepackName(download.title); + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; + + const gamesInSteam = games.filter((game) => + formattedTitle.startsWith(game.name) + ); + + if (gamesInSteam.length === 0) continue; + + for (const game of gamesInSteam) { + objectIdsOnSource.add(String(game.id)); + } + + results.push({ + objectIds: gamesInSteam.map((game) => String(game.id)), + title: download.title, + uris: download.uris, + fileSize: download.fileSize, + repacker: downloadSource.name, + uploadDate: download.uploadDate, + downloadSourceId: downloadSource.id, + createdAt: now, + updatedAt: now, + }); + } + + await repacksTable.bulkAdd(results); + + await downloadSourcesTable.update(downloadSource.id, { + objectIds: Array.from(objectIdsOnSource), + }); +}; + +const getSteamGames = async () => { + const response = await axios.get( + "https://assets.hydralauncher.gg/steam-games-by-letter.json" + ); + + return response.data; +}; + self.onmessage = async (event: MessageEvent) => { const [type, data] = event.data; @@ -55,6 +116,8 @@ self.onmessage = async (event: MessageEvent) => { const response = await axios.get>(data); + const steamGames = await getSteamGames(); + await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { const now = new Date(); @@ -70,18 +133,11 @@ self.onmessage = async (event: MessageEvent) => { const downloadSource = await downloadSourcesTable.get(id); - const repacks = response.data.downloads.map((download) => ({ - title: download.title, - uris: download.uris, - fileSize: download.fileSize, - repacker: response.data.name, - uploadDate: download.uploadDate, - downloadSourceId: downloadSource!.id, - createdAt: now, - updatedAt: now, - })); - - await repacksTable.bulkAdd(repacks); + await addNewDownloads( + downloadSource, + response.data.downloads, + steamGames + ); }); const channel = new BroadcastChannel(`download_sources:import:${data}`); @@ -110,6 +166,8 @@ self.onmessage = async (event: MessageEvent) => { const source = downloadSourceSchema.parse(response.data); + const steamGames = await getSteamGames(); + await db.transaction( "rw", repacksTable, @@ -121,29 +179,16 @@ self.onmessage = async (event: MessageEvent) => { status: DownloadSourceStatus.UpToDate, }); - const now = new Date(); - - const repacks = source.downloads - .filter( - (download) => - !existingRepacks.some( - (repack) => repack.title === download.title - ) - ) - .map((download) => ({ - title: download.title, - uris: download.uris, - fileSize: download.fileSize, - repacker: source.name, - uploadDate: download.uploadDate, - downloadSourceId: downloadSource.id, - createdAt: now, - updatedAt: now, - })); + const repacks = source.downloads.filter( + (download) => + !existingRepacks.some( + (repack) => repack.title === download.title + ) + ); - newRepacksCount += repacks.length; + await addNewDownloads(downloadSource, repacks, steamGames); - await repacksTable.bulkAdd(repacks); + newRepacksCount += repacks.length; } ); } catch (err: unknown) { diff --git a/src/renderer/src/workers/index.ts b/src/renderer/src/workers/index.ts index b8141a8f8..39367894f 100644 --- a/src/renderer/src/workers/index.ts +++ b/src/renderer/src/workers/index.ts @@ -1,5 +1,3 @@ -import RepacksWorker from "./repacks.worker?worker"; import DownloadSourcesWorker from "./download-sources.worker?worker"; -export const repacksWorker = new RepacksWorker(); export const downloadSourcesWorker = new DownloadSourcesWorker(); diff --git a/src/renderer/src/workers/repacks.worker.ts b/src/renderer/src/workers/repacks.worker.ts deleted file mode 100644 index c23945101..000000000 --- a/src/renderer/src/workers/repacks.worker.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { repacksTable } from "@renderer/dexie"; -import { formatName } from "@shared"; -import type { GameRepack } from "@types"; -import flexSearch from "flexsearch"; - -interface SerializedGameRepack extends Omit { - uris: string; -} - -const state = { - repacks: [] as SerializedGameRepack[], - index: null as flexSearch.Index | null, -}; - -self.onmessage = async ( - event: MessageEvent<[string, string] | "INDEX_REPACKS"> -) => { - if (event.data === "INDEX_REPACKS") { - state.index = new flexSearch.Index(); - - repacksTable - .toCollection() - .sortBy("uploadDate") - .then((results) => { - state.repacks = results.reverse(); - - for (let i = 0; i < state.repacks.length; i++) { - const repack = state.repacks[i]; - const formattedTitle = formatName(repack.title); - state.index!.add(i, formattedTitle); - } - - self.postMessage("INDEXING_COMPLETE"); - }); - } else { - const [requestId, query] = event.data; - - const results = state.index!.search(formatName(query)).map((index) => { - const repack = state.repacks.at(index as number) as SerializedGameRepack; - - return { - ...repack, - uris: [...repack.uris, repack.magnet].filter(Boolean), - }; - }); - - const channel = new BroadcastChannel(`repacks:search:${requestId}`); - - channel.postMessage(results); - } -}; diff --git a/src/types/index.ts b/src/types/index.ts index 434a15e7d..48756fcbe 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,13 +16,10 @@ export type FriendRequestAction = "ACCEPTED" | "REFUSED" | "CANCEL"; export interface GameRepack { id: number; title: string; - /** - * @deprecated Use uris instead - */ - magnet: string; uris: string[]; repacker: string; fileSize: string | null; + objectIds: string[]; uploadDate: Date | string | null; createdAt: Date; updatedAt: Date; @@ -77,15 +74,6 @@ export interface TorrentFile { length: number; } -/* Used by the catalogue */ -export interface CatalogueEntry { - objectId: string; - shop: GameShop; - title: string; - /* Epic Games covers cannot be guessed with objectID */ - cover: string; -} - export interface UserGame { objectId: string; shop: GameShop; @@ -301,6 +289,7 @@ export interface DownloadSource { repackCount: number; status: DownloadSourceStatus; downloadCount: number; + fingerprint: string; etag: string | null; createdAt: Date; updatedAt: Date; @@ -374,6 +363,15 @@ export interface ComparedAchievements { }[]; } +export interface CatalogueSearchPayload { + title: string; + downloadSourceFingerprints: string[]; + tags: number[]; + publishers: string[]; + genres: string[]; + developers: string[]; +} + export * from "./steam.types"; export * from "./real-debrid.types"; export * from "./ludusavi.types"; diff --git a/yarn.lock b/yarn.lock index b6b2d3b9b..c8bbd83ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,608 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@aws-crypto/crc32@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1" + integrity sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/crc32c@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz#4e34aab7f419307821509a98b9b08e84e0c1917e" + integrity sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/sha1-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz#b0ee2d2821d3861f017e965ef3b4cb38e3b6a0f4" + integrity sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg== + dependencies: + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" + integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== + dependencies: + "@aws-crypto/sha256-js" "^5.2.0" + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz#c4fdb773fdbed9a664fc1a95724e206cf3860042" + integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/supports-web-crypto@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz#a1e399af29269be08e695109aa15da0a07b5b5fb" + integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== + dependencies: + tslib "^2.6.2" + +"@aws-crypto/util@5.2.0", "@aws-crypto/util@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.2.0.tgz#71284c9cffe7927ddadac793c14f14886d3876da" + integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== + dependencies: + "@aws-sdk/types" "^3.222.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-s3@^3.705.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.712.0.tgz#2911e9c7a65fd9e5679926fdb838311a0b29e37c" + integrity sha512-Hq1IIwOFutmHtTz3mROR1XhTDL8rxcYbYw3ajjgeMJB5tjcvodpfkfz/L4dxXZMwqylWf6SNQNAiaGh5mlsGGQ== + dependencies: + "@aws-crypto/sha1-browser" "5.2.0" + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.712.0" + "@aws-sdk/client-sts" "3.712.0" + "@aws-sdk/core" "3.709.0" + "@aws-sdk/credential-provider-node" "3.712.0" + "@aws-sdk/middleware-bucket-endpoint" "3.709.0" + "@aws-sdk/middleware-expect-continue" "3.709.0" + "@aws-sdk/middleware-flexible-checksums" "3.709.0" + "@aws-sdk/middleware-host-header" "3.709.0" + "@aws-sdk/middleware-location-constraint" "3.709.0" + "@aws-sdk/middleware-logger" "3.709.0" + "@aws-sdk/middleware-recursion-detection" "3.709.0" + "@aws-sdk/middleware-sdk-s3" "3.709.0" + "@aws-sdk/middleware-ssec" "3.709.0" + "@aws-sdk/middleware-user-agent" "3.709.0" + "@aws-sdk/region-config-resolver" "3.709.0" + "@aws-sdk/signature-v4-multi-region" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@aws-sdk/util-endpoints" "3.709.0" + "@aws-sdk/util-user-agent-browser" "3.709.0" + "@aws-sdk/util-user-agent-node" "3.712.0" + "@aws-sdk/xml-builder" "3.709.0" + "@smithy/config-resolver" "^3.0.13" + "@smithy/core" "^2.5.5" + "@smithy/eventstream-serde-browser" "^3.0.14" + "@smithy/eventstream-serde-config-resolver" "^3.0.11" + "@smithy/eventstream-serde-node" "^3.0.13" + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/hash-blob-browser" "^3.1.10" + "@smithy/hash-node" "^3.0.11" + "@smithy/hash-stream-node" "^3.1.10" + "@smithy/invalid-dependency" "^3.0.11" + "@smithy/md5-js" "^3.0.11" + "@smithy/middleware-content-length" "^3.0.13" + "@smithy/middleware-endpoint" "^3.2.5" + "@smithy/middleware-retry" "^3.0.30" + "@smithy/middleware-serde" "^3.0.11" + "@smithy/middleware-stack" "^3.0.11" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/protocol-http" "^4.1.8" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.30" + "@smithy/util-defaults-mode-node" "^3.0.30" + "@smithy/util-endpoints" "^2.1.7" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-retry" "^3.0.11" + "@smithy/util-stream" "^3.3.2" + "@smithy/util-utf8" "^3.0.0" + "@smithy/util-waiter" "^3.2.0" + tslib "^2.6.2" + +"@aws-sdk/client-sso-oidc@3.712.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.712.0.tgz#ba3c9ae1b74f3c44e406397c60c812bb9e2e98a4" + integrity sha512-xNFrG9syrG6pxUP7Ld/nu3afQ9+rbJM9qrE+wDNz4VnNZ3vLiJty4fH85zBFhOQ5OF2DIJTWsFzXGi2FYjsCMA== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.709.0" + "@aws-sdk/credential-provider-node" "3.712.0" + "@aws-sdk/middleware-host-header" "3.709.0" + "@aws-sdk/middleware-logger" "3.709.0" + "@aws-sdk/middleware-recursion-detection" "3.709.0" + "@aws-sdk/middleware-user-agent" "3.709.0" + "@aws-sdk/region-config-resolver" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@aws-sdk/util-endpoints" "3.709.0" + "@aws-sdk/util-user-agent-browser" "3.709.0" + "@aws-sdk/util-user-agent-node" "3.712.0" + "@smithy/config-resolver" "^3.0.13" + "@smithy/core" "^2.5.5" + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/hash-node" "^3.0.11" + "@smithy/invalid-dependency" "^3.0.11" + "@smithy/middleware-content-length" "^3.0.13" + "@smithy/middleware-endpoint" "^3.2.5" + "@smithy/middleware-retry" "^3.0.30" + "@smithy/middleware-serde" "^3.0.11" + "@smithy/middleware-stack" "^3.0.11" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/protocol-http" "^4.1.8" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.30" + "@smithy/util-defaults-mode-node" "^3.0.30" + "@smithy/util-endpoints" "^2.1.7" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-retry" "^3.0.11" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sso@3.712.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.712.0.tgz#9644585700f5d96a16151bdb5387755adc524db8" + integrity sha512-tBo/eW3YpZ9f3Q1qA7aA8uliNFJJX0OP7R2IUJ8t6rqVTk15wWCEPNmXzUZKgruDnKUfCaF4+r9q/Yy4fBc9PA== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.709.0" + "@aws-sdk/middleware-host-header" "3.709.0" + "@aws-sdk/middleware-logger" "3.709.0" + "@aws-sdk/middleware-recursion-detection" "3.709.0" + "@aws-sdk/middleware-user-agent" "3.709.0" + "@aws-sdk/region-config-resolver" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@aws-sdk/util-endpoints" "3.709.0" + "@aws-sdk/util-user-agent-browser" "3.709.0" + "@aws-sdk/util-user-agent-node" "3.712.0" + "@smithy/config-resolver" "^3.0.13" + "@smithy/core" "^2.5.5" + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/hash-node" "^3.0.11" + "@smithy/invalid-dependency" "^3.0.11" + "@smithy/middleware-content-length" "^3.0.13" + "@smithy/middleware-endpoint" "^3.2.5" + "@smithy/middleware-retry" "^3.0.30" + "@smithy/middleware-serde" "^3.0.11" + "@smithy/middleware-stack" "^3.0.11" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/protocol-http" "^4.1.8" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.30" + "@smithy/util-defaults-mode-node" "^3.0.30" + "@smithy/util-endpoints" "^2.1.7" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-retry" "^3.0.11" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sts@3.712.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.712.0.tgz#455daebd946369c60c7795efbd7a6b5981d0662a" + integrity sha512-gIO6BD+hkEe3GKQhbiFP0zcNQv0EkP1Cl9SOstxS+X9CeudEgVX/xEPUjyoFVkfkntPBJ1g0I1u5xOzzRExl4g== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.712.0" + "@aws-sdk/core" "3.709.0" + "@aws-sdk/credential-provider-node" "3.712.0" + "@aws-sdk/middleware-host-header" "3.709.0" + "@aws-sdk/middleware-logger" "3.709.0" + "@aws-sdk/middleware-recursion-detection" "3.709.0" + "@aws-sdk/middleware-user-agent" "3.709.0" + "@aws-sdk/region-config-resolver" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@aws-sdk/util-endpoints" "3.709.0" + "@aws-sdk/util-user-agent-browser" "3.709.0" + "@aws-sdk/util-user-agent-node" "3.712.0" + "@smithy/config-resolver" "^3.0.13" + "@smithy/core" "^2.5.5" + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/hash-node" "^3.0.11" + "@smithy/invalid-dependency" "^3.0.11" + "@smithy/middleware-content-length" "^3.0.13" + "@smithy/middleware-endpoint" "^3.2.5" + "@smithy/middleware-retry" "^3.0.30" + "@smithy/middleware-serde" "^3.0.11" + "@smithy/middleware-stack" "^3.0.11" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/protocol-http" "^4.1.8" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.30" + "@smithy/util-defaults-mode-node" "^3.0.30" + "@smithy/util-endpoints" "^2.1.7" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-retry" "^3.0.11" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/core@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.709.0.tgz#d2b3d5b90f6614e3afc109ebdcaaedbb54c2d68b" + integrity sha512-7kuSpzdOTAE026j85wq/fN9UDZ70n0OHw81vFqMWwlEFtm5IQ/MRCLKcC4HkXxTdfy1PqFlmoXxWqeBa15tujw== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/core" "^2.5.5" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/property-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/signature-v4" "^4.2.4" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/util-middleware" "^3.0.11" + fast-xml-parser "4.4.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-env@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.709.0.tgz#a7f75375d8a413f9ab2bc42f743b943da6d3362d" + integrity sha512-ZMAp9LSikvHDFVa84dKpQmow6wsg956Um20cKuioPpX2GGreJFur7oduD+tRJT6FtIOHn+64YH+0MwiXLhsaIQ== + dependencies: + "@aws-sdk/core" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/property-provider" "^3.1.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-http@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.709.0.tgz#a378cbcc4cf373cc277944f1e84e9952f3884f5d" + integrity sha512-lIS7XLwCOyJnLD70f+VIRr8DNV1HPQe9oN6aguYrhoczqz7vDiVZLe3lh714cJqq9rdxzFypK5DqKHmcscMEPQ== + dependencies: + "@aws-sdk/core" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/property-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/util-stream" "^3.3.2" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-ini@3.712.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.712.0.tgz#21d94d3fbaf5cece29bc62d56cf7f0dfb8b7d25e" + integrity sha512-sTsdQ/Fm/suqMdpjhMuss/5uKL18vcuWnNTQVrG9iGNRqZLbq65MXquwbUpgzfoUmIcH+4CrY6H2ebpTIECIag== + dependencies: + "@aws-sdk/core" "3.709.0" + "@aws-sdk/credential-provider-env" "3.709.0" + "@aws-sdk/credential-provider-http" "3.709.0" + "@aws-sdk/credential-provider-process" "3.709.0" + "@aws-sdk/credential-provider-sso" "3.712.0" + "@aws-sdk/credential-provider-web-identity" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/credential-provider-imds" "^3.2.8" + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-node@3.712.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.712.0.tgz#6f017382b1182578cf62798310f42264b652e36e" + integrity sha512-gXrHymW3rMRYORkPVQwL8Gi5Lu92F16SoZR543x03qCi7rm00oL9tRD85ACxkhprS1Wh8lUIUMNoeiwnYWTNuQ== + dependencies: + "@aws-sdk/credential-provider-env" "3.709.0" + "@aws-sdk/credential-provider-http" "3.709.0" + "@aws-sdk/credential-provider-ini" "3.712.0" + "@aws-sdk/credential-provider-process" "3.709.0" + "@aws-sdk/credential-provider-sso" "3.712.0" + "@aws-sdk/credential-provider-web-identity" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/credential-provider-imds" "^3.2.8" + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-process@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.709.0.tgz#2521f810590f0874c54cc842d3d56f455a728325" + integrity sha512-IAC+jPlGQII6jhIylHOwh3RgSobqlgL59nw2qYTURr8hMCI0Z1p5y2ee646HTVt4WeCYyzUAXfxr6YI/Vitv+Q== + dependencies: + "@aws-sdk/core" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-sso@3.712.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.712.0.tgz#d29c8c14e2460a817ed2eb7ad5d205d7914817af" + integrity sha512-8lCMxY7Lb9VK9qdlNXRJXE3W1UDVURnJZ3a4XWYNY6yr1TfQaN40mMyXX1oNlXXJtMV0szRvjM8dZj37E/ESAw== + dependencies: + "@aws-sdk/client-sso" "3.712.0" + "@aws-sdk/core" "3.709.0" + "@aws-sdk/token-providers" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-web-identity@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.709.0.tgz#c2b03541cb57ae4c7d6abdca98f99a6a56833ea6" + integrity sha512-2lbDfE0IQ6gma/7BB2JpkjW5G0wGe4AS0x80oybYAYYviJmUtIR3Cn2pXun6bnAWElt4wYKl4su7oC36rs5rNA== + dependencies: + "@aws-sdk/core" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/property-provider" "^3.1.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/middleware-bucket-endpoint@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.709.0.tgz#a69bdebfebb7b5b174d3a396f2361f5025d168f4" + integrity sha512-03+tJOd7KIZOiqWH7Z8BOfQIWkKJgjcpKOJKZ6FR2KjWGUOE1G+bo11wF4UuHQ0RmpKnApt+pQghZmSnE7WEeg== + dependencies: + "@aws-sdk/types" "3.709.0" + "@aws-sdk/util-arn-parser" "3.693.0" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-config-provider" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-expect-continue@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.709.0.tgz#a7fec776da9de32e15088badfc09d69118f5d5ab" + integrity sha512-Tbl/DFvE4rHl8lMb9IzetwK4tf5R3VeHZkvEXQalsWoK0tbEQ8kXWi7wAYO4qbE7bFVvaxKX+irjJjTxf3BrCQ== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/middleware-flexible-checksums@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.709.0.tgz#f0fb543c2db724cb43bae215ff0aea942d06a967" + integrity sha512-wbYm9tkyCaqMeU82yjaXw7V5BxCSlSLNupENW63LC7Fvyo/aQzj6LjSMHcBpR2QwjBEhXCtF47L7aQ8SPTNhdw== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@aws-crypto/crc32c" "5.2.0" + "@aws-crypto/util" "5.2.0" + "@aws-sdk/core" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/is-array-buffer" "^3.0.0" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-stream" "^3.3.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-host-header@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.709.0.tgz#f44f5c62f9bd7e5a443603fed68143d2d9725219" + integrity sha512-8gQYCYAaIw4lOCd5WYdf15Y/61MgRsAnrb2eiTl+icMlUOOzl8aOl5iDwm/Idp0oHZTflwxM4XSvGXO83PRWcw== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/middleware-location-constraint@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.709.0.tgz#4437d3d3cfbbdfca60664b1f237d600b94fd06a5" + integrity sha512-5YQWPXfZq7OE0jB2G0PP8K10GBod/YPJXb+1CfJS6FbQaglRoIm8KZmVEvJNnptSKyGtE62veeCcCQcfAUfFig== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/middleware-logger@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.709.0.tgz#b9a0b016b7ae09cb502cc4faf45964d4b5745824" + integrity sha512-jDoGSccXv9zebnpUoisjWd5u5ZPIalrmm6TjvPzZ8UqzQt3Beiz0tnQwmxQD6KRc7ADweWP5Ntiqzbw9xpVajg== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/middleware-recursion-detection@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.709.0.tgz#d7dc253d4858d496caeb12dd6cddd87b250fb98b" + integrity sha512-PObL/wLr4lkfbQ0yXUWaoCWu/jcwfwZzCjsUiXW/H6hW9b/00enZxmx7OhtJYaR6xmh/Lcx5wbhIoDCbzdv0tw== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/middleware-sdk-s3@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.709.0.tgz#b6f22c77e64760869eb06255af58376f879742b2" + integrity sha512-FwtOG9t9xsLoLOQZ6qAdsWOjx9dsO6t28IjIDV1l6Ixiu2oC0Yks7goONjJUH0IDE4pDDDGzmuq0sn1XtHhheA== + dependencies: + "@aws-sdk/core" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@aws-sdk/util-arn-parser" "3.693.0" + "@smithy/core" "^2.5.5" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/protocol-http" "^4.1.8" + "@smithy/signature-v4" "^4.2.4" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-stream" "^3.3.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-ssec@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.709.0.tgz#bbf5253cdce45ed2759a108fd924fff4b8e049d5" + integrity sha512-2muiLe7YkmlwZp2SKz+goZrDThGfRq3o0FcJF3Puc0XGmcEPEDjih537mCoTrGgcXNFlBc7YChd84r3t72ySaQ== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/middleware-user-agent@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.709.0.tgz#2a467f14b3f4a9270bcdfde32e3d4e38701aaafe" + integrity sha512-ooc9ZJvgkjPhi9q05XwSfNTXkEBEIfL4hleo5rQBKwHG3aTHvwOM7LLzhdX56QZVa6sorPBp6fwULuRDSqiQHw== + dependencies: + "@aws-sdk/core" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@aws-sdk/util-endpoints" "3.709.0" + "@smithy/core" "^2.5.5" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/region-config-resolver@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.709.0.tgz#64547b333842e5804e1793e4d6d29578c0b34a68" + integrity sha512-/NoCAMEVKAg3kBKOrNtgOfL+ECt6nrl+L7q2SyYmrcY4tVCmwuECVqewQaHc03fTnJijfKLccw0Fj+6wOCnB6w== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + tslib "^2.6.2" + +"@aws-sdk/signature-v4-multi-region@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.709.0.tgz#0c6f9d3e2978158163b63a4085356616237223c9" + integrity sha512-m0vhJEy6SLbjL11K9cHzX/ZhCIj//1GkTbYk2d4tTQFSuPyJEkjmoeHk9dYm2mJy0wH48j29OJadI1JUsR5bOw== + dependencies: + "@aws-sdk/middleware-sdk-s3" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/protocol-http" "^4.1.8" + "@smithy/signature-v4" "^4.2.4" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/token-providers@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.709.0.tgz#56305ab187660a711fd172c329dc953ca754fa80" + integrity sha512-q5Ar6k71nci43IbULFgC8a89d/3EHpmd7HvBzqVGRcHnoPwh8eZDBfbBXKH83NGwcS1qPSRYiDbVfeWPm4/1jA== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/types@3.709.0", "@aws-sdk/types@^3.222.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.709.0.tgz#f8d7ab07e253d3ed0e3b360e09fc67c7430a73b9" + integrity sha512-ArtLTMxgjf13Kfu3gWH3Ez9Q5TkDdcRZUofpKH3pMGB/C6KAbeSCtIIDKfoRTUABzyGlPyCrZdnFjKyH+ypIpg== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/util-arn-parser@3.693.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.693.0.tgz#8dae27eb822ab4f88be28bb3c0fc11f1f13d3948" + integrity sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-endpoints@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.709.0.tgz#32dfe339d78b699ada68392bbb3bec25441bae5c" + integrity sha512-Mbc7AtL5WGCTKC16IGeUTz+sjpC3ptBda2t0CcK0kMVw3THDdcSq6ZlNKO747cNqdbwUvW34oHteUiHv4/z88Q== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/types" "^3.7.2" + "@smithy/util-endpoints" "^2.1.7" + tslib "^2.6.2" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz#1160f6d055cf074ca198eb8ecf89b6311537ad6c" + integrity sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-browser@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.709.0.tgz#ad6e867bdd348923ec10ddd6c37740ce0986cd8f" + integrity sha512-/rL2GasJzdTWUURCQKFldw2wqBtY4k4kCiA2tVZSKg3y4Ey7zO34SW8ebaeCE2/xoWOyLR2/etdKyphoo4Zrtg== + dependencies: + "@aws-sdk/types" "3.709.0" + "@smithy/types" "^3.7.2" + bowser "^2.11.0" + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-node@3.712.0": + version "3.712.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.712.0.tgz#7634627775e0993eace70dea1dd915122f1a053f" + integrity sha512-26X21bZ4FWsVpqs33uOXiB60TOWQdVlr7T7XONDFL/XN7GEpUJkWuuIB4PTok6VOmh1viYcdxZQqekXPuzXexQ== + dependencies: + "@aws-sdk/middleware-user-agent" "3.709.0" + "@aws-sdk/types" "3.709.0" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@aws-sdk/xml-builder@3.709.0": + version "3.709.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.709.0.tgz#5841faa1e78afcea064557a1a56709978b325758" + integrity sha512-2GPCwlNxeHspoK/Mc8nbk9cBOkSpp3j2SJUQmFnyQK6V/pR6II2oPRyZkMomug1Rc10hqlBHByMecq4zhV2uUw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": version "7.24.2" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz" @@ -1415,6 +2017,496 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== +"@smithy/abort-controller@^3.1.9": + version "3.1.9" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-3.1.9.tgz#47d323f754136a489e972d7fd465d534d72fcbff" + integrity sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader-native@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz#39045ed278ee1b6f4c12715c7565678557274c29" + integrity sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ== + dependencies: + "@smithy/util-base64" "^3.0.0" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz#754099909957fb1986c16eb88afad75919d7129d" + integrity sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ== + dependencies: + tslib "^2.6.2" + +"@smithy/config-resolver@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-3.0.13.tgz#653643a77a33d0f5907a5e7582353886b07ba752" + integrity sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + tslib "^2.6.2" + +"@smithy/core@^2.5.5": + version "2.5.5" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-2.5.5.tgz#c75b15caee9e58c800db3e6b99e9e373532d394a" + integrity sha512-G8G/sDDhXA7o0bOvkc7bgai6POuSld/+XhNnWAbpQTpLv2OZPvyqQ58tLPPlz0bSNsXktldDDREIv1LczFeNEw== + dependencies: + "@smithy/middleware-serde" "^3.0.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-stream" "^3.3.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/credential-provider-imds@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.8.tgz#27ed2747074c86a7d627a98e56f324a65cba88de" + integrity sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/property-provider" "^3.1.11" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + tslib "^2.6.2" + +"@smithy/eventstream-codec@^3.1.10": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-3.1.10.tgz#0c1a3457e7a23b71cd71525ceb668f8569a84dad" + integrity sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@smithy/types" "^3.7.2" + "@smithy/util-hex-encoding" "^3.0.0" + tslib "^2.6.2" + +"@smithy/eventstream-serde-browser@^3.0.14": + version "3.0.14" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.14.tgz#0c3584c7cde2e210aacdfbbd2b57c1d7e2ca3b95" + integrity sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg== + dependencies: + "@smithy/eventstream-serde-universal" "^3.0.13" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-config-resolver@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.11.tgz#5edceba836debea165ea93145231036f6286d67c" + integrity sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-node@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.13.tgz#5aebd7b553becee277e411a2b69f6af8c9d7b3a6" + integrity sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ== + dependencies: + "@smithy/eventstream-serde-universal" "^3.0.13" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-universal@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.13.tgz#609c922ea14a0a3eed23a28ac110344c935704eb" + integrity sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw== + dependencies: + "@smithy/eventstream-codec" "^3.1.10" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/fetch-http-handler@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.2.tgz#f034ff16416b37d92908a1381ef5fddbf4ef1879" + integrity sha512-R7rU7Ae3ItU4rC0c5mB2sP5mJNbCfoDc8I5XlYjIZnquyUwec7fEo78F6DA3SmgJgkU1qTMcZJuGblxZsl10ZA== + dependencies: + "@smithy/protocol-http" "^4.1.8" + "@smithy/querystring-builder" "^3.0.11" + "@smithy/types" "^3.7.2" + "@smithy/util-base64" "^3.0.0" + tslib "^2.6.2" + +"@smithy/hash-blob-browser@^3.1.10": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.10.tgz#985e308189c2687a15004152b97506882ffb2b13" + integrity sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA== + dependencies: + "@smithy/chunked-blob-reader" "^4.0.0" + "@smithy/chunked-blob-reader-native" "^3.0.1" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/hash-node@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-3.0.11.tgz#99e09ead3fc99c8cd7ca0f254ea0e35714f2a0d3" + integrity sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/hash-stream-node@^3.1.10": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-3.1.10.tgz#94716b4556f4ccf2807e605f47bb5b018ed7dfb0" + integrity sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/invalid-dependency@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-3.0.11.tgz#8144d7b0af9d34ab5f672e1f674f97f8740bb9ae" + integrity sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/is-array-buffer@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" + integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== + dependencies: + tslib "^2.6.2" + +"@smithy/is-array-buffer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz#9a95c2d46b8768946a9eec7f935feaddcffa5e7a" + integrity sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ== + dependencies: + tslib "^2.6.2" + +"@smithy/md5-js@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-3.0.11.tgz#27e4dab616348ff94aed24dc75e4017c582df40f" + integrity sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/middleware-content-length@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-3.0.13.tgz#6e08fe52739ac8fb3996088e0f8837e4b2ea187f" + integrity sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw== + dependencies: + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/middleware-endpoint@^3.2.5": + version "3.2.5" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.5.tgz#bdcfdf1f342cf933b0b8a709996f9a8fbb8148f4" + integrity sha512-VhJNs/s/lyx4weiZdXSloBgoLoS8osV0dKIain8nGmx7of3QFKu5BSdEuk1z/U8x9iwes1i+XCiNusEvuK1ijg== + dependencies: + "@smithy/core" "^2.5.5" + "@smithy/middleware-serde" "^3.0.11" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + "@smithy/util-middleware" "^3.0.11" + tslib "^2.6.2" + +"@smithy/middleware-retry@^3.0.30": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-3.0.30.tgz#2580322d0d28ad782b5b8c07c150b14efdc3b2f9" + integrity sha512-6323RL2BvAR3VQpTjHpa52kH/iSHyxd/G9ohb2MkBk2Ucu+oMtRXT8yi7KTSIS9nb58aupG6nO0OlXnQOAcvmQ== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/protocol-http" "^4.1.8" + "@smithy/service-error-classification" "^3.0.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-retry" "^3.0.11" + tslib "^2.6.2" + uuid "^9.0.1" + +"@smithy/middleware-serde@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-3.0.11.tgz#c7d54e0add4f83e05c6878a011fc664e21022f12" + integrity sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/middleware-stack@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-3.0.11.tgz#453af2096924e4064d9da4e053cfdf65d9a36acc" + integrity sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/node-config-provider@^3.1.12": + version "3.1.12" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-3.1.12.tgz#1b1d674fc83f943dc7b3017e37f16f374e878a6c" + integrity sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ== + dependencies: + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/node-http-handler@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-3.3.2.tgz#b34685863b74dabdaf7860aa81b42d0d5437c7e0" + integrity sha512-t4ng1DAd527vlxvOfKFYEe6/QFBcsj7WpNlWTyjorwXXcKw3XlltBGbyHfSJ24QT84nF+agDha9tNYpzmSRZPA== + dependencies: + "@smithy/abort-controller" "^3.1.9" + "@smithy/protocol-http" "^4.1.8" + "@smithy/querystring-builder" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/property-provider@^3.1.11": + version "3.1.11" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-3.1.11.tgz#161cf1c2a2ada361e417382c57f5ba6fbca8acad" + integrity sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/protocol-http@^4.1.8": + version "4.1.8" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-4.1.8.tgz#0461758671335f65e8ff3fc0885ab7ed253819c9" + integrity sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/querystring-builder@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-3.0.11.tgz#2ed04adbe725671824c5613d0d6f9376d791a909" + integrity sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-uri-escape" "^3.0.0" + tslib "^2.6.2" + +"@smithy/querystring-parser@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-3.0.11.tgz#9d3177ea19ce8462f18d9712b395239e1ca1f969" + integrity sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/service-error-classification@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz#d3d7fc0aacd2e60d022507367e55c7939e5bcb8a" + integrity sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog== + dependencies: + "@smithy/types" "^3.7.2" + +"@smithy/shared-ini-file-loader@^3.1.12": + version "3.1.12" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz#d98b1b663eb18935ce2cbc79024631d34f54042a" + integrity sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/signature-v4@^4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-4.2.4.tgz#3501d3d09fd82768867bfc00a7be4bad62f62f4d" + integrity sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-uri-escape" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/smithy-client@^3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-3.5.0.tgz#65cff262801b009998c1196764ee69929ee06f8a" + integrity sha512-Y8FeOa7gbDfCWf7njrkoRATPa5eNLUEjlJS5z5rXatYuGkCb80LbHcu8AQR8qgAZZaNHCLyo2N+pxPsV7l+ivg== + dependencies: + "@smithy/core" "^2.5.5" + "@smithy/middleware-endpoint" "^3.2.5" + "@smithy/middleware-stack" "^3.0.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-stream" "^3.3.2" + tslib "^2.6.2" + +"@smithy/types@^3.7.2": + version "3.7.2" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.7.2.tgz#05cb14840ada6f966de1bf9a9c7dd86027343e10" + integrity sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg== + dependencies: + tslib "^2.6.2" + +"@smithy/url-parser@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-3.0.11.tgz#e5f5ffabfb6230159167cf4cc970705fca6b8b2d" + integrity sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw== + dependencies: + "@smithy/querystring-parser" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-base64@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-3.0.0.tgz#f7a9a82adf34e27a72d0719395713edf0e493017" + integrity sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-body-length-browser@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz#86ec2f6256310b4845a2f064e2f571c1ca164ded" + integrity sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-body-length-node@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz#99a291bae40d8932166907fe981d6a1f54298a6d" + integrity sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA== + dependencies: + tslib "^2.6.2" + +"@smithy/util-buffer-from@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" + integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== + dependencies: + "@smithy/is-array-buffer" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-buffer-from@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz#559fc1c86138a89b2edaefc1e6677780c24594e3" + integrity sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-config-provider@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz#62c6b73b22a430e84888a8f8da4b6029dd5b8efe" + integrity sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-defaults-mode-browser@^3.0.30": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.30.tgz#6c0d95af3f15bef8f1fe3f6217cc4f5ba8df5554" + integrity sha512-nLuGmgfcr0gzm64pqF2UT4SGWVG8UGviAdayDlVzJPNa6Z4lqvpDzdRXmLxtOdEjVlTOEdpZ9dd3ZMMu488mzg== + dependencies: + "@smithy/property-provider" "^3.1.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + bowser "^2.11.0" + tslib "^2.6.2" + +"@smithy/util-defaults-mode-node@^3.0.30": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.30.tgz#33cdb02f90944b9ff221e2f8e0904a63ac1e335f" + integrity sha512-OD63eWoH68vp75mYcfYyuVH+p7Li/mY4sYOROnauDrtObo1cS4uWfsy/zhOTW8F8ZPxQC1ZXZKVxoxvMGUv2Ow== + dependencies: + "@smithy/config-resolver" "^3.0.13" + "@smithy/credential-provider-imds" "^3.2.8" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/property-provider" "^3.1.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-endpoints@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz#a088ebfab946a7219dd4763bfced82709894b82d" + integrity sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-hex-encoding@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6" + integrity sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-middleware@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-3.0.11.tgz#2ab5c17266b42c225e62befcffb048afa682b5bf" + integrity sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-retry@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-3.0.11.tgz#d267e5ccb290165cee69732547fea17b695a7425" + integrity sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ== + dependencies: + "@smithy/service-error-classification" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-stream@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-3.3.2.tgz#daeea26397e8541cf2499ce65bf0b8d528cba421" + integrity sha512-sInAqdiVeisUGYAv/FrXpmJ0b4WTFmciTRqzhb7wVuem9BHvhIG7tpiYHLDWrl2stOokNZpTTGqz3mzB2qFwXg== + dependencies: + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/types" "^3.7.2" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-uri-escape@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz#e43358a78bf45d50bb736770077f0f09195b6f54" + integrity sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-utf8@^2.0.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" + integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== + dependencies: + "@smithy/util-buffer-from" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-utf8@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-3.0.0.tgz#1a6a823d47cbec1fd6933e5fc87df975286d9d6a" + integrity sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-waiter@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-3.2.0.tgz#1e52f870e77d2e5572025f7606053e6ff00df93d" + integrity sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg== + dependencies: + "@smithy/abort-controller" "^3.1.9" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + "@sqltools/formatter@^1.2.5": version "1.2.5" resolved "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz" @@ -2508,6 +3600,11 @@ boolean@^3.0.1: resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== +bowser@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3896,6 +4993,13 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-xml-parser@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" + integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== + dependencies: + strnum "^1.0.5" + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -6902,6 +8006,11 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== +strnum@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + strtok3@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-9.0.1.tgz#7e3d7bbd2b829c9def6a7bb90d82e240abdd32be" @@ -7383,7 +8492,7 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -uuid@^9.0.0: +uuid@^9.0.0, uuid@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== From 5d5ce092b75247feb43ae72c0a942699085e514c Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 17:09:56 +0000 Subject: [PATCH 07/62] feat: adding source search --- .../src/pages/catalogue/catalogue.tsx | 34 ++----------------- .../src/pages/catalogue/filter-section.tsx | 12 ++++--- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index b2e4588a0..534af11de 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -298,38 +298,8 @@ export default function Catalogue() { dispatch(setSearch({ tags: [...filters.tags, value] })); } }} - items={[ - "Action", - "Strategy", - "RPG", - "Casual", - "Racing", - "Sports", - "Indie", - "Adventure", - "Simulation", - "Massively Multiplayer", - "Free to Play", - "Accounting", - "Animation & Modeling", - "Audio Production", - "Design & Illustration", - "Education", - "Photo Editing", - "Software Training", - "Utilities", - "Video Production", - "Web Publishing", - "Game Development", - "Early Access", - "Sexual Content", - "Nudity", - "Violent", - "Gore", - "Documentary", - "Tutorial", - ].map((genre) => ({ - label: genre, + items={[1, 2, 3, 4, 5].map((genre) => ({ + label: genre.toString(), value: genre, checked: filters.tags.includes(genre), }))} diff --git a/src/renderer/src/pages/catalogue/filter-section.tsx b/src/renderer/src/pages/catalogue/filter-section.tsx index e56f1dd63..1cb9d9fc9 100644 --- a/src/renderer/src/pages/catalogue/filter-section.tsx +++ b/src/renderer/src/pages/catalogue/filter-section.tsx @@ -2,17 +2,21 @@ import { CheckboxField, TextField } from "@renderer/components"; import { useFormat } from "@renderer/hooks"; import { useCallback, useMemo, useState } from "react"; -export interface FilterSectionProps { +export interface FilterSectionProps { title: string; items: { label: string; - value: string; + value: T; checked: boolean; }[]; - onSelect: (value: string) => void; + onSelect: (value: T) => void; } -export function FilterSection({ title, items, onSelect }: FilterSectionProps) { +export function FilterSection({ + title, + items, + onSelect, +}: FilterSectionProps) { const [search, setSearch] = useState(""); const filteredItems = useMemo(() => { From eb744346f0d3e1a91966f3e4f9f0e0452898a43f Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 17:10:33 +0000 Subject: [PATCH 08/62] feat: adding source search --- src/types/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/index.ts b/src/types/index.ts index 48756fcbe..f048b3f1c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -288,6 +288,7 @@ export interface DownloadSource { url: string; repackCount: number; status: DownloadSourceStatus; + objectIds: string[]; downloadCount: number; fingerprint: string; etag: string | null; From ede1dbc5e73044ad8bb6ac93ecad3fba941fba6d Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 17:12:59 +0000 Subject: [PATCH 09/62] feat: adding source search --- package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/package.json b/package.json index 43c8ee059..55a7f3bb4 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "electron-log": "^5.2.4", "electron-updater": "^6.3.9", "file-type": "^19.6.0", - "flexsearch": "^0.7.43", "i18next": "^23.11.2", "i18next-browser-languagedetector": "^7.2.1", "jsdom": "^24.0.0", diff --git a/yarn.lock b/yarn.lock index b96d134af..b0ef653b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5140,11 +5140,6 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -flexsearch@^0.7.43: - version "0.7.43" - resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.7.43.tgz#34f89b36278a466ce379c5bf6fb341965ed3f16c" - integrity sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg== - follow-redirects@^1.15.6: version "1.15.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" From 3be9053b76386481bbd4952102d8555f9d9412b3 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 17:33:13 +0000 Subject: [PATCH 10/62] feat: adding pacman --- scripts/upload-build.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/upload-build.cjs b/scripts/upload-build.cjs index 9f19ca892..c00b565a2 100644 --- a/scripts/upload-build.cjs +++ b/scripts/upload-build.cjs @@ -20,7 +20,7 @@ const s3 = new S3Client({ const dist = path.resolve(__dirname, "..", "dist"); -const extensionsToUpload = [".deb", ".exe"]; +const extensionsToUpload = [".deb", ".exe", ".pacman"]; fs.readdir(dist, async (err, files) => { if (err) throw err; From 170ac28bb56a4488bf59fd00e0ec5d507ad02fb7 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:48:04 -0300 Subject: [PATCH 11/62] fix: user data not being correctly loaded on hydra api setup --- .../achievements/merge-achievements.ts | 23 ++++++++++++++----- src/main/services/hydra-api.ts | 18 +++++++++------ src/main/services/user/get-user-data.ts | 1 + 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 50780e95d..dd8c877dd 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -7,8 +7,9 @@ import { WindowManager } from "../window-manager"; import { HydraApi } from "../hydra-api"; import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievements"; import { Game } from "@main/entity"; -import { achievementsLogger } from "../logger"; import { publishNewAchievementNotification } from "../notifications"; +import { SubscriptionRequiredError } from "@shared"; +import { achievementsLogger } from "../logger"; const saveAchievementsOnLocal = async ( objectId: string, @@ -120,10 +121,14 @@ export const mergeAchievements = async ( } if (game.remoteId) { - await HydraApi.put("/profile/games/achievements", { - id: game.remoteId, - achievements: mergedLocalAchievements, - }) + await HydraApi.put( + "/profile/games/achievements", + { + id: game.remoteId, + achievements: mergedLocalAchievements, + }, + { needsSubscription: !newAchievements.length } + ) .then((response) => { return saveAchievementsOnLocal( response.objectId, @@ -133,7 +138,13 @@ export const mergeAchievements = async ( ); }) .catch((err) => { - achievementsLogger.error(err); + if (err! instanceof SubscriptionRequiredError) { + achievementsLogger.log( + "Achievements not synchronized on API due to lack of subscription", + game.objectID, + game.title + ); + } return saveAchievementsOnLocal( game.objectID, diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index bac1486a0..8bd2d2ba7 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -23,7 +23,7 @@ interface HydraApiUserAuth { authToken: string; refreshToken: string; expirationTimestamp: number; - subscription: { expiresAt: Date | null } | null; + subscription: { expiresAt: Date | string | null } | null; } export class HydraApi { @@ -182,8 +182,6 @@ export class HydraApi { ); } - await getUserData(); - const userAuth = await userAuthRepository.findOne({ where: { id: 1 }, relations: { subscription: true }, @@ -197,6 +195,14 @@ export class HydraApi { ? { expiresAt: userAuth.subscription?.expiresAt } : null, }; + + const updatedUserData = await getUserData(); + + this.userAuth.subscription = updatedUserData?.subscription + ? { + expiresAt: updatedUserData.subscription.expiresAt, + } + : null; } private static sendSignOutEvent() { @@ -284,10 +290,8 @@ export class HydraApi { await this.revalidateAccessTokenIfExpired(); } - if (needsSubscription) { - if (!(await this.hasActiveSubscription())) { - throw new SubscriptionRequiredError(); - } + if (needsSubscription && !this.hasActiveSubscription()) { + throw new SubscriptionRequiredError(); } } diff --git a/src/main/services/user/get-user-data.ts b/src/main/services/user/get-user-data.ts index ff0128815..48d7a98fa 100644 --- a/src/main/services/user/get-user-data.ts +++ b/src/main/services/user/get-user-data.ts @@ -42,6 +42,7 @@ export const getUserData = () => { }) .catch(async (err) => { if (err instanceof UserNotLoggedInError) { + logger.info("User is not logged in", err); return null; } logger.error("Failed to get logged user"); From 065ea0c6fed5906d382ae86dda17855aceafdd8a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:20:47 -0300 Subject: [PATCH 12/62] feat: adjust log --- src/main/services/hydra-api.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 8bd2d2ba7..63dd9b16f 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -159,7 +159,11 @@ export class HydraApi { config.method, config.baseURL, config.url, - omit(config.headers, ["accessToken", "refreshToken"]), + omit(config.headers, [ + "accessToken", + "refreshToken", + "Authorization", + ]), Array.isArray(data) ? data : omit(data, ["accessToken", "refreshToken"]) From e78de55fe7d6c36fc6b5338e51ffbb2acebbf09e Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 18:39:40 +0000 Subject: [PATCH 13/62] feat: adding user tags --- .../src/pages/catalogue/catalogue.tsx | 29 +- .../src/pages/catalogue/filter-section.tsx | 46 +- .../src/pages/catalogue/steam-user-tags.ts | 452 ++++++++++++++++++ .../src/workers/download-sources.worker.ts | 2 +- 4 files changed, 498 insertions(+), 31 deletions(-) create mode 100644 src/renderer/src/pages/catalogue/steam-user-tags.ts diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 534af11de..cf2ac507f 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -18,6 +18,7 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import { FilterSection } from "./filter-section"; import { setSearch } from "@renderer/features"; import { useTranslation } from "react-i18next"; +import { steamUserTags } from "./steam-user-tags"; export default function Catalogue() { const inputRef = useRef(null); @@ -142,7 +143,7 @@ export default function Catalogue() { { downloadSources.find( (source) => source.fingerprint === fingerprint - )!.name + )?.name } ))} @@ -204,7 +205,7 @@ export default function Catalogue() { fontSize: 12, }} > - {game.genres.join(", ")} + {game.genres?.join(", ")}
@@ -278,11 +279,13 @@ export default function Catalogue() { "Gore", "Documentary", "Tutorial", - ].map((genre) => ({ - label: genre, - value: genre, - checked: filters.genres.includes(genre), - }))} + ] + .sort() + .map((genre) => ({ + label: genre, + value: genre, + checked: filters.genres.includes(genre), + }))} /> ({ - label: genre.toString(), - value: genre, - checked: filters.tags.includes(genre), - }))} + items={Object.entries(steamUserTags) + .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) + .map(([key, value]) => ({ + label: key, + value: value, + checked: filters.tags.includes(value), + }))} /> ({ onSelect, }: FilterSectionProps) { const [search, setSearch] = useState(""); + const [showMore, setShowMore] = useState(false); const filteredItems = useMemo(() => { - if (items.length > 10 && search.length > 0) { - return items.filter((item) => - item.label.toLowerCase().includes(search.toLowerCase()) - ); + if (search.length > 0) { + return items + .filter((item) => + item.label.toLowerCase().includes(search.toLowerCase()) + ) + .slice(0, 10); + } + + if (showMore) { + return items; } return items.slice(0, 10); - }, [items, search]); + }, [items, search, showMore]); const onSearch = useCallback((value: string) => { setSearch(value); @@ -70,19 +77,22 @@ export function FilterSection({
))} - + {!search && items.length > 10 && ( + + )} ); diff --git a/src/renderer/src/pages/catalogue/steam-user-tags.ts b/src/renderer/src/pages/catalogue/steam-user-tags.ts new file mode 100644 index 000000000..76721d4fa --- /dev/null +++ b/src/renderer/src/pages/catalogue/steam-user-tags.ts @@ -0,0 +1,452 @@ +export const steamUserTags = { + Atmospheric: 4166, + Fantasy: 1684, + Relaxing: 1654, + Funny: 4136, + "Sci-fi": 3942, + Horror: 1667, + "Family Friendly": 5350, + Retro: 4004, + Survival: 1662, + Mystery: 5716, + Dark: 4342, + Comedy: 1719, + "Psychological Horror": 1721, + Sports: 701, + "Old School": 3916, + Medieval: 4172, + Magic: 4057, + Racing: 699, + Building: 1643, + Management: 12472, + Space: 1755, + Tactical: 1708, + Drama: 5984, + Futuristic: 4295, + Logic: 6129, + Romance: 4947, + Crafting: 1702, + "Dark Fantasy": 4604, + Emotional: 5608, + "Survival Horror": 3978, + "1980s": 7743, + Nature: 30358, + Education: 1036, + "1990's": 6691, + Surreal: 1710, + "Post-apocalyptic": 3835, + War: 1678, + Zombies: 1659, + Historical: 3987, + Stealth: 1687, + Investigation: 8369, + Military: 4168, + "LGBTQ+": 44868, + Cyberpunk: 4115, + "Lore-Rich": 3854, + Detective: 5613, + Aliens: 1673, + Thriller: 4064, + Economy: 4695, + Robots: 5752, + Demons: 9541, + "Dark Humor": 5923, + Psychological: 5186, + Driving: 1644, + Supernatural: 10808, + "Comic Book": 1751, + Modern: 5673, + Psychedelic: 1714, + Dystopian: 5030, + Flight: 15045, + "Artificial Intelligence": 7926, + Loot: 4236, + Memes: 10397, + "Alternate History": 4598, + Cats: 17894, + Parkour: 4036, + Mythology: 16094, + Crime: 6378, + "Game Development": 13906, + Destruction: 5363, + Philosophical: 15277, + Capitalism: 4845, + "Dark Comedy": 19995, + Automation: 255534, + Lovecraftian: 7432, + Noir: 6052, + Swordplay: 4608, + Science: 5794, + Cooking: 3920, + America: 13190, + Dragons: 4046, + "World War II": 4150, + Parody: 4878, + Agriculture: 22602, + Conspiracy: 5372, + Gothic: 3952, + "Martial Arts": 6915, + Mechs: 4821, + Underground: 21006, + Satire: 1651, + Pirates: 1681, + Steampunk: 1777, + Dog: 1638, + "Time Travel": 10679, + Mining: 5981, + Transportation: 10383, + Ninja: 1688, + Vampire: 4018, + Tanks: 13276, + Political: 4853, + Otome: 31579, + Underwater: 9157, + Hunting: 9564, + Fishing: 15564, + Trains: 1616, + Dinosaurs: 5160, + Western: 1647, + Hacking: 5502, + Faith: 180368, + Programming: 5432, + Superhero: 1671, + Politics: 4754, + Assassin: 4376, + Gambling: 16250, + Naval: 6910, + Diplomacy: 6310, + Heist: 1680, + Snow: 9803, + Archery: 13382, + "Cold War": 5179, + Sailing: 13577, + "Football (Soccer)": 1254546, + Foreign: 51306, + Offroad: 7622, + Horses: 6041, + Illuminati: 7478, + Sniper: 7423, + Transhumanism: 4137, + Werewolves: 17015, + Mars: 6702, + Boxing: 12190, + Jet: 92092, + Motorbike: 198913, + Golf: 7038, + Bikes: 123332, + "World War I": 5382, + Rome: 6948, + Submarine: 19780, + Basketball: 1746, + Baseball: 5727, + LEGO: 1736, + Skateboarding: 1753, + "Mini Golf": 22955, + Wrestling: 47827, + "Football (American)": 1254552, + Tennis: 5914, + Pool: 17927, + Skating: 96359, + Cycling: 19568, + Motocross: 15868, + "Warhammer 40K": 12286, + Lemmings: 17337, + Hockey: 324176, + Bowling: 7328, + Snowboarding: 28444, + Skiing: 7309, + BMX: 252854, + ATV: 129761, + Indie: 492, + Action: 19, + Casual: 597, + Adventure: 21, + Simulation: 599, + RPG: 122, + Strategy: 9, + "Action-Adventure": 4106, + Software: 8013, + "2D": 3871, + "3D": 4191, + Colorful: 4305, + "Pixel Graphics": 3964, + Cute: 4726, + "First-Person": 3839, + Anime: 4085, + "Third Person": 1697, + Stylized: 4252, + "Top-Down": 4791, + Realistic: 4175, + Cartoony: 4195, + Minimalist: 4094, + "Hand-drawn": 6815, + VR: 21978, + Cartoon: 4562, + "Text-Based": 31275, + Cinematic: 4145, + "2.5D": 4975, + Isometric: 5851, + Abstract: 4400, + "Split Screen": 10816, + "3D Vision": 29363, + Voxel: 1732, + Beautiful: 5411, + FMV: 18594, + "360 Video": 776177, + "Story Rich": 1742, + Combat: 3993, + Controller: 7481, + "Female Protagonist": 7208, + "Choices Matter": 6426, + "Open World": 1695, + PvP: 1775, + PvE: 6730, + Linear: 7250, + "Multiple Endings": 6971, + Physics: 3968, + "Character Customization": 4747, + "Procedural Generation": 5125, + Tabletop: 17389, + "Turn-Based Combat": 4325, + "Turn-Based Tactics": 14139, + "Hack and Slash": 1646, + "Resource Management": 8945, + "Base-Building": 7332, + "Score Attack": 5154, + Narration: 5094, + Conversation: 15172, + Nonlinear: 6869, + Tutorial: 12057, + "Perma Death": 1759, + "Team-Based": 5711, + Deckbuilding: 32322, + "Inventory Management": 6276, + "Level Editor": 8122, + "Grid-Based Movement": 7569, + Moddable: 1669, + "Class-Based": 4155, + "Vehicular Combat": 11104, + "Gun Customization": 5765, + "6DOF": 4835, + Trading: 4202, + "Bullet Time": 5796, + "Time Manipulation": 6625, + "Quick-Time Events": 4559, + "Dynamic Narration": 9592, + "Hex Grid": 1717, + "Naval Combat": 4994, + "Music-Based Procedural Generation": 8253, + "Asymmetric VR": 856791, + Puzzle: 1664, + Arcade: 1773, + Shooter: 1774, + Platformer: 1625, + "Visual Novel": 3799, + Sandbox: 3810, + "Rogue-like": 1716, + "Action RPG": 4231, + "Point & Click": 1698, + "Action Roguelike": 42804, + "Interactive Fiction": 11014, + "Turn-Based Strategy": 1741, + "Dating Sim": 9551, + JRPG: 4434, + "Party-Based RPG": 10695, + "Walking Simulator": 5900, + "Design & Illustration": 84, + "Card Game": 1666, + "Life Sim": 10235, + Utilities: 87, + "Strategy RPG": 17305, + "Board Game": 1770, + RTS: 1676, + "Tower Defense": 1645, + "Web Publishing": 1038, + "City Builder": 4328, + "Beat 'em up": 4158, + "Automobile Sim": 1100687, + "2D Fighter": 4736, + Rhythm: 1752, + "3D Fighter": 6506, + "Farming Sim": 87918, + "Animation & Modeling": 872, + "e-sports": 5055, + "Grand Strategy": 4364, + "Space Sim": 16598, + "Colony Sim": 220585, + "Word Game": 24003, + "Battle Royale": 176981, + MMORPG: 1754, + "Auto Battler": 1084988, + "Audio Production": 1027, + "Video Production": 784, + "God Game": 5300, + "4X": 1670, + MOBA: 1718, + "Photo Editing": 809, + Trivia: 10437, + "Immersive Sim": 9204, + "Political Sim": 26921, + "Outbreak Sim": 1100686, + "Medical Sim": 1100688, + Short: 4234, + Movie: 4700, + Episodic: 4242, + Gaming: 150626, + Documentary: 15339, + Exploration: 3834, + "2D Platformer": 5379, + FPS: 1663, + "Rogue-lite": 3959, + "Shoot 'Em Up": 4255, + "3D Platformer": 5395, + "Side Scroller": 3798, + "Choose Your Own Adventure": 4486, + "Puzzle-Platformer": 5537, + "Hidden Object": 1738, + "Bullet Hell": 4885, + "Dungeon Crawler": 1720, + "Top-Down Shooter": 4637, + Clicker: 379975, + "Third-Person Shooter": 3814, + "Precision Platformer": 3877, + "Time Management": 16689, + "Real Time Tactics": 3813, + "Arena Shooter": 5547, + Collectathon: 5652, + "Tactical RPG": 21725, + Idler: 615955, + Wargame: 4684, + Metroidvania: 1628, + Runner: 8666, + "Card Battler": 791774, + "Souls-like": 29482, + CRPG: 4474, + "Creature Collector": 916648, + "Twin Stick Shooter": 4758, + "Match 3": 1665, + "Mystery Dungeon": 198631, + "Hero Shooter": 620519, + "Spectacle fighter": 4777, + "Looter Shooter": 353880, + Solitaire: 13070, + "Combat Racing": 4102, + "Action RTS": 1723, + Sokoban: 1730, + "Trading Card Game": 9271, + Typing: 1674, + "Boomer Shooter": 1023537, + "Traditional Roguelike": 454187, + "On-Rails Shooter": 56690, + Spelling: 71389, + Roguevania: 922563, + Singleplayer: 4182, + Multiplayer: 3859, + "Co-op": 1685, + "Online Co-Op": 3843, + "Massively Multiplayer": 128, + "Local Multiplayer": 7368, + "Local Co-Op": 3841, + "4 Player Local": 4840, + "Co-op Campaign": 4508, + "Asynchronous Multiplayer": 17770, + "Profile Features Limited": 1003823, + "Demo Available": 21491, + "Adult Content": 65443, + Hentai: 9130, + Fighting: 1743, + Classic: 1693, + Cozy: 97376, + "Open World Survival Craft": 1100689, + Wholesome: 552282, + "Roguelike Deckbuilder": 1091588, + Narrative: 7702, + Immersive: 3934, + "Party Game": 7178, + Party: 7108, + "Escape Room": 769306, + Addictive: 4190, + Nostalgia: 14720, + Farming: 4520, + "Cult Classic": 7782, + Spaceships: 4291, + "Electronic Music": 61357, + Pinball: 6621, + "Social Deduction": 745697, + Ambient: 29855, + Dwarf: 7918, + "Job Simulator": 35079, + Epic: 3965, + "Instrumental Music": 189941, + "Jump Scare": 42089, + "Boss Rush": 11095, + "Rock Music": 337964, + "Tile-Matching": 176733, + Vikings: 11634, + "Extraction Shooter": 1199779, + "8-bit Music": 117648, + "Well-Written": 8461, + Mahjong: 33572, + "Shop Keeper": 91114, + Electronic: 143739, + Birds: 6214, + Dice: 7556, + Musou: 323922, + Fox: 30927, + Coding: 42329, + Elf: 102530, + "Hobby Sim": 1220528, + Cricket: 158638, + Rugby: 49213, + Volleyball: 847164, + Snooker: 363767, + Reboot: 5941, + "Based On A Novel": 3796, + "Free to Play": 113, + "Early Access": 493, + Experimental: 13782, + "Software Training": 1445, + Minigames: 8093, + Remake: 5708, + Sequel: 5230, + Experience: 9994, + Kickstarter: 5153, + Crowdfunded: 7113, + Benchmark: 5407, + "Feature Film": 233824, + Difficult: 4026, + Competitive: 3878, + Unforgiving: 1733, + "Turn-Based": 1677, + "Replay Value": 4711, + "Fast-Paced": 1734, + "Real-Time": 4161, + "Real-Time with Pause": 7107, + "Time Attack": 5390, + "Sexual Content": 12095, + Nudity: 6650, + Mature: 5611, + NSFW: 24904, + "Character Action Game": 3955, + "Villain Protagonist": 11333, + "Silent Protagonist": 15954, + Chess: 4184, + Violent: 4667, + Gore: 4345, + Blood: 5228, + Soundtrack: 7948, + "Great Soundtrack": 1756, + Music: 1621, + "Mouse only": 11123, + "Touch-Friendly": 25085, + "Intentionally Awkward Controls": 14906, + "Voice Control": 27758, + Mod: 5348, + RPGMaker: 5577, + GameMaker: 1649, + "Dungeons & Dragons": 14153, + "Games Workshop": 5310, + TrackIR: 8075, + Hardware: 603297, + "Steam Machine": 348922, +}; diff --git a/src/renderer/src/workers/download-sources.worker.ts b/src/renderer/src/workers/download-sources.worker.ts index a847520a8..40665dc99 100644 --- a/src/renderer/src/workers/download-sources.worker.ts +++ b/src/renderer/src/workers/download-sources.worker.ts @@ -77,7 +77,7 @@ const addNewDownloads = async ( const getSteamGames = async () => { const response = await axios.get( - "https://assets.hydralauncher.gg/steam-games-by-letter.json" + `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` ); return response.data; From a557bbb94825f3b2537852152eb9e62f8671ab83 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 18:43:17 +0000 Subject: [PATCH 14/62] feat: adding user tags --- src/renderer/src/pages/catalogue/catalogue.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index cf2ac507f..a99ac0500 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -23,6 +23,8 @@ import { steamUserTags } from "./steam-user-tags"; export default function Catalogue() { const inputRef = useRef(null); + const abortControllerRef = useRef(null); + const [focused, setFocused] = useState(false); const [searchParams] = useSearchParams(); @@ -43,8 +45,16 @@ export default function Catalogue() { useEffect(() => { setGames([]); + abortControllerRef.current?.abort(); + + const abortController = new AbortController(); + abortControllerRef.current = abortController; window.electron.searchGames(filters).then((games) => { + if (abortController.signal.aborted) { + return; + } + setGames(games); }); }, [filters]); From 3af0ae9f857e953d34e459e0c2fbab7392520152 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 18:59:51 +0000 Subject: [PATCH 15/62] feat: adding scroll to filters --- .../src/pages/catalogue/filter-section.tsx | 44 ++++++------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/renderer/src/pages/catalogue/filter-section.tsx b/src/renderer/src/pages/catalogue/filter-section.tsx index b5df3c47a..940394a18 100644 --- a/src/renderer/src/pages/catalogue/filter-section.tsx +++ b/src/renderer/src/pages/catalogue/filter-section.tsx @@ -18,23 +18,16 @@ export function FilterSection({ onSelect, }: FilterSectionProps) { const [search, setSearch] = useState(""); - const [showMore, setShowMore] = useState(false); const filteredItems = useMemo(() => { if (search.length > 0) { - return items - .filter((item) => - item.label.toLowerCase().includes(search.toLowerCase()) - ) - .slice(0, 10); + return items.filter((item) => + item.label.toLowerCase().includes(search.toLowerCase()) + ); } - if (showMore) { - return items; - } - - return items.slice(0, 10); - }, [items, search, showMore]); + return items; + }, [items, search]); const onSearch = useCallback((value: string) => { setSearch(value); @@ -66,7 +59,15 @@ export function FilterSection({ theme="dark" /> -
+
{filteredItems.map((item) => (
({ />
))} - - {!search && items.length > 10 && ( - - )}
); From d3450c5f652abe389332974c7191fa9c91895374 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 21:28:51 +0000 Subject: [PATCH 16/62] feat: adding link direct from sources --- package.json | 2 + src/main/events/index.ts | 2 + src/preload/index.ts | 2 + src/renderer/src/components/badge/badge.tsx | 6 +- .../checkbox-field/checkbox-field.css.ts | 1 + src/renderer/src/declaration.d.ts | 2 + src/renderer/src/features/catalogue-search.ts | 2 +- .../src/pages/catalogue/catalogue.tsx | 120 +++++++++++++++++- .../src/pages/catalogue/filter-section.tsx | 60 ++++++--- .../settings/settings-download-sources.tsx | 26 +++- yarn.lock | 79 +++++++++++- 11 files changed, 265 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 55a7f3bb4..ee93d418c 100644 --- a/package.json +++ b/package.json @@ -62,11 +62,13 @@ "lodash-es": "^4.17.21", "parse-torrent": "^11.0.17", "piscina": "^4.7.0", + "rc-virtual-list": "^3.16.1", "react-hook-form": "^7.53.0", "react-i18next": "^14.1.0", "react-loading-skeleton": "^3.4.0", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", + "react-virtualized": "^9.22.5", "sound-play": "^1.1.0", "sudo-prompt": "^9.2.1", "tar": "^7.4.3", diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 35863eac8..723127e82 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -8,6 +8,8 @@ import "./catalogue/get-random-game"; import "./catalogue/search-games"; import "./catalogue/get-game-stats"; import "./catalogue/get-trending-games"; +import "./catalogue/get-publishers"; +import "./catalogue/get-developers"; import "./hardware/get-disk-free-space"; import "./library/add-game-to-library"; import "./library/create-game-shortcut"; diff --git a/src/preload/index.ts b/src/preload/index.ts index cab56fe23..86414e1a6 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -65,6 +65,8 @@ contextBridge.exposeInMainWorld("electron", { listener ); }, + getPublishers: () => ipcRenderer.invoke("getPublishers"), + getDevelopers: () => ipcRenderer.invoke("getDevelopers"), /* User preferences */ getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), diff --git a/src/renderer/src/components/badge/badge.tsx b/src/renderer/src/components/badge/badge.tsx index 752a33ba1..c4819ae38 100644 --- a/src/renderer/src/components/badge/badge.tsx +++ b/src/renderer/src/components/badge/badge.tsx @@ -7,9 +7,5 @@ export interface BadgeProps { } export function Badge({ children }: BadgeProps) { - return ( -
- {children} -
- ); + return
{children}
; } diff --git a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts b/src/renderer/src/components/checkbox-field/checkbox-field.css.ts index 6546d9428..b4d3afc8d 100644 --- a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts +++ b/src/renderer/src/components/checkbox-field/checkbox-field.css.ts @@ -25,6 +25,7 @@ export const checkbox = recipe({ border: `solid 1px ${vars.color.border}`, minWidth: "20px", minHeight: "20px", + color: vars.color.darkBackground, ":hover": { borderColor: "rgba(255, 255, 255, 0.5)", }, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index c809b4b3e..c8033d7e8 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -68,6 +68,8 @@ declare global { shop: GameShop, cb: (achievements: GameAchievement[]) => void ) => () => Electron.IpcRenderer; + getPublishers: () => Promise; + getDevelopers: () => Promise; /* Library */ addGameToLibrary: ( diff --git a/src/renderer/src/features/catalogue-search.ts b/src/renderer/src/features/catalogue-search.ts index 90fd5051a..a6318a262 100644 --- a/src/renderer/src/features/catalogue-search.ts +++ b/src/renderer/src/features/catalogue-search.ts @@ -34,4 +34,4 @@ export const catalogueSearchSlice = createSlice({ }, }); -export const { setSearch } = catalogueSearchSlice.actions; +export const { setSearch, clearSearch } = catalogueSearchSlice.actions; diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index a99ac0500..9bbcd3586 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -20,6 +20,14 @@ import { setSearch } from "@renderer/features"; import { useTranslation } from "react-i18next"; import { steamUserTags } from "./steam-user-tags"; +const filterCategoryColors = { + genres: "hsl(262deg 50% 47%)", + tags: "hsl(95deg 50% 20%)", + downloadSourceFingerprints: "hsl(27deg 50% 40%)", + developers: "hsl(340deg 50% 46%)", + publishers: "hsl(200deg 50% 30%)", +}; + export default function Catalogue() { const inputRef = useRef(null); @@ -34,6 +42,8 @@ export default function Catalogue() { const [downloadSources, setDownloadSources] = useState([]); const [games, setGames] = useState([]); + const [publishers, setPublishers] = useState([]); + const [developers, setDevelopers] = useState([]); const filters = useAppSelector((state) => state.catalogueSearch.value); @@ -59,6 +69,16 @@ export default function Catalogue() { }); }, [filters]); + useEffect(() => { + window.electron.getDevelopers().then((developers) => { + setDevelopers(developers); + }); + + window.electron.getPublishers().then((publishers) => { + setPublishers(publishers); + }); + }, []); + const gamesWithRepacks = useMemo(() => { return games.map((game) => { const repacks = getRepacksForObjectId(game.objectId); @@ -148,13 +168,50 @@ export default function Catalogue() { }} >
+ {filters.genres.map((genre) => ( + +
+
+ + {genre} +
+ + ))} + + {filters.tags.map((tag) => ( + +
+ {tag} +
+
+ ))} + {filters.downloadSourceFingerprints.map((fingerprint) => ( - { - downloadSources.find( - (source) => source.fingerprint === fingerprint - )?.name - } +
+
+ + { + downloadSources.find( + (source) => source.fingerprint === fingerprint + )?.name + } +
))}
@@ -248,6 +305,7 @@ export default function Catalogue() {
{ if (filters.genres.includes(value)) { dispatch( @@ -300,6 +358,7 @@ export default function Catalogue() { { if (filters.tags.includes(value)) { dispatch( @@ -322,6 +381,7 @@ export default function Catalogue() { { if (filters.downloadSourceFingerprints.includes(value)) { dispatch( @@ -351,6 +411,56 @@ export default function Catalogue() { ), }))} /> + + { + if (filters.developers.includes(value)) { + dispatch( + setSearch({ + developers: filters.developers.filter( + (developer) => developer !== value + ), + }) + ); + } else { + dispatch( + setSearch({ developers: [...filters.developers, value] }) + ); + } + }} + items={developers.map((developer) => ({ + label: developer, + value: developer, + checked: filters.developers.includes(developer), + }))} + /> + + { + if (filters.publishers.includes(value)) { + dispatch( + setSearch({ + publishers: filters.publishers.filter( + (publisher) => publisher !== value + ), + }) + ); + } else { + dispatch( + setSearch({ publishers: [...filters.publishers, value] }) + ); + } + }} + items={publishers.map((publisher) => ({ + label: publisher, + value: publisher, + checked: filters.publishers.includes(publisher), + }))} + />
diff --git a/src/renderer/src/pages/catalogue/filter-section.tsx b/src/renderer/src/pages/catalogue/filter-section.tsx index 940394a18..ea1b7224b 100644 --- a/src/renderer/src/pages/catalogue/filter-section.tsx +++ b/src/renderer/src/pages/catalogue/filter-section.tsx @@ -2,6 +2,8 @@ import { CheckboxField, TextField } from "@renderer/components"; import { useFormat } from "@renderer/hooks"; import { useCallback, useMemo, useState } from "react"; +import List from "rc-virtual-list"; + export interface FilterSectionProps { title: string; items: { @@ -10,11 +12,13 @@ export interface FilterSectionProps { checked: boolean; }[]; onSelect: (value: T) => void; + color: string; } export function FilterSection({ title, items, + color, onSelect, }: FilterSectionProps) { const [search, setSearch] = useState(""); @@ -37,15 +41,25 @@ export function FilterSection({ return (
-

- {title} -

+
+
+

+ {title} +

+
{formatNumber(items.length)} disponíveis @@ -59,25 +73,31 @@ export function FilterSection({ theme="dark" /> -
- {filteredItems.map((item) => ( -
+ {(item) => ( +
onSelect(item.value)} />
- ))} -
+ )} +
); } diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 94846fc57..f5c01a21c 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -7,12 +7,14 @@ import * as styles from "./settings-download-sources.css"; import type { DownloadSource } from "@types"; import { NoEntryIcon, PlusCircleIcon, SyncIcon } from "@primer/octicons-react"; import { AddDownloadSourceModal } from "./add-download-source-modal"; -import { useRepacks, useToast } from "@renderer/hooks"; +import { useAppDispatch, useRepacks, useToast } from "@renderer/hooks"; import { DownloadSourceStatus } from "@shared"; -import { SPACING_UNIT } from "@renderer/theme.css"; +import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { settingsContext } from "@renderer/context"; import { downloadSourcesTable } from "@renderer/dexie"; import { downloadSourcesWorker } from "@renderer/workers"; +import { clearSearch, setSearch } from "@renderer/features"; +import { useNavigate } from "react-router-dom"; export function SettingsDownloadSources() { const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] = @@ -28,6 +30,10 @@ export function SettingsDownloadSources() { const { t } = useTranslation("settings"); const { showSuccessToast } = useToast(); + const dispatch = useAppDispatch(); + + const navigate = useNavigate(); + const { updateRepacks } = useRepacks(); const getDownloadSources = async () => { @@ -96,6 +102,13 @@ export function SettingsDownloadSources() { setShowAddDownloadSourceModal(false); }; + const navigateToCatalogue = (fingerprint: string) => { + dispatch(clearSearch()); + dispatch(setSearch({ downloadSourceFingerprints: [fingerprint] })); + + navigate("/catalogue"); + }; + return ( <> {statusTitle[downloadSource.status]}
-
navigateToCatalogue(downloadSource.fingerprint)} > {t("download_count", { @@ -161,7 +179,7 @@ export function SettingsDownloadSources() { downloadSource.downloadCount.toLocaleString(), })} -
+
Date: Fri, 20 Dec 2024 21:33:17 +0000 Subject: [PATCH 17/62] feat: adding link direct from sources --- src/main/events/catalogue/get-developers.ts | 10 ++++++++++ src/main/events/catalogue/get-publishers.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/main/events/catalogue/get-developers.ts create mode 100644 src/main/events/catalogue/get-publishers.ts diff --git a/src/main/events/catalogue/get-developers.ts b/src/main/events/catalogue/get-developers.ts new file mode 100644 index 000000000..76ae566b5 --- /dev/null +++ b/src/main/events/catalogue/get-developers.ts @@ -0,0 +1,10 @@ +import { HydraApi } from "@main/services"; +import { registerEvent } from "../register-event"; + +const getDevelopers = async (_event: Electron.IpcMainInvokeEvent) => { + return HydraApi.get(`/catalogue/developers`, null, { + needsAuth: false, + }); +}; + +registerEvent("getDevelopers", getDevelopers); diff --git a/src/main/events/catalogue/get-publishers.ts b/src/main/events/catalogue/get-publishers.ts new file mode 100644 index 000000000..3b8fdc5fb --- /dev/null +++ b/src/main/events/catalogue/get-publishers.ts @@ -0,0 +1,10 @@ +import { HydraApi } from "@main/services"; +import { registerEvent } from "../register-event"; + +const getPublishers = async (_event: Electron.IpcMainInvokeEvent) => { + return HydraApi.get(`/catalogue/publishers`, null, { + needsAuth: false, + }); +}; + +registerEvent("getPublishers", getPublishers); From b28d34cf661e670d0e9770afaac0565172cdf8c0 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 22:14:58 +0000 Subject: [PATCH 18/62] feat: increasing number of results --- src/main/events/catalogue/search-games.ts | 2 +- .../src/components/checkbox-field/checkbox-field.css.ts | 3 +++ src/renderer/src/pages/catalogue/catalogue.scss | 1 + src/renderer/src/pages/catalogue/catalogue.tsx | 2 +- src/renderer/src/pages/catalogue/filter-section.tsx | 2 +- 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/events/catalogue/search-games.ts b/src/main/events/catalogue/search-games.ts index dd010ae6e..40b3b033c 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -8,7 +8,7 @@ const searchGames = async ( ) => { return HydraApi.post( "/catalogue/search", - { ...payload, take: 12, skip: 0 }, + { ...payload, take: 24, skip: 0 }, { needsAuth: false } ); }; diff --git a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts b/src/renderer/src/components/checkbox-field/checkbox-field.css.ts index b4d3afc8d..ce7aead84 100644 --- a/src/renderer/src/components/checkbox-field/checkbox-field.css.ts +++ b/src/renderer/src/components/checkbox-field/checkbox-field.css.ts @@ -51,4 +51,7 @@ export const checkboxInput = style({ export const checkboxLabel = style({ cursor: "pointer", + textOverflow: "ellipsis", + overflow: "hidden", + whiteSpace: "nowrap", }); diff --git a/src/renderer/src/pages/catalogue/catalogue.scss b/src/renderer/src/pages/catalogue/catalogue.scss index ecf99931a..902cdcd81 100644 --- a/src/renderer/src/pages/catalogue/catalogue.scss +++ b/src/renderer/src/pages/catalogue/catalogue.scss @@ -100,6 +100,7 @@ border-radius: 4px; padding: 16px; border: 1px solid globals.$border-color; + align-self: flex-start; } &__ai-recommendations-button-text { diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 9bbcd3586..62bb3f790 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -404,7 +404,7 @@ export default function Catalogue() { } }} items={downloadSources.map((downloadSource) => ({ - label: `${downloadSource.name} (${downloadSource.objectIds.length})`, + label: downloadSource.name, value: downloadSource.fingerprint, checked: filters.downloadSourceFingerprints.includes( downloadSource.fingerprint diff --git a/src/renderer/src/pages/catalogue/filter-section.tsx b/src/renderer/src/pages/catalogue/filter-section.tsx index ea1b7224b..976b92c76 100644 --- a/src/renderer/src/pages/catalogue/filter-section.tsx +++ b/src/renderer/src/pages/catalogue/filter-section.tsx @@ -75,7 +75,7 @@ export function FilterSection({ 10 ? 10 : filteredItems.length)} itemHeight={28} itemKey="value" styles={{ From f37b92261fc5121e0e5a882fd71225fa2f1d0727 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 20 Dec 2024 22:40:41 +0000 Subject: [PATCH 19/62] feat: adding button to clear selected filters --- .../download-sources/put-download-source.ts | 10 +++++-- .../src/pages/catalogue/catalogue.tsx | 7 +++++ .../src/pages/catalogue/filter-section.tsx | 29 +++++++++++++++++-- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/main/events/download-sources/put-download-source.ts b/src/main/events/download-sources/put-download-source.ts index 3aefd9275..72297059e 100644 --- a/src/main/events/download-sources/put-download-source.ts +++ b/src/main/events/download-sources/put-download-source.ts @@ -5,9 +5,13 @@ const putDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, objectIds: string[] ) => { - return HydraApi.put<{ fingerprint: string }>("/download-sources", { - objectIds, - }); + return HydraApi.put<{ fingerprint: string }>( + "/download-sources", + { + objectIds, + }, + { needsAuth: false } + ); }; registerEvent("putDownloadSource", putDownloadSource); diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 62bb3f790..96bb3dd24 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -305,6 +305,7 @@ export default function Catalogue() {
dispatch(setSearch({ genres: [] }))} color={filterCategoryColors.genres} onSelect={(value) => { if (filters.genres.includes(value)) { @@ -359,6 +360,7 @@ export default function Catalogue() { dispatch(setSearch({ tags: [] }))} onSelect={(value) => { if (filters.tags.includes(value)) { dispatch( @@ -382,6 +384,9 @@ export default function Catalogue() { + dispatch(setSearch({ downloadSourceFingerprints: [] })) + } onSelect={(value) => { if (filters.downloadSourceFingerprints.includes(value)) { dispatch( @@ -415,6 +420,7 @@ export default function Catalogue() { dispatch(setSearch({ developers: [] }))} onSelect={(value) => { if (filters.developers.includes(value)) { dispatch( @@ -440,6 +446,7 @@ export default function Catalogue() { dispatch(setSearch({ publishers: [] }))} onSelect={(value) => { if (filters.publishers.includes(value)) { dispatch( diff --git a/src/renderer/src/pages/catalogue/filter-section.tsx b/src/renderer/src/pages/catalogue/filter-section.tsx index 976b92c76..8448dd962 100644 --- a/src/renderer/src/pages/catalogue/filter-section.tsx +++ b/src/renderer/src/pages/catalogue/filter-section.tsx @@ -13,6 +13,7 @@ export interface FilterSectionProps { }[]; onSelect: (value: T) => void; color: string; + onClear: () => void; } export function FilterSection({ @@ -20,6 +21,7 @@ export function FilterSection({ items, color, onSelect, + onClear, }: FilterSectionProps) { const [search, setSearch] = useState(""); @@ -33,6 +35,10 @@ export function FilterSection({ return items; }, [items, search]); + const selectedItemsCount = useMemo(() => { + return items.filter((item) => item.checked).length; + }, [items]); + const onSearch = useCallback((value: string) => { setSearch(value); }, []); @@ -61,9 +67,26 @@ export function FilterSection({
- - {formatNumber(items.length)} disponíveis - + {selectedItemsCount > 0 ? ( + + ) : ( + + {formatNumber(items.length)} disponíveis + + )} Date: Fri, 20 Dec 2024 20:31:31 -0300 Subject: [PATCH 20/62] feat: refactor component --- .../pages/achievements/achievement-list.tsx | 40 +++++++++++++++++ .../achievements/achievements-content.tsx | 43 ++----------------- src/types/index.ts | 1 + 3 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 src/renderer/src/pages/achievements/achievement-list.tsx diff --git a/src/renderer/src/pages/achievements/achievement-list.tsx b/src/renderer/src/pages/achievements/achievement-list.tsx new file mode 100644 index 000000000..e6c6166ee --- /dev/null +++ b/src/renderer/src/pages/achievements/achievement-list.tsx @@ -0,0 +1,40 @@ +import { useDate } from "@renderer/hooks"; +import type { UserAchievement } from "@types"; +import { useTranslation } from "react-i18next"; +import * as styles from "./achievements.css"; + +interface AchievementListProps { + achievements: UserAchievement[]; +} + +export function AchievementList({ achievements }: AchievementListProps) { + const { t } = useTranslation("achievement"); + const { formatDateTime } = useDate(); + + return ( +
    + {achievements.map((achievement, index) => ( +
  • + {achievement.displayName} +
    +

    {achievement.displayName}

    +

    {achievement.description}

    +
    + {achievement.unlockTime && ( +
    + {t("unlocked_at")} +

    {formatDateTime(achievement.unlockTime)}

    +
    + )} +
  • + ))} +
+ ); +} diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index a7a9aaa8b..bc15da17b 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -1,9 +1,8 @@ import { setHeaderTitle } from "@renderer/features"; -import { useAppDispatch, useDate, useUserDetails } from "@renderer/hooks"; +import { useAppDispatch, useUserDetails } from "@renderer/hooks"; import { steamUrlBuilder } from "@shared"; import { useContext, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import * as styles from "./achievements.css"; import { buildGameDetailsPath, formatDownloadProgress, @@ -11,11 +10,13 @@ import { import { LockIcon, PersonIcon, TrophyIcon } from "@primer/octicons-react"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { gameDetailsContext } from "@renderer/context"; -import type { ComparedAchievements, UserAchievement } from "@types"; +import type { ComparedAchievements } from "@types"; import { average } from "color.js"; import Color from "color"; import { Link } from "@renderer/components"; import { ComparedAchievementList } from "./compared-achievement-list"; +import * as styles from "./achievements.css"; +import { AchievementList } from "./achievement-list"; interface UserInfo { id: string; @@ -30,10 +31,6 @@ interface AchievementsContentProps { comparedAchievements: ComparedAchievements | null; } -interface AchievementListProps { - achievements: UserAchievement[]; -} - interface AchievementSummaryProps { user: UserInfo; isComparison?: boolean; @@ -171,38 +168,6 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { ); } -function AchievementList({ achievements }: AchievementListProps) { - const { t } = useTranslation("achievement"); - const { formatDateTime } = useDate(); - - return ( -
    - {achievements.map((achievement, index) => ( -
  • - {achievement.displayName} -
    -

    {achievement.displayName}

    -

    {achievement.description}

    -
    - {achievement.unlockTime && ( -
    - {t("unlocked_at")} -

    {formatDateTime(achievement.unlockTime)}

    -
    - )} -
  • - ))} -
- ); -} - export function AchievementsContent({ otherUser, comparedAchievements, diff --git a/src/types/index.ts b/src/types/index.ts index 3669a91ec..4a6575c1f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -35,6 +35,7 @@ export interface AchievementData { icon: string; icongray: string; hidden: boolean; + points?: number; } export interface UserAchievement { From 81cded0052b2d88ac3432e3ce670636d26f8e930 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:22:13 -0300 Subject: [PATCH 21/62] feat: add points to achievement list --- src/locales/ar/translation.json | 2 +- src/locales/bg/translation.json | 2 +- src/locales/cs/translation.json | 2 +- src/locales/en/translation.json | 5 ++- src/locales/es/translation.json | 2 +- src/locales/et/translation.json | 2 +- src/locales/pt-BR/translation.json | 5 ++- src/locales/pt-PT/translation.json | 2 +- src/locales/ru/translation.json | 2 +- src/locales/zh/translation.json | 2 +- src/renderer/src/assets/icons/hydra.svg | 13 ++++++ .../pages/achievements/achievement-list.tsx | 42 +++++++++++++++---- src/types/index.ts | 2 + 13 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 src/renderer/src/assets/icons/hydra.svg diff --git a/src/locales/ar/translation.json b/src/locales/ar/translation.json index 156c3da40..4dd0f73c7 100644 --- a/src/locales/ar/translation.json +++ b/src/locales/ar/translation.json @@ -383,7 +383,7 @@ "achievement_unlocked": "تم فتح الإنجاز", "user_achievements": "{{displayName}}إنجازات", "your_achievements": "إنجازاتك", - "unlocked_at": "مقفلة في:", + "unlocked_at": "مقفلة في: {{date}}", "subscription_needed": "مطلوب اشتراك Hydra Cloud لرؤية هذا المحتوى", "new_achievements_unlocked": "مفتوح {{achievementCount}} انجازات جديدة من {{gameCount}} ألعاب", "achievement_progress": "{{unlockedCount}}/{{totalCount}} الإنجازات", diff --git a/src/locales/bg/translation.json b/src/locales/bg/translation.json index a1a5306fa..521d1cdea 100644 --- a/src/locales/bg/translation.json +++ b/src/locales/bg/translation.json @@ -362,7 +362,7 @@ "achievement_unlocked": "Постижението е отключено", "user_achievements": "Постиженията на {{displayName}} ", "your_achievements": "Вашите Постижения", - "unlocked_at": "Отключено на:", + "unlocked_at": "Отключено на: {{date}}", "subscription_needed": "Необходим е абонамент за Hydra Cloud, за да видите това съдържание", "new_achievements_unlocked": "Отключени {{achievementCount}} нови постижения от {{gameCount}} игра", "achievement_progress": "{{unlockedCount}}/{{totalCount}} постижения", diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index d839fa461..fab790cb1 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -362,7 +362,7 @@ "achievement_unlocked": "Achievement odemčen", "user_achievements": "Achievementy uživatele {{displayName}}", "your_achievements": "Vaše achievementy", - "unlocked_at": "Odemčeno:", + "unlocked_at": "Odemčeno: {{date}}", "subscription_needed": "Je vyžadováno předplatné Hydra Cloud pro zobrazení tohoto obsahu", "new_achievements_unlocked": "Odemčeno {{achievementCount}} nových achievementů z {{gameCount}} her", "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementů", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b66b4da60..d792d21a3 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -368,11 +368,12 @@ "achievement_unlocked": "Achievement unlocked", "user_achievements": "{{displayName}}'s Achievements", "your_achievements": "Your Achievements", - "unlocked_at": "Unlocked at:", + "unlocked_at": "Unlocked at: {{date}}", "subscription_needed": "A Hydra Cloud subscription is required to see this content", "new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games", "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievements", - "achievements_unlocked_for_game": "Unlocked {{achievementCount}} new achievements for {{gameTitle}}" + "achievements_unlocked_for_game": "Unlocked {{achievementCount}} new achievements for {{gameTitle}}", + "hidden_achievement_tooltip": "This is a hidden achievement" }, "tour": { "subscription_tour_title": "Hydra Cloud Subscription", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index a766be1c0..2431c46b2 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -364,7 +364,7 @@ "achievement_unlocked": "Logro desbloqueado", "user_achievements": "Logros de {{displayName}}", "your_achievements": "Tus Logros", - "unlocked_at": "Desbloqueado el:", + "unlocked_at": "Desbloqueado el: {{date}}", "subscription_needed": "Se necesita una suscripción a Hydra Cloud necesita para ver este contenido", "new_achievements_unlocked": "Desbloqueados {{achievementCount}} nuevos logros de {{gameCount}} juegos", "achievement_progress": "{{unlockedCount}}/{{totalCount}} logros", diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json index 9a01fd509..5d059a049 100644 --- a/src/locales/et/translation.json +++ b/src/locales/et/translation.json @@ -359,7 +359,7 @@ "achievement_unlocked": "Saavutus avatud", "user_achievements": "{{displayName}} saavutused", "your_achievements": "Sinu saavutused", - "unlocked_at": "Avatud:", + "unlocked_at": "Avatud: {{date}}", "subscription_needed": "Selle sisu nägemiseks on vaja Hydra Cloud tellimust", "new_achievements_unlocked": "Avatud {{achievementCount}} uut saavutust {{gameCount}} mängust" }, diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 1bf4d27ef..079151511 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -366,11 +366,12 @@ "achievement_unlocked": "Conquista desbloqueada", "your_achievements": "Suas Conquistas", "user_achievements": "Conquistas de {{displayName}}", - "unlocked_at": "Desbloqueado em:", + "unlocked_at": "Desbloqueada em: {{date}}", "subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo", "new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos", "achievement_progress": "{{unlockedCount}}/{{totalCount}} conquistas", - "achievements_unlocked_for_game": "Desbloqueadas {{achievementCount}} novas conquistas em {{gameTitle}}" + "achievements_unlocked_for_game": "Desbloqueadas {{achievementCount}} novas conquistas em {{gameTitle}}", + "hidden_achievement_tooltip": "Está é uma conquista oculta" }, "tour": { "subscription_tour_title": "Assinatura Hydra Cloud", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 36320dc8e..a847ad707 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -356,7 +356,7 @@ "achievement_unlocked": "Conquista desbloqueada", "your_achievements": "As tuas Conquistas", "user_achievements": "Conquistas de {{displayName}}", - "unlocked_at": "Desbloqueada em:", + "unlocked_at": "Desbloqueada em: {{date}}", "subscription_needed": "Precisas de uma subscrição Hydra Cloud para visualizar este conteúdo", "new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos" }, diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index ddeace23d..afc2b7907 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -358,7 +358,7 @@ "achievement_unlocked": "Достижение разблокировано", "user_achievements": "Достижения {{displayName}}", "your_achievements": "Ваши достижения", - "unlocked_at": "Разблокировано:", + "unlocked_at": "Разблокировано: {{date}}", "subscription_needed": "Для просмотра этого содержимого необходима подписка на Hydra Cloud", "new_achievements_unlocked": "Разблокировано {{achievementCount}} новых достижений из {{gameCount}} игр", "achievement_progress": "{{unlockedCount}}/{{totalCount}} достижений", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 9bab7516d..b44f37a9c 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -359,7 +359,7 @@ "achievement_unlocked": "成就已解锁", "user_achievements": "{{displayName}}的成就", "your_achievements": "你的成就", - "unlocked_at": "解锁于:", + "unlocked_at": "解锁于: {{date}}", "subscription_needed": "需要订阅 Hydra Cloud 才能看到此内容", "new_achievements_unlocked": "从 {{gameCount}} 游戏中解锁 {{achievementCount}} 新成就" }, diff --git a/src/renderer/src/assets/icons/hydra.svg b/src/renderer/src/assets/icons/hydra.svg new file mode 100644 index 000000000..ce1e5cf0f --- /dev/null +++ b/src/renderer/src/assets/icons/hydra.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/renderer/src/pages/achievements/achievement-list.tsx b/src/renderer/src/pages/achievements/achievement-list.tsx index e6c6166ee..353cd8770 100644 --- a/src/renderer/src/pages/achievements/achievement-list.tsx +++ b/src/renderer/src/pages/achievements/achievement-list.tsx @@ -2,6 +2,8 @@ import { useDate } from "@renderer/hooks"; import type { UserAchievement } from "@types"; import { useTranslation } from "react-i18next"; import * as styles from "./achievements.css"; +import { CalendarIcon, EyeClosedIcon } from "@primer/octicons-react"; +import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; interface AchievementListProps { achievements: UserAchievement[]; @@ -23,16 +25,42 @@ export function AchievementList({ achievements }: AchievementListProps) { alt={achievement.displayName} loading="lazy" /> +
-

{achievement.displayName}

+

+ {achievement.hidden && ( + + + + )} + {achievement.displayName} +

{achievement.description}

- {achievement.unlockTime && ( -
- {t("unlocked_at")} -

{formatDateTime(achievement.unlockTime)}

-
- )} +
+ {achievement.points && ( +
+ +

{achievement.points}

+
+ )} + {achievement.unlockTime && ( +
+ {formatDateTime(achievement.unlockTime)} +
+ )} +
))} diff --git a/src/types/index.ts b/src/types/index.ts index 4a6575c1f..c6d16987a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -362,12 +362,14 @@ export interface ComparedAchievements { owner: { totalAchievementCount: number; unlockedAchievementCount: number; + achievementsPointsEarnedSum?: number; }; target: { displayName: string; profileImageUrl: string; totalAchievementCount: number; unlockedAchievementCount: number; + achievementsPointsEarnedSum: number; }; achievements: { hidden: boolean; From 06e59fdfa0196062e3a3567ba51a5c90c740cc34 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 21 Dec 2024 20:49:34 -0300 Subject: [PATCH 22/62] feat: adding panel --- .../pages/achievements/achievement-list.tsx | 2 +- .../achievements/achievement-panel.css.ts | 65 +++++++++++++++++++ .../pages/achievements/achievement-panel.tsx | 37 +++++++++++ .../achievements/achievements-content.tsx | 11 +++- 4 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 src/renderer/src/pages/achievements/achievement-panel.css.ts create mode 100644 src/renderer/src/pages/achievements/achievement-panel.tsx diff --git a/src/renderer/src/pages/achievements/achievement-list.tsx b/src/renderer/src/pages/achievements/achievement-list.tsx index 353cd8770..afea65225 100644 --- a/src/renderer/src/pages/achievements/achievement-list.tsx +++ b/src/renderer/src/pages/achievements/achievement-list.tsx @@ -2,7 +2,7 @@ import { useDate } from "@renderer/hooks"; import type { UserAchievement } from "@types"; import { useTranslation } from "react-i18next"; import * as styles from "./achievements.css"; -import { CalendarIcon, EyeClosedIcon } from "@primer/octicons-react"; +import { EyeClosedIcon } from "@primer/octicons-react"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; interface AchievementListProps { diff --git a/src/renderer/src/pages/achievements/achievement-panel.css.ts b/src/renderer/src/pages/achievements/achievement-panel.css.ts new file mode 100644 index 000000000..58888c2c4 --- /dev/null +++ b/src/renderer/src/pages/achievements/achievement-panel.css.ts @@ -0,0 +1,65 @@ +import { style } from "@vanilla-extract/css"; +import { recipe } from "@vanilla-extract/recipes"; + +import { SPACING_UNIT, vars } from "../../theme.css"; + +export const panel = style({ + width: "100%", + height: "72px", + minHeight: "72px", + padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 3}px`, + backgroundColor: vars.color.background, + display: "flex", + alignItems: "center", + justifyContent: "space-between", + transition: "all ease 0.2s", + borderBottom: `solid 1px ${vars.color.border}`, + overflow: "hidden", +}); + +export const content = style({ + display: "flex", + flexDirection: "column", + gap: `${SPACING_UNIT}px`, +}); + +export const actions = style({ + display: "flex", + gap: `${SPACING_UNIT}px`, +}); + +export const downloadDetailsRow = style({ + gap: `${SPACING_UNIT}px`, + display: "flex", + color: vars.color.body, + alignItems: "center", +}); + +export const downloadsLink = style({ + color: vars.color.body, + textDecoration: "underline", +}); + +export const progressBar = recipe({ + base: { + position: "absolute", + bottom: "0", + left: "0", + width: "100%", + height: "3px", + transition: "all ease 0.2s", + "::-webkit-progress-bar": { + backgroundColor: "transparent", + }, + "::-webkit-progress-value": { + backgroundColor: vars.color.muted, + }, + }, + variants: { + disabled: { + true: { + opacity: vars.opacity.disabled, + }, + }, + }, +}); diff --git a/src/renderer/src/pages/achievements/achievement-panel.tsx b/src/renderer/src/pages/achievements/achievement-panel.tsx new file mode 100644 index 000000000..aaf9c97cf --- /dev/null +++ b/src/renderer/src/pages/achievements/achievement-panel.tsx @@ -0,0 +1,37 @@ +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; + +import { useDate, useDownload } from "@renderer/hooks"; + +import * as styles from "./achievement-panel.css"; + +import { gameDetailsContext } from "@renderer/context"; + +export interface HeroPanelProps { + isHeaderStuck: boolean; +} + +export function AchievementPanel({ isHeaderStuck }: HeroPanelProps) { + const { t } = useTranslation("game_details"); + + const { formatDate } = useDate(); + + const { game, repacks, gameColor } = useContext(gameDetailsContext); + + const { lastPacket } = useDownload(); + + const isGameDownloading = + game?.status === "active" && lastPacket?.game.id === game?.id; + + const showProgressBar = + (game?.status === "active" && game?.progress < 1) || + game?.status === "paused"; + + return ( + <> +
+
Teste 123131312
+
+ + ); +} diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index bc15da17b..8fc530c53 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -17,6 +17,7 @@ import { Link } from "@renderer/components"; import { ComparedAchievementList } from "./compared-achievement-list"; import * as styles from "./achievements.css"; import { AchievementList } from "./achievement-list"; +import { AchievementPanel } from "./achievement-panel"; interface UserInfo { id: string; @@ -320,9 +321,15 @@ export function AchievementsContent({ )} {otherUser ? ( - + <> + + + ) : ( - + <> + + + )} From 1522b00579704e2eb129bcf9c1f2f22d07dd9580 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:31:30 -0300 Subject: [PATCH 23/62] feat: some visuals --- .../achievements/achievement-panel.css.ts | 8 +-- .../pages/achievements/achievement-panel.tsx | 23 +++------ .../achievements/achievements-content.tsx | 3 +- .../compared-achievement-list.tsx | 8 +-- .../compared-achievement-panel.tsx | 49 +++++++++++++++++++ .../profile-content/user-stats-box.tsx | 30 ++++++++---- 6 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 src/renderer/src/pages/achievements/compared-achievement-panel.tsx diff --git a/src/renderer/src/pages/achievements/achievement-panel.css.ts b/src/renderer/src/pages/achievements/achievement-panel.css.ts index 58888c2c4..0056a7f53 100644 --- a/src/renderer/src/pages/achievements/achievement-panel.css.ts +++ b/src/renderer/src/pages/achievements/achievement-panel.css.ts @@ -5,22 +5,18 @@ import { SPACING_UNIT, vars } from "../../theme.css"; export const panel = style({ width: "100%", - height: "72px", - minHeight: "72px", - padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 3}px`, + padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`, backgroundColor: vars.color.background, display: "flex", alignItems: "center", justifyContent: "space-between", - transition: "all ease 0.2s", borderBottom: `solid 1px ${vars.color.border}`, - overflow: "hidden", }); export const content = style({ display: "flex", - flexDirection: "column", gap: `${SPACING_UNIT}px`, + justifyContent: "center", }); export const actions = style({ diff --git a/src/renderer/src/pages/achievements/achievement-panel.tsx b/src/renderer/src/pages/achievements/achievement-panel.tsx index aaf9c97cf..bf48e5172 100644 --- a/src/renderer/src/pages/achievements/achievement-panel.tsx +++ b/src/renderer/src/pages/achievements/achievement-panel.tsx @@ -1,11 +1,9 @@ import { useContext } from "react"; import { useTranslation } from "react-i18next"; - -import { useDate, useDownload } from "@renderer/hooks"; - +import { gameDetailsContext } from "@renderer/context"; import * as styles from "./achievement-panel.css"; -import { gameDetailsContext } from "@renderer/context"; +import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; export interface HeroPanelProps { isHeaderStuck: boolean; @@ -14,23 +12,14 @@ export interface HeroPanelProps { export function AchievementPanel({ isHeaderStuck }: HeroPanelProps) { const { t } = useTranslation("game_details"); - const { formatDate } = useDate(); - - const { game, repacks, gameColor } = useContext(gameDetailsContext); - - const { lastPacket } = useDownload(); - - const isGameDownloading = - game?.status === "active" && lastPacket?.game.id === game?.id; - - const showProgressBar = - (game?.status === "active" && game?.progress < 1) || - game?.status === "paused"; + const {} = useContext(gameDetailsContext); return ( <>
-
Teste 123131312
+
+ Pontos desbloqueados: 69/420 +
); diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index 8fc530c53..c633aefea 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -18,6 +18,7 @@ import { ComparedAchievementList } from "./compared-achievement-list"; import * as styles from "./achievements.css"; import { AchievementList } from "./achievement-list"; import { AchievementPanel } from "./achievement-panel"; +import { ComparedAchievementPanel } from "./compared-achievement-panel"; interface UserInfo { id: string; @@ -322,7 +323,7 @@ export function AchievementsContent({ {otherUser ? ( <> - + ) : ( diff --git a/src/renderer/src/pages/achievements/compared-achievement-list.tsx b/src/renderer/src/pages/achievements/compared-achievement-list.tsx index 21f119364..349ef755c 100644 --- a/src/renderer/src/pages/achievements/compared-achievement-list.tsx +++ b/src/renderer/src/pages/achievements/compared-achievement-list.tsx @@ -58,11 +58,9 @@ export function ComparedAchievementList({ gap: `${SPACING_UNIT}px`, justifyContent: "center", }} + title={formatDateTime(achievement.ownerStat.unlockTime!)} > - - {formatDateTime(achievement.ownerStat.unlockTime!)} - ) : (
- - {formatDateTime(achievement.targetStat.unlockTime!)} -
) : (
+
+
+ Total de pontos: 4200 +
+ {achievements.owner.achievementsPointsEarnedSum !== undefined && ( +
+ + {achievements.owner.achievementsPointsEarnedSum} +
+ )} +
+ + {achievements.target.achievementsPointsEarnedSum} +
+
+ + ); +} diff --git a/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx b/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx index 888b03d4e..f89cba025 100644 --- a/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx @@ -4,6 +4,7 @@ import { userProfileContext } from "@renderer/context"; import { useTranslation } from "react-i18next"; import { useFormat } from "@renderer/hooks"; import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; +import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; export function UserStatsBox() { const { userStats } = useContext(userProfileContext); @@ -42,21 +43,30 @@ export function UserStatsBox() { {userStats.achievementsPointsEarnedSum && (
  • {t("achievements")}

    -

    - Total points: {userStats.achievementsPointsEarnedSum.value} - - Top {userStats.achievementsPointsEarnedSum.topPercentile}%{" "} -

    -

    Unlocked: {userStats.unlockedAchievementSum}

    +
    +

    + + {userStats.achievementsPointsEarnedSum.value} points +

    +

    + Top {userStats.achievementsPointsEarnedSum.topPercentile}%{" "} +

    +
    +

    Unlock count: {userStats.unlockedAchievementSum}

  • )}
  • {t("games")}

    -

    - Total playtime:{" "} - {formatPlayTime(userStats.totalPlayTimeInSeconds.value)} - Top{" "} - {userStats.totalPlayTimeInSeconds.topPercentile}% -

    +
    +

    + Total playtime:{" "} + {formatPlayTime(userStats.totalPlayTimeInSeconds.value)} +

    +

    Top {userStats.totalPlayTimeInSeconds.topPercentile}%

    +
  • From 93bc7c690ff1b42d39f2ed2816b87a4dfa1a7561 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 22 Dec 2024 12:38:31 -0300 Subject: [PATCH 24/62] feat: ui adjustments --- src/locales/en/translation.json | 6 ++- src/locales/pt-BR/translation.json | 6 ++- .../pages/achievements/achievement-list.tsx | 4 +- .../achievements/achievement-panel.css.ts | 2 +- .../compared-achievement-panel.tsx | 44 +++++++++---------- src/types/index.ts | 1 + 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 2d40976a7..3b3ad552d 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -168,7 +168,8 @@ "select_folder": "Select folder", "backup_from": "Backup from {{date}}", "custom_backup_location_set": "Custom backup location set", - "no_directory_selected": "No directory selected" + "no_directory_selected": "No directory selected", + "available_points": "Available points:" }, "activation": { "title": "Activate Hydra", @@ -378,7 +379,8 @@ "new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games", "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievements", "achievements_unlocked_for_game": "Unlocked {{achievementCount}} new achievements for {{gameTitle}}", - "hidden_achievement_tooltip": "This is a hidden achievement" + "hidden_achievement_tooltip": "This is a hidden achievement", + "achievement_earn_points": "Earn {{points}} with this achievement" }, "tour": { "subscription_tour_title": "Hydra Cloud Subscription", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 45f8e9d97..c8e72eb19 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -164,7 +164,8 @@ "select_folder": "Selecione a pasta", "manage_files_description": "Gerencie quais arquivos serão feitos backup", "clear": "Limpar", - "no_directory_selected": "Nenhum diretório selecionado" + "no_directory_selected": "Nenhum diretório selecionado", + "available_points": "Pontos disponíveis:" }, "activation": { "title": "Ativação", @@ -376,7 +377,8 @@ "new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos", "achievement_progress": "{{unlockedCount}}/{{totalCount}} conquistas", "achievements_unlocked_for_game": "Desbloqueadas {{achievementCount}} novas conquistas em {{gameTitle}}", - "hidden_achievement_tooltip": "Está é uma conquista oculta" + "hidden_achievement_tooltip": "Está é uma conquista oculta", + "achievement_earn_points": "Ganhe {{points}} com essa conquista" }, "tour": { "subscription_tour_title": "Assinatura Hydra Cloud", diff --git a/src/renderer/src/pages/achievements/achievement-list.tsx b/src/renderer/src/pages/achievements/achievement-list.tsx index afea65225..0bd09a692 100644 --- a/src/renderer/src/pages/achievements/achievement-list.tsx +++ b/src/renderer/src/pages/achievements/achievement-list.tsx @@ -44,7 +44,9 @@ export function AchievementList({ achievements }: AchievementListProps) { {achievement.points && (

    {achievement.points}

    diff --git a/src/renderer/src/pages/achievements/achievement-panel.css.ts b/src/renderer/src/pages/achievements/achievement-panel.css.ts index 0056a7f53..1882a8ef4 100644 --- a/src/renderer/src/pages/achievements/achievement-panel.css.ts +++ b/src/renderer/src/pages/achievements/achievement-panel.css.ts @@ -5,7 +5,7 @@ import { SPACING_UNIT, vars } from "../../theme.css"; export const panel = style({ width: "100%", - padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`, + padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 3}px`, backgroundColor: vars.color.background, display: "flex", alignItems: "center", diff --git a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx index 6912e59e7..0f35aa5dc 100644 --- a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx +++ b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx @@ -19,31 +19,31 @@ export function ComparedAchievementPanel({ const {} = useContext(gameDetailsContext); return ( - <> -
    -
    - Total de pontos: 4200 -
    - {achievements.owner.achievementsPointsEarnedSum !== undefined && ( -
    - - {achievements.owner.achievementsPointsEarnedSum} -
    - )} +
    +
    + {t("available_points")} {" "} + {achievements.achievementsPointsTotal} +
    + {achievements.owner.achievementsPointsEarnedSum !== undefined && (
    - {achievements.target.achievementsPointsEarnedSum} + {achievements.owner.achievementsPointsEarnedSum}
    + )} +
    + + {achievements.target.achievementsPointsEarnedSum}
    - +
    ); } diff --git a/src/types/index.ts b/src/types/index.ts index 28780e7eb..37ecb2d2b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -368,6 +368,7 @@ export interface GameArtifact { } export interface ComparedAchievements { + achievementsPointsTotal: number; owner: { totalAchievementCount: number; unlockedAchievementCount: number; From 041b7d520ce32b4a57825042a90c7cab8fcb8895 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:47:13 -0300 Subject: [PATCH 25/62] feat: ui adjustments and add friend playing game --- src/locales/en/translation.json | 5 +++- src/locales/pt-BR/translation.json | 5 ++-- src/main/events/user/get-user.ts | 19 +++++++++++++- src/main/services/hydra-api.ts | 2 +- .../profile/profile-content/friends-box.tsx | 12 ++++++++- .../profile-content/user-stats-box.tsx | 26 +++++++++++++------ src/types/index.ts | 7 +++++ 7 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 3b3ad552d..2c81eac22 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -368,7 +368,10 @@ "background_image_updated": "Background image updated", "stats": "Stats", "achievements": "Achievements", - "games": "Games" + "games": "Games", + "top_percentile": "Top {{percentile}}%", + "ranking_updated_weekly": "Ranking is updated weekly", + "playing": "Playing {{game}}" }, "achievement": { "achievement_unlocked": "Achievement unlocked", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index c8e72eb19..0ed8a3b45 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -300,7 +300,7 @@ "last_time_played": "Última sessão {{period}}", "activity": "Atividades recentes", "library": "Biblioteca", - "total_play_time": "Tempo total de jogo: {{amount}}", + "total_play_time": "Tempo de jogo: {{amount}}", "no_recent_activity_title": "Hmmm… nada por aqui", "no_recent_activity_description": "Parece que você não jogou nada recentemente. Que tal começar agora?", "display_name": "Nome de exibição", @@ -366,7 +366,8 @@ "background_image_updated": "Imagem de fundo salva", "stats": "Estatísticas", "achievements": "Conquistas", - "games": "Jogos" + "games": "Jogos", + "ranking_updated_weekly": "Ranking é atualizado semanalmente" }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", diff --git a/src/main/events/user/get-user.ts b/src/main/events/user/get-user.ts index 6bbab9c45..f51b0456f 100644 --- a/src/main/events/user/get-user.ts +++ b/src/main/events/user/get-user.ts @@ -11,7 +11,7 @@ const getSteamGame = async (objectId: string) => { }); return { - title: steamGame.name, + title: steamGame.name as string, iconUrl: steamUrlBuilder.icon(objectId, steamGame.clientIcon), }; } catch (err) { @@ -67,8 +67,25 @@ const getUser = async ( } } + const friends = await Promise.all( + profile.friends.map(async (friend) => { + if (!friend.currentGame) return friend; + + const currentGame = await getSteamGame(friend.currentGame.objectId); + + return { + ...friend, + currentGame: { + ...friend.currentGame, + ...currentGame, + }, + }; + }) + ); + return { ...profile, + friends, libraryGames, recentGames, }; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 49e2db36c..63dd9b16f 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -30,7 +30,7 @@ export class HydraApi { private static instance: AxiosInstance; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes - private static readonly ADD_LOG_INTERCEPTOR = false; + private static readonly ADD_LOG_INTERCEPTOR = true; private static secondsToMilliseconds = (seconds: number) => seconds * 1000; diff --git a/src/renderer/src/pages/profile/profile-content/friends-box.tsx b/src/renderer/src/pages/profile/profile-content/friends-box.tsx index 82d4ff9df..4d3df7e89 100644 --- a/src/renderer/src/pages/profile/profile-content/friends-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/friends-box.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import * as styles from "./profile-content.css"; import { Avatar, Link } from "@renderer/components"; +import { buildGameDetailsPath } from "@renderer/helpers"; export function FriendsBox() { const { userProfile, userStats } = useContext(userProfileContext); @@ -35,7 +36,16 @@ export function FriendsBox() { alt={friend.displayName} /> - {friend.displayName} +
    + + {friend.displayName} + + {friend.currentGame && ( + +

    {t("playing", { game: friend.currentGame.title })}

    + + )} +
    ))} diff --git a/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx b/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx index f89cba025..40096d80b 100644 --- a/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-stats-box.tsx @@ -42,16 +42,19 @@ export function UserStatsBox() {
      {userStats.achievementsPointsEarnedSum && (
    • -

      {t("achievements")}

      +

      {t("achievements")}

      - {userStats.achievementsPointsEarnedSum.value} points + {userStats.achievementsPointsEarnedSum.value}

      -

      - Top {userStats.achievementsPointsEarnedSum.topPercentile}%{" "} +

      + {t("top_percentile", { + percentile: + userStats.achievementsPointsEarnedSum.topPercentile, + })}

      Unlock count: {userStats.unlockedAchievementSum}

      @@ -59,13 +62,20 @@ export function UserStatsBox() { )}
    • -

      {t("games")}

      +

      {t("games")}

      - Total playtime:{" "} - {formatPlayTime(userStats.totalPlayTimeInSeconds.value)} + {t("total_play_time", { + amount: formatPlayTime( + userStats.totalPlayTimeInSeconds.value + ), + })} +

      +

      + {t("top_percentile", { + percentile: userStats.totalPlayTimeInSeconds.topPercentile, + })}

      -

      Top {userStats.totalPlayTimeInSeconds.topPercentile}%

    diff --git a/src/types/index.ts b/src/types/index.ts index 37ecb2d2b..7cca371c6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -208,6 +208,13 @@ export interface UserFriend { profileImageUrl: string | null; createdAt: string; updatedAt: string; + currentGame: { + title: string; + iconUrl: string; + objectId: string; + shop: GameShop; + sessionDurationInSeconds: number; + } | null; } export interface UserFriends { From 313ebc60550a03c13c2330931980b67c3b0dbd6e Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:38:55 -0300 Subject: [PATCH 26/62] feat: cloud modal --- src/locales/ar/translation.json | 4 +- src/locales/bg/translation.json | 4 +- src/locales/ca/translation.json | 2 +- src/locales/cs/translation.json | 4 +- src/locales/da/translation.json | 2 +- src/locales/de/translation.json | 2 +- src/locales/en/translation.json | 8 +- src/locales/es/translation.json | 7 +- src/locales/et/translation.json | 4 +- src/locales/id/translation.json | 2 +- src/locales/kk/translation.json | 2 +- src/locales/nb/translation.json | 2 +- src/locales/pt-BR/translation.json | 11 +-- src/locales/pt-PT/translation.json | 4 +- src/locales/ru/translation.json | 4 +- src/locales/uk/translation.json | 2 +- src/locales/zh/translation.json | 4 +- src/renderer/src/app.tsx | 17 ++-- .../game-details/game-details.context.tsx | 7 -- .../game-details.context.types.ts | 1 - src/renderer/src/features/index.ts | 1 + .../src/features/subscription-slice.ts | 25 ++++++ src/renderer/src/hooks/use-subscription.ts | 28 +++++++ .../pages/achievements/achievement-list.tsx | 22 +++++- .../achievements/achievements-content.tsx | 5 +- .../game-details/game-details-content.tsx | 6 +- .../pages/game-details/sidebar/sidebar.tsx | 17 ++-- .../profile-content/profile-content.css.ts | 9 +++ .../profile-content/user-stats-box.tsx | 69 +++++++++------- .../hydra-cloud/hydra-cloud-modal.css.ts | 79 +++++++++++++++++++ .../hydra-cloud/hydra-cloud-modal.tsx | 36 +++++++++ src/renderer/src/store.ts | 2 + 32 files changed, 304 insertions(+), 88 deletions(-) create mode 100644 src/renderer/src/features/subscription-slice.ts create mode 100644 src/renderer/src/hooks/use-subscription.ts create mode 100644 src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.css.ts create mode 100644 src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx diff --git a/src/locales/ar/translation.json b/src/locales/ar/translation.json index 4dd0f73c7..a4724b21e 100644 --- a/src/locales/ar/translation.json +++ b/src/locales/ar/translation.json @@ -309,7 +309,7 @@ "last_time_played": "لعبت آخر مرة {{period}}", "activity": "النشاط الأخير", "library": "مكتبة", - "total_play_time": "إجمالي وقت اللعب: {{amount}}", + "total_play_time": "إجمالي وقت اللعب", "no_recent_activity_title": "هممم... لا شيء هنا", "no_recent_activity_description": "لم تلعب أي مباراة مؤخرًا. ", "display_name": "اسم العرض", @@ -389,7 +389,7 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} الإنجازات", "achievements_unlocked_for_game": "مفتوح {{achievementCount}} انجازات جديدة ل {{gameTitle}}" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "اشتراك Hydra كلاود", "subscribe_now": "اشترك الآن", "cloud_saving": "الحفظ السحابي", diff --git a/src/locales/bg/translation.json b/src/locales/bg/translation.json index 521d1cdea..3112bb08a 100644 --- a/src/locales/bg/translation.json +++ b/src/locales/bg/translation.json @@ -293,7 +293,7 @@ "last_time_played": "Последно играно {{period}}", "activity": "Скорошна активност", "library": "Библиотека", - "total_play_time": "Общо време за игра: {{amount}}", + "total_play_time": "Общо време за игра", "no_recent_activity_title": "Хмм… няма нищо тук", "no_recent_activity_description": "Не сте играли игри напоследък. Време е да промените това.!", "display_name": "Показване на името", @@ -368,7 +368,7 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} постижения", "achievements_unlocked_for_game": "Отключени {{achievementCount}} нови постижения за {{gameTitle}}" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Hydra Cloud Абонамент", "subscribe_now": "Абонирай се сега", "cloud_saving": "Запазване в облака", diff --git a/src/locales/ca/translation.json b/src/locales/ca/translation.json index 8b96486f1..acf4b3c7b 100644 --- a/src/locales/ca/translation.json +++ b/src/locales/ca/translation.json @@ -224,7 +224,7 @@ "last_time_played": "Última partida {{period}}", "activity": "Activitat recent", "library": "Biblioteca", - "total_play_time": "Temps total de joc:{{amount}}", + "total_play_time": "Temps total de joc", "no_recent_activity_title": "Hmmm… encara no res", "no_recent_activity_description": "No has jugat a cap joc recentment. És el moment de canviar-ho!", "display_name": "Nom de visualització", diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index fab790cb1..c12914446 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -293,7 +293,7 @@ "last_time_played": "Naposledy hráno {{period}}", "activity": "Nedávná aktivita", "library": "Knihovna", - "total_play_time": "Celkový odehraný čas: {{amount}}", + "total_play_time": "Celkový odehraný čas", "no_recent_activity_title": "Hmmm… nic tu není", "no_recent_activity_description": "V poslední době si nehrál žádnout hru, můžeš to ale napravit!", "display_name": "Zobrazované jméno", @@ -368,7 +368,7 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementů", "achievements_unlocked_for_game": "Odemčeno {{achievementCount}} nových achievementů pro {{gameTitle}}" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Předplatné Hydra Cloud", "subscribe_now": "Připojit se", "cloud_saving": "Ukládání v cloudu", diff --git a/src/locales/da/translation.json b/src/locales/da/translation.json index 58087bfa2..711c81a34 100644 --- a/src/locales/da/translation.json +++ b/src/locales/da/translation.json @@ -251,7 +251,7 @@ "last_time_played": "Sidst spillet {{period}}", "activity": "Seneste aktivitet", "library": "Bibliotek", - "total_play_time": "Samlet spiltid: {{amount}}", + "total_play_time": "Samlet spiltid", "no_recent_activity_title": "Hmmm… ikke noget her", "no_recent_activity_description": "Du har ikke spillet nogen spil for nyligt. Dét er det på tide at lave om på!", "display_name": "Brugernavn", diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 2852430f0..bf1eff601 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -224,7 +224,7 @@ "last_time_played": "Zuletzt gespielt {{period}}", "activity": "Letzte Aktivität", "library": "Bibliothek", - "total_play_time": "Gesamtspielzeit: {{amount}}", + "total_play_time": "Gesamtspielzeit", "no_recent_activity_title": "Hmmm… hier ist nichts", "no_recent_activity_description": "Du hast in letzter Zeit keine Spiele gespielt. Es wird Zeit das zu ändern!", "display_name": "Anzeigename", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 2c81eac22..1ba8b6e26 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -163,7 +163,7 @@ "no_download_option_info": "No information available", "backup_deletion_failed": "Failed to delete backup", "max_number_of_artifacts_reached": "Maximum number of backups reached for this game", - "achievements_not_sync": "Your achievements are not synchronized", + "achievements_not_sync": "See how to synchronize your achievements", "manage_files_description": "Manage which files will be backed up and restored", "select_folder": "Select folder", "backup_from": "Backup from {{date}}", @@ -302,7 +302,7 @@ "last_time_played": "Last played {{period}}", "activity": "Recent Activity", "library": "Library", - "total_play_time": "Total playtime: {{amount}}", + "total_play_time": "Total playtime", "no_recent_activity_title": "Hmmm… nothing here", "no_recent_activity_description": "You haven't played any games recently. It's time to change that!", "display_name": "Display name", @@ -383,9 +383,9 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievements", "achievements_unlocked_for_game": "Unlocked {{achievementCount}} new achievements for {{gameTitle}}", "hidden_achievement_tooltip": "This is a hidden achievement", - "achievement_earn_points": "Earn {{points}} with this achievement" + "achievement_earn_points": "Earn {{points}} points with this achievement" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Hydra Cloud Subscription", "subscribe_now": "Subscribe now", "cloud_saving": "Cloud saving", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 2431c46b2..a9261c9d6 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -295,7 +295,7 @@ "last_time_played": "Última vez jugado: {{period}}", "activity": "Actividad reciente", "library": "Biblioteca", - "total_play_time": "Total de tiempo jugado: {{amount}}", + "total_play_time": "Has jugado", "no_recent_activity_title": "Que raro, no hay nada por acá...", "no_recent_activity_description": "No has jugado ningún juego recientemente, ¡vamos a cambiar eso ahora!", "display_name": "Nombre en pantalla", @@ -358,7 +358,8 @@ "your_friend_code": "Tu código de amigo:", "upload_banner": "Subir un banner", "uploading_banner": "Subiendo banner…", - "background_image_updated": "Imagen de fondo actualizada" + "background_image_updated": "Imagen de fondo actualizada", + "playing": "Jugando {{game}}" }, "achievement": { "achievement_unlocked": "Logro desbloqueado", @@ -370,7 +371,7 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} logros", "achievements_unlocked_for_game": "Se han desbloqueado {{achievementCount}} nuevos logros de {{gameTitle}}" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Suscripción Hydra Cloud", "subscribe_now": "Suscribirse ahora", "cloud_saving": "Guardado en la nube", diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json index 5d059a049..91b4a63a8 100644 --- a/src/locales/et/translation.json +++ b/src/locales/et/translation.json @@ -290,7 +290,7 @@ "last_time_played": "Viimati mängitud {{period}}", "activity": "Hiljutine aktiivsus", "library": "Kogu", - "total_play_time": "Kogu mängitud aeg: {{amount}}", + "total_play_time": "Kogu mängitud aeg", "no_recent_activity_title": "Hmmm… siin pole midagi", "no_recent_activity_description": "Sa pole hiljuti ühtegi mängu mänginud. On aeg seda muuta!", "display_name": "Kuvatav nimi", @@ -363,7 +363,7 @@ "subscription_needed": "Selle sisu nägemiseks on vaja Hydra Cloud tellimust", "new_achievements_unlocked": "Avatud {{achievementCount}} uut saavutust {{gameCount}} mängust" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Hydra Cloud Tellimus", "subscribe_now": "Telli kohe", "cloud_saving": "Pilvesalvestus", diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index 90e32062c..ba4a06f19 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -224,7 +224,7 @@ "last_time_played": "Terakhir dimainkan {{period}}", "activity": "Aktivitas terbaru", "library": "Perpustakaan", - "total_play_time": "Total waktu bermain: {{amount}}", + "total_play_time": "Total waktu bermain", "no_recent_activity_title": "Hmm… kosong di sini", "no_recent_activity_description": "Kamu belum main game baru-baru ini. Yuk, mulai main!", "display_name": "Nama tampilan", diff --git a/src/locales/kk/translation.json b/src/locales/kk/translation.json index a15f6418f..6d5d84044 100644 --- a/src/locales/kk/translation.json +++ b/src/locales/kk/translation.json @@ -220,7 +220,7 @@ "last_time_played": "Соңғы ойын {{period}}", "activity": "Соңғы әрекет", "library": "Кітапхана", - "total_play_time": "Барлығы ойнаған: {{amount}}", + "total_play_time": "Барлығы ойнаған", "no_recent_activity_title": "Хммм... Мұнда ештеңе жоқ", "no_recent_activity_description": "Сіз ұзақ уақыт бойы ештеңе ойнаған жоқсыз. Мұны өзгерту керек!", "display_name": "Көрсету аты", diff --git a/src/locales/nb/translation.json b/src/locales/nb/translation.json index 32c2943b7..5c5f68823 100644 --- a/src/locales/nb/translation.json +++ b/src/locales/nb/translation.json @@ -251,7 +251,7 @@ "last_time_played": "Sist spilt {{period}}", "activity": "Seneste aktivitet", "library": "Bibliotek", - "total_play_time": "Samlet spilltid: {{amount}}", + "total_play_time": "Samlet spilltid", "no_recent_activity_title": "Hmmm… ikke noe her", "no_recent_activity_description": "Du har ikke spilt noen spill for på det seneste. Det er det på tide at endre på!", "display_name": "Brukernavn", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 0ed8a3b45..f716d5175 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -158,7 +158,7 @@ "no_download_option_info": "Sem informações disponíveis", "backup_deletion_failed": "Falha ao apagar backup", "max_number_of_artifacts_reached": "Número máximo de backups atingido para este jogo", - "achievements_not_sync": "Suas conquistas não estão sincronizadas", + "achievements_not_sync": "Veja como exibir suas conquistas no perfil", "backup_from": "Backup de {{date}}", "custom_backup_location_set": "Localização customizada selecionada", "select_folder": "Selecione a pasta", @@ -300,7 +300,7 @@ "last_time_played": "Última sessão {{period}}", "activity": "Atividades recentes", "library": "Biblioteca", - "total_play_time": "Tempo de jogo: {{amount}}", + "total_play_time": "Tempo total de jogo", "no_recent_activity_title": "Hmmm… nada por aqui", "no_recent_activity_description": "Parece que você não jogou nada recentemente. Que tal começar agora?", "display_name": "Nome de exibição", @@ -367,7 +367,8 @@ "stats": "Estatísticas", "achievements": "Conquistas", "games": "Jogos", - "ranking_updated_weekly": "Ranking é atualizado semanalmente" + "ranking_updated_weekly": "Ranking é atualizado semanalmente", + "playing": "Jogando {{game}}" }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", @@ -379,9 +380,9 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} conquistas", "achievements_unlocked_for_game": "Desbloqueadas {{achievementCount}} novas conquistas em {{gameTitle}}", "hidden_achievement_tooltip": "Está é uma conquista oculta", - "achievement_earn_points": "Ganhe {{points}} com essa conquista" + "achievement_earn_points": "Ganhe {{points}} pontos com essa conquista" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Assinatura Hydra Cloud", "subscribe_now": "Inscreva-se agora", "cloud_achievements": "Salvamento de conquistas em nuvem", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index a847ad707..ce081b3f6 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -287,7 +287,7 @@ "last_time_played": "Última sessão {{period}}", "activity": "Atividade recente", "library": "Biblioteca", - "total_play_time": "Tempo total de jogo: {{amount}}", + "total_play_time": "Tempo total de jogo", "no_recent_activity_title": "Hmmm… não há nada por aqui", "no_recent_activity_description": "Parece que não jogaste nada recentemente. Que tal começar agora?", "display_name": "Nome de apresentação", @@ -360,7 +360,7 @@ "subscription_needed": "Precisas de uma subscrição Hydra Cloud para visualizar este conteúdo", "new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Subscrição Hydra Cloud", "subscribe_now": "Subscreve agora", "cloud_achievements": "Gravação de conquistas na nuvem", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 7bb103740..2ea69d140 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -294,7 +294,7 @@ "last_time_played": "Последняя игра {{period}}", "activity": "Недавняя активность", "library": "Библиотека", - "total_play_time": "Всего сыграно: {{amount}}", + "total_play_time": "Всего сыграно", "no_recent_activity_title": "Хммм... Тут ничего нет", "no_recent_activity_description": "Вы давно ни во что не играли. Пора это изменить!", "display_name": "Отображаемое имя", @@ -365,7 +365,7 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} достижений", "achievements_unlocked_for_game": "Разблокировано {{achievementCount}} новых достижений для {{gameTitle}}" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Подписка Hydra Cloud", "subscribe_now": "Подпишитесь прямо сейчас", "cloud_saving": "Сохранение в облаке", diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index 8550d4256..ed4b3d588 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -231,7 +231,7 @@ "sign_out_modal_text": "Ваша бібліотека пов'язана з поточним обліковим записом. При виході з системи ваша бібліотека буде недоступною, і прогрес не буде збережено. Продовжити вихід?", "sign_out_modal_title": "Ви впевнені?", "successfully_signed_out": "Успішний вихід з акаунту", - "total_play_time": "Всього зіграно: {{amount}}", + "total_play_time": "Всього зіграно", "try_again": "Будь ласка, попробуйте ще раз" } } diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index b44f37a9c..664877fa8 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -290,7 +290,7 @@ "last_time_played": "上次游玩时间 {{period}}", "activity": "近期活动", "library": "库", - "total_play_time": "总游戏时长: {{amount}}", + "total_play_time": "总游戏时长", "no_recent_activity_title": "Emmm… 这里暂时啥都没有", "no_recent_activity_description": "你最近没玩过任何游戏。是时候做出改变了!", "display_name": "昵称", @@ -363,7 +363,7 @@ "subscription_needed": "需要订阅 Hydra Cloud 才能看到此内容", "new_achievements_unlocked": "从 {{gameCount}} 游戏中解锁 {{achievementCount}} 新成就" }, - "tour": { + "hydra_cloud": { "subscription_tour_title": "Hydra 云订阅", "subscribe_now": "现在订购", "cloud_saving": "云存档", diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 6bd0acefe..cad14f55a 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -29,6 +29,8 @@ import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; import { downloadSourcesWorker } from "./workers"; import { repacksContext } from "./context"; import { logger } from "./logger"; +import { useSubscription } from "./hooks/use-subscription"; +import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-modal"; export interface AppProps { children: React.ReactNode; @@ -47,21 +49,21 @@ export function App() { const { indexRepacks } = useContext(repacksContext); const { + userDetails, + hasActiveSubscription, isFriendsModalVisible, friendRequetsModalTab, friendModalUserId, syncFriendRequests, hideFriendsModal, - } = useUserDetails(); - - const { - userDetails, - hasActiveSubscription, fetchUserDetails, updateUserDetails, clearUserDetails, } = useUserDetails(); + const { hideHydraCloudModal, showHydraCloudModal, isHydraCloudModalVisible } = + useSubscription(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); @@ -308,6 +310,11 @@ export function App() { onClose={handleToastClose} /> + + {userDetails && ( ({ setShowGameOptionsModal: () => {}, setShowRepacksModal: () => {}, setHasNSFWContentBlocked: () => {}, - handleClickOpenCheckout: () => {}, }); const { Provider } = gameDetailsContext; @@ -111,11 +110,6 @@ export function GameDetailsContextProvider({ (state) => state.userPreferences.value ); - const handleClickOpenCheckout = () => { - // TODO: show modal before redirecting to checkout page - window.electron.openCheckout(); - }; - const updateGame = useCallback(async () => { return window.electron .getGameByObjectId(objectId!) @@ -290,7 +284,6 @@ export function GameDetailsContextProvider({ updateGame, setShowRepacksModal, setShowGameOptionsModal, - handleClickOpenCheckout, }} > {children} diff --git a/src/renderer/src/context/game-details/game-details.context.types.ts b/src/renderer/src/context/game-details/game-details.context.types.ts index 842065cc3..49718430f 100644 --- a/src/renderer/src/context/game-details/game-details.context.types.ts +++ b/src/renderer/src/context/game-details/game-details.context.types.ts @@ -29,5 +29,4 @@ export interface GameDetailsContext { setShowRepacksModal: React.Dispatch>; setShowGameOptionsModal: React.Dispatch>; setHasNSFWContentBlocked: React.Dispatch>; - handleClickOpenCheckout: () => void; } diff --git a/src/renderer/src/features/index.ts b/src/renderer/src/features/index.ts index fdc23e682..5fd14a2a7 100644 --- a/src/renderer/src/features/index.ts +++ b/src/renderer/src/features/index.ts @@ -6,3 +6,4 @@ export * from "./window-slice"; export * from "./toast-slice"; export * from "./user-details-slice"; export * from "./running-game-slice"; +export * from "./subscription-slice"; diff --git a/src/renderer/src/features/subscription-slice.ts b/src/renderer/src/features/subscription-slice.ts new file mode 100644 index 000000000..601086808 --- /dev/null +++ b/src/renderer/src/features/subscription-slice.ts @@ -0,0 +1,25 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export interface SubscriptionState { + isHydraCloudModalVisible: boolean; +} + +const initialState: SubscriptionState = { + isHydraCloudModalVisible: false, +}; + +export const subscriptionSlice = createSlice({ + name: "subscription", + initialState, + reducers: { + setHydraCloudModalVisible: (state) => { + state.isHydraCloudModalVisible = true; + }, + setHydraCloudModalHidden: (state) => { + state.isHydraCloudModalVisible = false; + }, + }, +}); + +export const { setHydraCloudModalVisible, setHydraCloudModalHidden } = + subscriptionSlice.actions; diff --git a/src/renderer/src/hooks/use-subscription.ts b/src/renderer/src/hooks/use-subscription.ts new file mode 100644 index 000000000..930fa552b --- /dev/null +++ b/src/renderer/src/hooks/use-subscription.ts @@ -0,0 +1,28 @@ +import { useCallback } from "react"; +import { useAppDispatch, useAppSelector } from "./redux"; +import { + setHydraCloudModalVisible, + setHydraCloudModalHidden, +} from "@renderer/features"; + +export function useSubscription() { + const dispatch = useAppDispatch(); + + const { isHydraCloudModalVisible } = useAppSelector( + (state) => state.subscription + ); + + const showHydraCloudModal = useCallback(() => { + dispatch(setHydraCloudModalVisible()); + }, [dispatch]); + + const hideHydraCloudModal = useCallback(() => { + dispatch(setHydraCloudModalHidden()); + }, [dispatch]); + + return { + isHydraCloudModalVisible, + showHydraCloudModal, + hideHydraCloudModal, + }; +} diff --git a/src/renderer/src/pages/achievements/achievement-list.tsx b/src/renderer/src/pages/achievements/achievement-list.tsx index 0bd09a692..12c1b957a 100644 --- a/src/renderer/src/pages/achievements/achievement-list.tsx +++ b/src/renderer/src/pages/achievements/achievement-list.tsx @@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next"; import * as styles from "./achievements.css"; import { EyeClosedIcon } from "@primer/octicons-react"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; +import { useSubscription } from "@renderer/hooks/use-subscription"; +import { vars } from "@renderer/theme.css"; interface AchievementListProps { achievements: UserAchievement[]; @@ -11,6 +13,7 @@ interface AchievementListProps { export function AchievementList({ achievements }: AchievementListProps) { const { t } = useTranslation("achievement"); + const { showHydraCloudModal } = useSubscription(); const { formatDateTime } = useDate(); return ( @@ -41,7 +44,7 @@ export function AchievementList({ achievements }: AchievementListProps) {

    {achievement.description}

    - {achievement.points && ( + {achievement.points != undefined ? (

    {achievement.points}

    + ) : ( + )} {achievement.unlockTime && (
    @@ -92,7 +93,7 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {

    diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 70ce165fa..f32f8161e 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -14,6 +14,7 @@ import { steamUrlBuilder } from "@shared"; import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif"; import { useUserDetails } from "@renderer/hooks"; +import { useSubscription } from "@renderer/hooks/use-subscription"; const HERO_ANIMATION_THRESHOLD = 25; @@ -31,9 +32,10 @@ export function GameDetailsContent() { gameColor, setGameColor, hasNSFWContentBlocked, - handleClickOpenCheckout, } = useContext(gameDetailsContext); + const { showHydraCloudModal } = useSubscription(); + const { userDetails, hasActiveSubscription } = useUserDetails(); const { setShowCloudSyncModal, getGameArtifacts } = @@ -104,7 +106,7 @@ export function GameDetailsContent() { } if (!hasActiveSubscription) { - handleClickOpenCheckout(); + showHydraCloudModal(); return; } diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 86f24f5aa..95673804a 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -21,6 +21,8 @@ import { howLongToBeatEntriesTable } from "@renderer/dexie"; import { SidebarSection } from "../sidebar-section/sidebar-section"; import { buildGameAchievementPath } from "@renderer/helpers"; import { SPACING_UNIT } from "@renderer/theme.css"; +import { useSubmit } from "react-router-dom"; +import { useSubscription } from "@renderer/hooks/use-subscription"; const fakeAchievements: UserAchievement[] = [ { @@ -67,15 +69,10 @@ export function Sidebar() { const [activeRequirement, setActiveRequirement] = useState("minimum"); - const { - gameTitle, - shopDetails, - objectId, - shop, - stats, - achievements, - handleClickOpenCheckout, - } = useContext(gameDetailsContext); + const { gameTitle, shopDetails, objectId, shop, stats, achievements } = + useContext(gameDetailsContext); + + const { showHydraCloudModal } = useSubscription(); const { t } = useTranslation("game_details"); const { formatDateTime } = useDate(); @@ -179,7 +176,7 @@ export function Sidebar() { {!hasActiveSubscription && ( + )} +
  • -

    {t("games")}

    +

    {t("total_play_time")}

    -

    - {t("total_play_time", { - amount: formatPlayTime( - userStats.totalPlayTimeInSeconds.value - ), - })} -

    +

    {formatPlayTime(userStats.totalPlayTimeInSeconds.value)}

    {t("top_percentile", { percentile: userStats.totalPlayTimeInSeconds.topPercentile, diff --git a/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.css.ts b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.css.ts new file mode 100644 index 000000000..a164c9007 --- /dev/null +++ b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.css.ts @@ -0,0 +1,79 @@ +import { SPACING_UNIT, vars } from "../../../theme.css"; +import { style } from "@vanilla-extract/css"; + +export const friendListDisplayName = style({ + fontWeight: "bold", + fontSize: vars.size.body, + textAlign: "left", + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", +}); + +export const friendListContainer = style({ + display: "flex", + gap: `${SPACING_UNIT * 3}px`, + alignItems: "center", + borderRadius: "4px", + border: `solid 1px ${vars.color.border}`, + width: "100%", + height: "54px", + minHeight: "54px", + transition: "all ease 0.2s", + position: "relative", + ":hover": { + backgroundColor: "rgba(255, 255, 255, 0.15)", + }, +}); + +export const friendListButton = style({ + display: "flex", + alignItems: "center", + position: "absolute", + cursor: "pointer", + height: "100%", + width: "100%", + flexDirection: "row", + color: vars.color.body, + gap: `${SPACING_UNIT + SPACING_UNIT / 2}px`, + padding: `0 ${SPACING_UNIT}px`, +}); + +export const friendRequestItem = style({ + color: vars.color.body, + ":hover": { + backgroundColor: "rgba(255, 255, 255, 0.15)", + }, +}); + +export const acceptRequestButton = style({ + cursor: "pointer", + color: vars.color.body, + width: "28px", + height: "28px", + ":hover": { + color: vars.color.success, + }, +}); + +export const cancelRequestButton = style({ + cursor: "pointer", + color: vars.color.body, + width: "28px", + height: "28px", + ":hover": { + color: vars.color.danger, + }, +}); + +export const friendCodeButton = style({ + color: vars.color.body, + cursor: "pointer", + display: "flex", + gap: `${SPACING_UNIT / 2}px`, + alignItems: "center", + transition: "all ease 0.2s", + ":hover": { + color: vars.color.muted, + }, +}); diff --git a/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx new file mode 100644 index 000000000..1f90669f7 --- /dev/null +++ b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx @@ -0,0 +1,36 @@ +import { Button, Modal } from "@renderer/components"; +import { SPACING_UNIT } from "@renderer/theme.css"; +import { useTranslation } from "react-i18next"; + +export interface HydraCloudModalProps { + visible: boolean; + onClose: () => void; +} + +export const HydraCloudModal = ({ visible, onClose }: HydraCloudModalProps) => { + const { t } = useTranslation("hydra_cloud"); + + const handleClickOpenCheckout = () => { + window.electron.openCheckout(); + }; + + return ( + +

    + Você descobriu uma funcionalidade Hydra Cloud! + +
    + + ); +}; diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts index 0f2bee9f8..7be495cb3 100644 --- a/src/renderer/src/store.ts +++ b/src/renderer/src/store.ts @@ -8,6 +8,7 @@ import { toastSlice, userDetailsSlice, gameRunningSlice, + subscriptionSlice, } from "@renderer/features"; export const store = configureStore({ @@ -20,6 +21,7 @@ export const store = configureStore({ toast: toastSlice.reducer, userDetails: userDetailsSlice.reducer, gameRunning: gameRunningSlice.reducer, + subscription: subscriptionSlice.reducer, }, }); From c8566dd2beca96d34a638cf6d10313eb6c0bf485 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 22 Dec 2024 18:56:24 -0300 Subject: [PATCH 27/62] feat: ui improvement --- src/locales/en/translation.json | 14 +-- src/locales/pt-BR/translation.json | 16 ++-- .../get-compared-unlocked-achievements.ts | 41 +++++++-- .../pages/achievements/achievement-panel.tsx | 23 +++-- .../achievements/achievements-content.tsx | 2 +- .../compared-achievement-list.tsx | 20 ++++- .../compared-achievement-panel.tsx | 2 +- .../profile/profile-content/friends-box.tsx | 4 +- .../profile-content/profile-content.css.ts | 16 ++++ .../profile-content/profile-content.tsx | 19 +++- .../profile-content/user-stats-box.tsx | 89 ++++++++++++------- src/types/index.ts | 1 + 12 files changed, 182 insertions(+), 65 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 1ba8b6e26..b5576cd13 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -168,8 +168,7 @@ "select_folder": "Select folder", "backup_from": "Backup from {{date}}", "custom_backup_location_set": "Custom backup location set", - "no_directory_selected": "No directory selected", - "available_points": "Available points:" + "no_directory_selected": "No directory selected" }, "activation": { "title": "Activate Hydra", @@ -367,11 +366,14 @@ "uploading_banner": "Uploading banner…", "background_image_updated": "Background image updated", "stats": "Stats", - "achievements": "Achievements", + "achievements": "achievements", "games": "Games", "top_percentile": "Top {{percentile}}%", "ranking_updated_weekly": "Ranking is updated weekly", - "playing": "Playing {{game}}" + "playing": "Playing {{game}}", + "achievements_unlocked": "Achievements Unlocked", + "earned_points": "Earned points", + "show_achievements_on_profile": "Show your achievements and earned points on your profile" }, "achievement": { "achievement_unlocked": "Achievement unlocked", @@ -383,7 +385,9 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievements", "achievements_unlocked_for_game": "Unlocked {{achievementCount}} new achievements for {{gameTitle}}", "hidden_achievement_tooltip": "This is a hidden achievement", - "achievement_earn_points": "Earn {{points}} points with this achievement" + "achievement_earn_points": "Earn {{points}} points with this achievement", + "earned_points": "Earned points:", + "available_points": "Available points:" }, "hydra_cloud": { "subscription_tour_title": "Hydra Cloud Subscription", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index f716d5175..7e783d5f8 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -164,8 +164,7 @@ "select_folder": "Selecione a pasta", "manage_files_description": "Gerencie quais arquivos serão feitos backup", "clear": "Limpar", - "no_directory_selected": "Nenhum diretório selecionado", - "available_points": "Pontos disponíveis:" + "no_directory_selected": "Nenhum diretório selecionado" }, "activation": { "title": "Ativação", @@ -365,10 +364,13 @@ "uploading_banner": "Carregando banner…", "background_image_updated": "Imagem de fundo salva", "stats": "Estatísticas", - "achievements": "Conquistas", + "achievements": "conquistas", "games": "Jogos", - "ranking_updated_weekly": "Ranking é atualizado semanalmente", - "playing": "Jogando {{game}}" + "ranking_updated_weekly": "O ranking é atualizado semanalmente", + "playing": "Jogando {{game}}", + "achievements_unlocked": "Conquistas desbloqueadas", + "earned_points": "Pontos ganhos", + "show_achievements_on_profile": "Exiba suas conquistas e pontos no perfil" }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", @@ -380,7 +382,9 @@ "achievement_progress": "{{unlockedCount}}/{{totalCount}} conquistas", "achievements_unlocked_for_game": "Desbloqueadas {{achievementCount}} novas conquistas em {{gameTitle}}", "hidden_achievement_tooltip": "Está é uma conquista oculta", - "achievement_earn_points": "Ganhe {{points}} pontos com essa conquista" + "achievement_earn_points": "Ganhe {{points}} pontos com essa conquista", + "earned_points": "Pontos ganhos:", + "available_points": "Pontos disponíveis:" }, "hydra_cloud": { "subscription_tour_title": "Assinatura Hydra Cloud", diff --git a/src/main/events/user/get-compared-unlocked-achievements.ts b/src/main/events/user/get-compared-unlocked-achievements.ts index 0c1171400..0b6652121 100644 --- a/src/main/events/user/get-compared-unlocked-achievements.ts +++ b/src/main/events/user/get-compared-unlocked-achievements.ts @@ -13,6 +13,9 @@ const getComparedUnlockedAchievements = async ( where: { id: 1 }, }); + const showHiddenAchievementsDescription = + userPreferences?.showHiddenAchievementsDescription || false; + return HydraApi.get( `/users/${userId}/games/achievements/compare`, { @@ -21,15 +24,35 @@ const getComparedUnlockedAchievements = async ( language: userPreferences?.language || "en", } ).then((achievements) => { - const sortedAchievements = achievements.achievements.sort((a, b) => { - if (a.targetStat.unlocked && !b.targetStat.unlocked) return -1; - if (!a.targetStat.unlocked && b.targetStat.unlocked) return 1; - if (a.targetStat.unlocked && b.targetStat.unlocked) { - return b.targetStat.unlockTime! - a.targetStat.unlockTime!; - } - - return Number(a.hidden) - Number(b.hidden); - }); + const sortedAchievements = achievements.achievements + .sort((a, b) => { + if (a.targetStat.unlocked && !b.targetStat.unlocked) return -1; + if (!a.targetStat.unlocked && b.targetStat.unlocked) return 1; + if (a.targetStat.unlocked && b.targetStat.unlocked) { + return b.targetStat.unlockTime! - a.targetStat.unlockTime!; + } + + return Number(a.hidden) - Number(b.hidden); + }) + .map((achievement) => { + if (!achievement.hidden) return achievement; + + if (!achievement.ownerStat) { + return { + ...achievement, + description: "", + }; + } + + if (!showHiddenAchievementsDescription && achievement.hidden) { + return { + ...achievement, + description: "", + }; + } + + return achievement; + }); return { ...achievements, diff --git a/src/renderer/src/pages/achievements/achievement-panel.tsx b/src/renderer/src/pages/achievements/achievement-panel.tsx index bf48e5172..b1233bd0f 100644 --- a/src/renderer/src/pages/achievements/achievement-panel.tsx +++ b/src/renderer/src/pages/achievements/achievement-panel.tsx @@ -4,13 +4,25 @@ import { gameDetailsContext } from "@renderer/context"; import * as styles from "./achievement-panel.css"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; +import { UserAchievement } from "@types"; -export interface HeroPanelProps { - isHeaderStuck: boolean; +export interface AchievementPanelProps { + achievements: UserAchievement[]; } -export function AchievementPanel({ isHeaderStuck }: HeroPanelProps) { - const { t } = useTranslation("game_details"); +export function AchievementPanel({ achievements }: AchievementPanelProps) { + const { t } = useTranslation("achievement"); + + const achievementsPointsTotal = achievements.reduce( + (acc, achievement) => acc + (achievement.points ?? 0), + 0 + ); + + const achievementsPointsEarnedSum = achievements.reduce( + (acc, achievement) => + acc + (achievement.unlocked ? (achievement.points ?? 0) : 0), + 0 + ); const {} = useContext(gameDetailsContext); @@ -18,7 +30,8 @@ export function AchievementPanel({ isHeaderStuck }: HeroPanelProps) { <>
    - Pontos desbloqueados: 69/420 + {t("earned_points")} + {achievementsPointsEarnedSum} / {achievementsPointsTotal}
    diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx index 59139768f..87865280a 100644 --- a/src/renderer/src/pages/achievements/achievements-content.tsx +++ b/src/renderer/src/pages/achievements/achievements-content.tsx @@ -329,7 +329,7 @@ export function AchievementsContent({ ) : ( <> - + )} diff --git a/src/renderer/src/pages/achievements/compared-achievement-list.tsx b/src/renderer/src/pages/achievements/compared-achievement-list.tsx index 349ef755c..44aec686a 100644 --- a/src/renderer/src/pages/achievements/compared-achievement-list.tsx +++ b/src/renderer/src/pages/achievements/compared-achievement-list.tsx @@ -1,8 +1,13 @@ import type { ComparedAchievements } from "@types"; import * as styles from "./achievements.css"; -import { CheckCircleIcon, LockIcon } from "@primer/octicons-react"; +import { + CheckCircleIcon, + EyeClosedIcon, + LockIcon, +} from "@primer/octicons-react"; import { useDate } from "@renderer/hooks"; import { SPACING_UNIT } from "@renderer/theme.css"; +import { useTranslation } from "react-i18next"; export interface ComparedAchievementListProps { achievements: ComparedAchievements; @@ -11,6 +16,7 @@ export interface ComparedAchievementListProps { export function ComparedAchievementList({ achievements, }: ComparedAchievementListProps) { + const { t } = useTranslation("achievement"); const { formatDateTime } = useDate(); return ( @@ -43,7 +49,17 @@ export function ComparedAchievementList({ loading="lazy" />
    -

    {achievement.displayName}

    +

    + {achievement.hidden && ( + + + + )} + {achievement.displayName} +

    {achievement.description}

    diff --git a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx index 0f35aa5dc..3ac76d8da 100644 --- a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx +++ b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx @@ -14,7 +14,7 @@ export interface ComparedAchievementPanelProps { export function ComparedAchievementPanel({ achievements, }: ComparedAchievementPanelProps) { - const { t } = useTranslation("game_details"); + const { t } = useTranslation("achievement"); const {} = useContext(gameDetailsContext); diff --git a/src/renderer/src/pages/profile/profile-content/friends-box.tsx b/src/renderer/src/pages/profile/profile-content/friends-box.tsx index 4d3df7e89..454d13f0a 100644 --- a/src/renderer/src/pages/profile/profile-content/friends-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/friends-box.tsx @@ -41,9 +41,7 @@ export function FriendsBox() { {friend.displayName} {friend.currentGame && ( - -

    {t("playing", { game: friend.currentGame.title })}

    - +

    {t("playing", { game: friend.currentGame.title })}

    )}
  • diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.css.ts b/src/renderer/src/pages/profile/profile-content/profile-content.css.ts index b078e53d1..cc462a95e 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.css.ts +++ b/src/renderer/src/pages/profile/profile-content/profile-content.css.ts @@ -105,6 +105,22 @@ export const listItem = style({ }, }); +export const statsListItem = style({ + display: "flex", + flexDirection: "column", + transition: "all ease 0.1s", + color: vars.color.muted, + width: "100%", + overflow: "hidden", + borderRadius: "4px", + padding: `${SPACING_UNIT}px ${SPACING_UNIT}px`, + gap: `${SPACING_UNIT}px`, + ":hover": { + backgroundColor: "rgba(255, 255, 255, 0.15)", + textDecoration: "none", + }, +}); + export const gamesGrid = style({ listStyle: "none", margin: "0", diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index f4baccd98..2217d5690 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -22,6 +22,7 @@ import { } from "@renderer/helpers"; import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; import { UserStatsBox } from "./user-stats-box"; +import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; export function ProfileContent() { const { userProfile, isMe, userStats } = useContext(userProfileContext); @@ -157,7 +158,7 @@ export function ProfileContent() { height: "100%", width: "100%", background: - "linear-gradient(0deg, rgba(0, 0, 0, 0.7) 20%, transparent 100%)", + "linear-gradient(0deg, rgba(0, 0, 0, 0.75) 25%, transparent 100%)", padding: 8, }} > @@ -187,6 +188,22 @@ export function ProfileContent() { flexDirection: "column", }} > + {game.achievementsPointsEarnedSum > 0 && ( +
    + + {numberFormatter.format( + game.achievementsPointsEarnedSum + )} +
    + )}
      -
    • -

      {t("achievements")}

      - {userStats.achievementsPointsEarnedSum !== undefined ? ( - <> + {(isMe || userStats.unlockedAchievementSum !== undefined) && ( +
    • +

      + {t("achievements_unlocked")} +

      + {userStats.unlockedAchievementSum !== undefined ? (
      -

      +

      + {userStats.unlockedAchievementSum}{" "} + {t("achievements")} +

      +
      + ) : ( + + )} +
    • + )} + + {(isMe || userStats.achievementsPointsEarnedSum !== undefined) && ( +
    • +

      {t("earned_points")}

      + {userStats.achievementsPointsEarnedSum !== undefined ? ( +
      +

      - {userStats.achievementsPointsEarnedSum.value} + {numberFormatter.format( + userStats.achievementsPointsEarnedSum.value + )}

      {t("top_percentile", { @@ -67,25 +90,27 @@ export function UserStatsBox() { })}

      -

      Unlock count: {userStats.unlockedAchievementSum}

      - - ) : ( - - )} -
    • + ) : ( + + )} + + )} -
    • +
    • {t("total_play_time")}

      -

      {formatPlayTime(userStats.totalPlayTimeInSeconds.value)}

      +

      + + {formatPlayTime(userStats.totalPlayTimeInSeconds.value)} +

      {t("top_percentile", { percentile: userStats.totalPlayTimeInSeconds.topPercentile, diff --git a/src/types/index.ts b/src/types/index.ts index 7cca371c6..8bdace2fa 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -99,6 +99,7 @@ export interface UserGame { lastTimePlayed: Date | null; unlockedAchievementCount: number; achievementCount: number; + achievementsPointsEarnedSum: number; } export interface DownloadQueue { From 32032e1c8a7827411c3d08d124313eef0e15a828 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 22 Dec 2024 23:38:54 +0000 Subject: [PATCH 28/62] fix: fixing download sources migration --- src/locales/pt-BR/translation.json | 7 +- .../src/components/header/header.css.ts | 15 +- src/renderer/src/components/header/header.tsx | 6 +- .../src/pages/catalogue/catalogue.scss | 16 +- .../src/pages/catalogue/catalogue.tsx | 241 ++++++---- .../src/pages/catalogue/steam-user-tags.ts | 452 ------------------ .../src/workers/download-sources.worker.ts | 69 +-- 7 files changed, 205 insertions(+), 601 deletions(-) delete mode 100644 src/renderer/src/pages/catalogue/steam-user-tags.ts diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index a1197c78f..fa11ef273 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -279,8 +279,11 @@ "instructions": "Verifique a forma correta de instalar algum deles no seu distro Linux, garantindo assim a execução normal do jogo" }, "catalogue": { - "next_page": "Próxima página", - "previous_page": "Página anterior" + "search": "Pesquisar…", + "developers": "Desenvolvedores", + "genres": "Gêneros", + "tags": "Tags", + "download_sources": "Fontes de download" }, "modal": { "close": "Botão de fechar" diff --git a/src/renderer/src/components/header/header.css.ts b/src/renderer/src/components/header/header.css.ts index a592f35b8..903f4a42d 100644 --- a/src/renderer/src/components/header/header.css.ts +++ b/src/renderer/src/components/header/header.css.ts @@ -74,9 +74,18 @@ export const search = recipe({ }, }); -export const searchButton = style({ - WebkitAppRegion: "no-drag", -} as ComplexStyleRule); +export const searchButton = recipe({ + base: { + WebkitAppRegion: "no-drag", + } as ComplexStyleRule, + variants: { + hidden: { + true: { + visibility: "hidden", + }, + }, + }, +}); export const section = style({ display: "flex", diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index eb6a9f3b2..2c79b1d4c 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -35,6 +35,10 @@ export function Header() { return t(pathTitle[location.pathname]); }, [location.pathname, headerTitle, t]); + const showSearchButton = useMemo(() => { + return location.pathname.startsWith("/catalogue"); + }, [location.pathname]); + const handleBackButtonClick = () => { navigate(-1); }; @@ -71,7 +75,7 @@ export function Header() {

      +
    {/* - ))} + + )) + )} -
    + {/*
    -
    +
    */}
    - -
    dispatch(setSearch({ genres: [] }))} color={filterCategoryColors.genres} onSelect={(value) => { @@ -358,7 +399,7 @@ export default function Catalogue() { /> dispatch(setSearch({ tags: [] }))} onSelect={(value) => { @@ -382,7 +423,7 @@ export default function Catalogue() { /> dispatch(setSearch({ downloadSourceFingerprints: [] })) @@ -418,7 +459,7 @@ export default function Catalogue() { /> dispatch(setSearch({ developers: [] }))} onSelect={(value) => { @@ -444,7 +485,7 @@ export default function Catalogue() { /> dispatch(setSearch({ publishers: [] }))} onSelect={(value) => { diff --git a/src/renderer/src/pages/catalogue/steam-user-tags.ts b/src/renderer/src/pages/catalogue/steam-user-tags.ts deleted file mode 100644 index 76721d4fa..000000000 --- a/src/renderer/src/pages/catalogue/steam-user-tags.ts +++ /dev/null @@ -1,452 +0,0 @@ -export const steamUserTags = { - Atmospheric: 4166, - Fantasy: 1684, - Relaxing: 1654, - Funny: 4136, - "Sci-fi": 3942, - Horror: 1667, - "Family Friendly": 5350, - Retro: 4004, - Survival: 1662, - Mystery: 5716, - Dark: 4342, - Comedy: 1719, - "Psychological Horror": 1721, - Sports: 701, - "Old School": 3916, - Medieval: 4172, - Magic: 4057, - Racing: 699, - Building: 1643, - Management: 12472, - Space: 1755, - Tactical: 1708, - Drama: 5984, - Futuristic: 4295, - Logic: 6129, - Romance: 4947, - Crafting: 1702, - "Dark Fantasy": 4604, - Emotional: 5608, - "Survival Horror": 3978, - "1980s": 7743, - Nature: 30358, - Education: 1036, - "1990's": 6691, - Surreal: 1710, - "Post-apocalyptic": 3835, - War: 1678, - Zombies: 1659, - Historical: 3987, - Stealth: 1687, - Investigation: 8369, - Military: 4168, - "LGBTQ+": 44868, - Cyberpunk: 4115, - "Lore-Rich": 3854, - Detective: 5613, - Aliens: 1673, - Thriller: 4064, - Economy: 4695, - Robots: 5752, - Demons: 9541, - "Dark Humor": 5923, - Psychological: 5186, - Driving: 1644, - Supernatural: 10808, - "Comic Book": 1751, - Modern: 5673, - Psychedelic: 1714, - Dystopian: 5030, - Flight: 15045, - "Artificial Intelligence": 7926, - Loot: 4236, - Memes: 10397, - "Alternate History": 4598, - Cats: 17894, - Parkour: 4036, - Mythology: 16094, - Crime: 6378, - "Game Development": 13906, - Destruction: 5363, - Philosophical: 15277, - Capitalism: 4845, - "Dark Comedy": 19995, - Automation: 255534, - Lovecraftian: 7432, - Noir: 6052, - Swordplay: 4608, - Science: 5794, - Cooking: 3920, - America: 13190, - Dragons: 4046, - "World War II": 4150, - Parody: 4878, - Agriculture: 22602, - Conspiracy: 5372, - Gothic: 3952, - "Martial Arts": 6915, - Mechs: 4821, - Underground: 21006, - Satire: 1651, - Pirates: 1681, - Steampunk: 1777, - Dog: 1638, - "Time Travel": 10679, - Mining: 5981, - Transportation: 10383, - Ninja: 1688, - Vampire: 4018, - Tanks: 13276, - Political: 4853, - Otome: 31579, - Underwater: 9157, - Hunting: 9564, - Fishing: 15564, - Trains: 1616, - Dinosaurs: 5160, - Western: 1647, - Hacking: 5502, - Faith: 180368, - Programming: 5432, - Superhero: 1671, - Politics: 4754, - Assassin: 4376, - Gambling: 16250, - Naval: 6910, - Diplomacy: 6310, - Heist: 1680, - Snow: 9803, - Archery: 13382, - "Cold War": 5179, - Sailing: 13577, - "Football (Soccer)": 1254546, - Foreign: 51306, - Offroad: 7622, - Horses: 6041, - Illuminati: 7478, - Sniper: 7423, - Transhumanism: 4137, - Werewolves: 17015, - Mars: 6702, - Boxing: 12190, - Jet: 92092, - Motorbike: 198913, - Golf: 7038, - Bikes: 123332, - "World War I": 5382, - Rome: 6948, - Submarine: 19780, - Basketball: 1746, - Baseball: 5727, - LEGO: 1736, - Skateboarding: 1753, - "Mini Golf": 22955, - Wrestling: 47827, - "Football (American)": 1254552, - Tennis: 5914, - Pool: 17927, - Skating: 96359, - Cycling: 19568, - Motocross: 15868, - "Warhammer 40K": 12286, - Lemmings: 17337, - Hockey: 324176, - Bowling: 7328, - Snowboarding: 28444, - Skiing: 7309, - BMX: 252854, - ATV: 129761, - Indie: 492, - Action: 19, - Casual: 597, - Adventure: 21, - Simulation: 599, - RPG: 122, - Strategy: 9, - "Action-Adventure": 4106, - Software: 8013, - "2D": 3871, - "3D": 4191, - Colorful: 4305, - "Pixel Graphics": 3964, - Cute: 4726, - "First-Person": 3839, - Anime: 4085, - "Third Person": 1697, - Stylized: 4252, - "Top-Down": 4791, - Realistic: 4175, - Cartoony: 4195, - Minimalist: 4094, - "Hand-drawn": 6815, - VR: 21978, - Cartoon: 4562, - "Text-Based": 31275, - Cinematic: 4145, - "2.5D": 4975, - Isometric: 5851, - Abstract: 4400, - "Split Screen": 10816, - "3D Vision": 29363, - Voxel: 1732, - Beautiful: 5411, - FMV: 18594, - "360 Video": 776177, - "Story Rich": 1742, - Combat: 3993, - Controller: 7481, - "Female Protagonist": 7208, - "Choices Matter": 6426, - "Open World": 1695, - PvP: 1775, - PvE: 6730, - Linear: 7250, - "Multiple Endings": 6971, - Physics: 3968, - "Character Customization": 4747, - "Procedural Generation": 5125, - Tabletop: 17389, - "Turn-Based Combat": 4325, - "Turn-Based Tactics": 14139, - "Hack and Slash": 1646, - "Resource Management": 8945, - "Base-Building": 7332, - "Score Attack": 5154, - Narration: 5094, - Conversation: 15172, - Nonlinear: 6869, - Tutorial: 12057, - "Perma Death": 1759, - "Team-Based": 5711, - Deckbuilding: 32322, - "Inventory Management": 6276, - "Level Editor": 8122, - "Grid-Based Movement": 7569, - Moddable: 1669, - "Class-Based": 4155, - "Vehicular Combat": 11104, - "Gun Customization": 5765, - "6DOF": 4835, - Trading: 4202, - "Bullet Time": 5796, - "Time Manipulation": 6625, - "Quick-Time Events": 4559, - "Dynamic Narration": 9592, - "Hex Grid": 1717, - "Naval Combat": 4994, - "Music-Based Procedural Generation": 8253, - "Asymmetric VR": 856791, - Puzzle: 1664, - Arcade: 1773, - Shooter: 1774, - Platformer: 1625, - "Visual Novel": 3799, - Sandbox: 3810, - "Rogue-like": 1716, - "Action RPG": 4231, - "Point & Click": 1698, - "Action Roguelike": 42804, - "Interactive Fiction": 11014, - "Turn-Based Strategy": 1741, - "Dating Sim": 9551, - JRPG: 4434, - "Party-Based RPG": 10695, - "Walking Simulator": 5900, - "Design & Illustration": 84, - "Card Game": 1666, - "Life Sim": 10235, - Utilities: 87, - "Strategy RPG": 17305, - "Board Game": 1770, - RTS: 1676, - "Tower Defense": 1645, - "Web Publishing": 1038, - "City Builder": 4328, - "Beat 'em up": 4158, - "Automobile Sim": 1100687, - "2D Fighter": 4736, - Rhythm: 1752, - "3D Fighter": 6506, - "Farming Sim": 87918, - "Animation & Modeling": 872, - "e-sports": 5055, - "Grand Strategy": 4364, - "Space Sim": 16598, - "Colony Sim": 220585, - "Word Game": 24003, - "Battle Royale": 176981, - MMORPG: 1754, - "Auto Battler": 1084988, - "Audio Production": 1027, - "Video Production": 784, - "God Game": 5300, - "4X": 1670, - MOBA: 1718, - "Photo Editing": 809, - Trivia: 10437, - "Immersive Sim": 9204, - "Political Sim": 26921, - "Outbreak Sim": 1100686, - "Medical Sim": 1100688, - Short: 4234, - Movie: 4700, - Episodic: 4242, - Gaming: 150626, - Documentary: 15339, - Exploration: 3834, - "2D Platformer": 5379, - FPS: 1663, - "Rogue-lite": 3959, - "Shoot 'Em Up": 4255, - "3D Platformer": 5395, - "Side Scroller": 3798, - "Choose Your Own Adventure": 4486, - "Puzzle-Platformer": 5537, - "Hidden Object": 1738, - "Bullet Hell": 4885, - "Dungeon Crawler": 1720, - "Top-Down Shooter": 4637, - Clicker: 379975, - "Third-Person Shooter": 3814, - "Precision Platformer": 3877, - "Time Management": 16689, - "Real Time Tactics": 3813, - "Arena Shooter": 5547, - Collectathon: 5652, - "Tactical RPG": 21725, - Idler: 615955, - Wargame: 4684, - Metroidvania: 1628, - Runner: 8666, - "Card Battler": 791774, - "Souls-like": 29482, - CRPG: 4474, - "Creature Collector": 916648, - "Twin Stick Shooter": 4758, - "Match 3": 1665, - "Mystery Dungeon": 198631, - "Hero Shooter": 620519, - "Spectacle fighter": 4777, - "Looter Shooter": 353880, - Solitaire: 13070, - "Combat Racing": 4102, - "Action RTS": 1723, - Sokoban: 1730, - "Trading Card Game": 9271, - Typing: 1674, - "Boomer Shooter": 1023537, - "Traditional Roguelike": 454187, - "On-Rails Shooter": 56690, - Spelling: 71389, - Roguevania: 922563, - Singleplayer: 4182, - Multiplayer: 3859, - "Co-op": 1685, - "Online Co-Op": 3843, - "Massively Multiplayer": 128, - "Local Multiplayer": 7368, - "Local Co-Op": 3841, - "4 Player Local": 4840, - "Co-op Campaign": 4508, - "Asynchronous Multiplayer": 17770, - "Profile Features Limited": 1003823, - "Demo Available": 21491, - "Adult Content": 65443, - Hentai: 9130, - Fighting: 1743, - Classic: 1693, - Cozy: 97376, - "Open World Survival Craft": 1100689, - Wholesome: 552282, - "Roguelike Deckbuilder": 1091588, - Narrative: 7702, - Immersive: 3934, - "Party Game": 7178, - Party: 7108, - "Escape Room": 769306, - Addictive: 4190, - Nostalgia: 14720, - Farming: 4520, - "Cult Classic": 7782, - Spaceships: 4291, - "Electronic Music": 61357, - Pinball: 6621, - "Social Deduction": 745697, - Ambient: 29855, - Dwarf: 7918, - "Job Simulator": 35079, - Epic: 3965, - "Instrumental Music": 189941, - "Jump Scare": 42089, - "Boss Rush": 11095, - "Rock Music": 337964, - "Tile-Matching": 176733, - Vikings: 11634, - "Extraction Shooter": 1199779, - "8-bit Music": 117648, - "Well-Written": 8461, - Mahjong: 33572, - "Shop Keeper": 91114, - Electronic: 143739, - Birds: 6214, - Dice: 7556, - Musou: 323922, - Fox: 30927, - Coding: 42329, - Elf: 102530, - "Hobby Sim": 1220528, - Cricket: 158638, - Rugby: 49213, - Volleyball: 847164, - Snooker: 363767, - Reboot: 5941, - "Based On A Novel": 3796, - "Free to Play": 113, - "Early Access": 493, - Experimental: 13782, - "Software Training": 1445, - Minigames: 8093, - Remake: 5708, - Sequel: 5230, - Experience: 9994, - Kickstarter: 5153, - Crowdfunded: 7113, - Benchmark: 5407, - "Feature Film": 233824, - Difficult: 4026, - Competitive: 3878, - Unforgiving: 1733, - "Turn-Based": 1677, - "Replay Value": 4711, - "Fast-Paced": 1734, - "Real-Time": 4161, - "Real-Time with Pause": 7107, - "Time Attack": 5390, - "Sexual Content": 12095, - Nudity: 6650, - Mature: 5611, - NSFW: 24904, - "Character Action Game": 3955, - "Villain Protagonist": 11333, - "Silent Protagonist": 15954, - Chess: 4184, - Violent: 4667, - Gore: 4345, - Blood: 5228, - Soundtrack: 7948, - "Great Soundtrack": 1756, - Music: 1621, - "Mouse only": 11123, - "Touch-Friendly": 25085, - "Intentionally Awkward Controls": 14906, - "Voice Control": 27758, - Mod: 5348, - RPGMaker: 5577, - GameMaker: 1649, - "Dungeons & Dragons": 14153, - "Games Workshop": 5310, - TrackIR: 8075, - Hardware: 603297, - "Steam Machine": 348922, -}; diff --git a/src/renderer/src/workers/download-sources.worker.ts b/src/renderer/src/workers/download-sources.worker.ts index 40665dc99..185afed5d 100644 --- a/src/renderer/src/workers/download-sources.worker.ts +++ b/src/renderer/src/workers/download-sources.worker.ts @@ -83,6 +83,37 @@ const getSteamGames = async () => { return response.data; }; +const importDownloadSource = async (url: string) => { + const response = await axios.get>(url); + + const steamGames = await getSteamGames(); + + await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { + const now = new Date(); + + const id = await downloadSourcesTable.add({ + url, + name: response.data.name, + etag: response.headers["etag"], + status: DownloadSourceStatus.UpToDate, + downloadCount: response.data.downloads.length, + createdAt: now, + updatedAt: now, + }); + + const downloadSource = await downloadSourcesTable.get(id); + + await addNewDownloads(downloadSource, response.data.downloads, steamGames); + }); +}; + +const deleteDownloadSource = async (id: number) => { + await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { + await repacksTable.where({ downloadSourceId: id }).delete(); + await downloadSourcesTable.where({ id }).delete(); + }); +}; + self.onmessage = async (event: MessageEvent) => { const [type, data] = event.data; @@ -102,10 +133,7 @@ self.onmessage = async (event: MessageEvent) => { } if (type === "DELETE_DOWNLOAD_SOURCE") { - await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { - await repacksTable.where({ downloadSourceId: data }).delete(); - await downloadSourcesTable.where({ id: data }).delete(); - }); + await deleteDownloadSource(data); const channel = new BroadcastChannel(`download_sources:delete:${data}`); @@ -113,32 +141,7 @@ self.onmessage = async (event: MessageEvent) => { } if (type === "IMPORT_DOWNLOAD_SOURCE") { - const response = - await axios.get>(data); - - const steamGames = await getSteamGames(); - - await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { - const now = new Date(); - - const id = await downloadSourcesTable.add({ - url: data, - name: response.data.name, - etag: response.headers["etag"], - status: DownloadSourceStatus.UpToDate, - downloadCount: response.data.downloads.length, - createdAt: now, - updatedAt: now, - }); - - const downloadSource = await downloadSourcesTable.get(id); - - await addNewDownloads( - downloadSource, - response.data.downloads, - steamGames - ); - }); + await importDownloadSource(data); const channel = new BroadcastChannel(`download_sources:import:${data}`); channel.postMessage(true); @@ -153,6 +156,12 @@ self.onmessage = async (event: MessageEvent) => { const existingRepacks = await repacksTable.toArray(); for (const downloadSource of downloadSources) { + if (!downloadSource.fingerprint) { + await deleteDownloadSource(downloadSource.id); + await importDownloadSource(downloadSource.url); + continue; + } + const headers = new AxiosHeaders(); if (downloadSource.etag) { From 496924b2a49c0ce6b61e681504f656bff0b9b1e4 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 22 Dec 2024 23:42:49 +0000 Subject: [PATCH 29/62] fix: fixing download sources migration --- .../src/pages/catalogue/catalogue.tsx | 24 +++++++++++-------- .../src/pages/catalogue/filter-section.tsx | 10 ++++---- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index e5787ea8f..e85e94b5c 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -403,23 +403,27 @@ export default function Catalogue() { color={filterCategoryColors.tags} onClear={() => dispatch(setSearch({ tags: [] }))} onSelect={(value) => { - if (filters.tags.includes(value)) { + if (filters.tags.includes(Number(value))) { dispatch( setSearch({ - tags: filters.tags.filter((tag) => tag !== value), + tags: filters.tags.filter((tag) => tag !== Number(value)), }) ); } else { - dispatch(setSearch({ tags: [...filters.tags, value] })); + dispatch( + setSearch({ tags: [...filters.tags, Number(value)] }) + ); } }} - items={Object.entries(steamUserTags) - .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) - .map(([key, value]) => ({ - label: key, - value: value, - checked: filters.tags.includes(value), - }))} + items={ + Object.entries(steamUserTags) + .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) + .map(([key, value]) => ({ + label: key, + value: value, + checked: filters.tags.includes(value as number), + })) as any + } /> { +export interface FilterSectionProps { title: string; items: { label: string; - value: T; + value: string; checked: boolean; }[]; - onSelect: (value: T) => void; + onSelect: (value: string) => void; color: string; onClear: () => void; } -export function FilterSection({ +export function FilterSection({ title, items, color, onSelect, onClear, -}: FilterSectionProps) { +}: FilterSectionProps) { const [search, setSearch] = useState(""); const filteredItems = useMemo(() => { From c9c6d5ee4660245f8f8089eee2c64e331ec4c0ba Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 00:05:27 +0000 Subject: [PATCH 30/62] fix: fixing download sources migration --- .../src/components/header/header.css.ts | 32 +++++--- src/renderer/src/components/header/header.tsx | 76 +++++++++++++++---- src/renderer/src/hooks/use-repacks.ts | 4 +- .../src/pages/catalogue/catalogue.scss | 54 ------------- .../src/pages/catalogue/catalogue.tsx | 45 ----------- 5 files changed, 86 insertions(+), 125 deletions(-) diff --git a/src/renderer/src/components/header/header.css.ts b/src/renderer/src/components/header/header.css.ts index 903f4a42d..128559860 100644 --- a/src/renderer/src/components/header/header.css.ts +++ b/src/renderer/src/components/header/header.css.ts @@ -74,16 +74,28 @@ export const search = recipe({ }, }); -export const searchButton = recipe({ - base: { - WebkitAppRegion: "no-drag", - } as ComplexStyleRule, - variants: { - hidden: { - true: { - visibility: "hidden", - }, - }, +export const searchInput = style({ + backgroundColor: "transparent", + border: "none", + width: "100%", + height: "100%", + outline: "none", + color: "#DADBE1", + cursor: "default", + fontFamily: "inherit", + textOverflow: "ellipsis", + ":focus": { + cursor: "text", + }, +}); + +export const actionButton = style({ + color: "inherit", + cursor: "pointer", + transition: "all ease 0.2s", + padding: `${SPACING_UNIT}px`, + ":hover": { + color: "#DADBE1", }, }); diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index 2c79b1d4c..97ecbdc2d 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -1,13 +1,13 @@ import { useTranslation } from "react-i18next"; -import { useMemo } from "react"; +import { useMemo, useRef, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import { ArrowLeftIcon, SearchIcon } from "@primer/octicons-react"; +import { ArrowLeftIcon, SearchIcon, XIcon } from "@primer/octicons-react"; -import { useAppSelector } from "@renderer/hooks"; +import { useAppDispatch, useAppSelector } from "@renderer/hooks"; import * as styles from "./header.css"; import { AutoUpdateSubHeader } from "./auto-update-sub-header"; -import { Button } from "../button/button"; +import { setSearch } from "@renderer/features"; const pathTitle: Record = { "/": "home", @@ -17,6 +17,8 @@ const pathTitle: Record = { }; export function Header() { + const inputRef = useRef(null); + const navigate = useNavigate(); const location = useLocation(); @@ -24,6 +26,14 @@ export function Header() { (state) => state.window ); + const searchValue = useAppSelector( + (state) => state.catalogueSearch.value.title + ); + + const dispatch = useAppDispatch(); + + const [isFocused, setIsFocused] = useState(false); + const { t } = useTranslation("header"); const title = useMemo(() => { @@ -35,14 +45,27 @@ export function Header() { return t(pathTitle[location.pathname]); }, [location.pathname, headerTitle, t]); - const showSearchButton = useMemo(() => { - return location.pathname.startsWith("/catalogue"); - }, [location.pathname]); + const focusInput = () => { + setIsFocused(true); + inputRef.current?.focus(); + }; + + const handleBlur = () => { + setIsFocused(false); + }; const handleBackButtonClick = () => { navigate(-1); }; + const handleSearch = (value: string) => { + dispatch(setSearch({ title: value })); + + if (!location.pathname.startsWith("/catalogue")) { + navigate("/catalogue"); + } + }; + return ( <>
    - +
    + + + handleSearch(event.target.value)} + onFocus={() => setIsFocused(true)} + onBlur={handleBlur} + /> + + {searchValue && ( + + )} +
    diff --git a/src/renderer/src/hooks/use-repacks.ts b/src/renderer/src/hooks/use-repacks.ts index e55a2036c..1c160be6d 100644 --- a/src/renderer/src/hooks/use-repacks.ts +++ b/src/renderer/src/hooks/use-repacks.ts @@ -18,7 +18,9 @@ export function useRepacks() { const updateRepacks = useCallback(() => { repacksTable.toArray().then((repacks) => { - dispatch(setRepacks(repacks)); + dispatch( + setRepacks(repacks.filter((repack) => Array.isArray(repack.objectIds))) + ); }); }, [dispatch]); diff --git a/src/renderer/src/pages/catalogue/catalogue.scss b/src/renderer/src/pages/catalogue/catalogue.scss index a066ab7f3..e1ba3113f 100644 --- a/src/renderer/src/pages/catalogue/catalogue.scss +++ b/src/renderer/src/pages/catalogue/catalogue.scss @@ -19,60 +19,6 @@ width: 100%; padding: 16px; - &__search-container { - background-color: globals.$dark-background-color; - display: inline-flex; - transition: all ease 0.2s; - width: 200px; - align-items: center; - border-radius: 8px; - border: 1px solid globals.$border-color; - height: 40px; - - &:hover { - border-color: rgba(255, 255, 255, 0.5); - } - - &--focused { - width: 250px; - border-color: #dadbe1; - } - } - - &__search-icon-button { - color: inherit; - cursor: pointer; - transition: all ease 0.2s; - padding: 8px; - - &:hover { - color: #dadbe1; - } - } - - &__search-clear-button { - color: inherit; - cursor: pointer; - transition: all ease 0.2s; - padding: 8px; - - &:hover { - color: #dadbe1; - } - } - - &__search-input { - background-color: transparent; - border: none; - width: 100%; - height: 100%; - outline: none; - color: #dadbe1; - cursor: default; - font-family: inherit; - text-overflow: ellipsis; - } - &__game-item { background-color: globals.$dark-background-color; width: 100%; diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index e85e94b5c..bb44801be 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -141,51 +141,6 @@ export default function Catalogue() { return (
    -
    -
    - - - onSearch(event.target.value)} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - /> - - {filters.title && ( - - )} -
    -
    -
    Date: Mon, 23 Dec 2024 00:06:23 +0000 Subject: [PATCH 31/62] fix: fixing download sources migration --- .../src/pages/catalogue/catalogue.tsx | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index bb44801be..d3372eafa 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -2,11 +2,9 @@ import { Badge } from "@renderer/components"; import type { DownloadSource } from "@types"; -import cn from "classnames"; - import { useAppDispatch, useAppSelector, useRepacks } from "@renderer/hooks"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { SearchIcon, XIcon } from "@primer/octicons-react"; +import { XIcon } from "@primer/octicons-react"; import "./catalogue.scss"; @@ -34,8 +32,6 @@ export default function Catalogue() { const abortControllerRef = useRef(null); - const [focused, setFocused] = useState(false); - const [steamUserTags, setSteamUserTags] = useState({}); const [searchParams] = useSearchParams(); @@ -105,18 +101,6 @@ export default function Catalogue() { }); }, [getRepacksForObjectId]); - const focusInput = useCallback(() => { - setFocused(true); - inputRef.current?.focus(); - }, []); - - const onSearch = useCallback( - (value: string) => { - dispatch(setSearch({ title: value })); - }, - [dispatch] - ); - useEffect(() => { axios .get( @@ -133,12 +117,6 @@ export default function Catalogue() { }); }, [i18n.language]); - useEffect(() => { - if (search) { - focusInput(); - } - }, [search, focusInput]); - return (
    Date: Mon, 23 Dec 2024 00:07:02 +0000 Subject: [PATCH 32/62] fix: fixing download sources migration --- src/renderer/src/pages/catalogue/catalogue.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index d3372eafa..3890a3e12 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -3,7 +3,7 @@ import { Badge } from "@renderer/components"; import type { DownloadSource } from "@types"; import { useAppDispatch, useAppSelector, useRepacks } from "@renderer/hooks"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { XIcon } from "@primer/octicons-react"; import "./catalogue.scss"; @@ -28,15 +28,10 @@ const filterCategoryColors = { }; export default function Catalogue() { - const inputRef = useRef(null); - const abortControllerRef = useRef(null); const [steamUserTags, setSteamUserTags] = useState({}); - const [searchParams] = useSearchParams(); - const search = searchParams.get("search"); - const navigate = useNavigate(); const [downloadSources, setDownloadSources] = useState([]); From 4607665908cba84edaa5628b73140fed76817ca1 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 00:07:35 +0000 Subject: [PATCH 33/62] fix: fixing download sources migration --- src/renderer/src/pages/catalogue/catalogue.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 3890a3e12..2a120b057 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -12,7 +12,7 @@ import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { downloadSourcesTable } from "@renderer/dexie"; import { steamUrlBuilder } from "@shared"; import { buildGameDetailsPath } from "@renderer/helpers"; -import { useNavigate, useSearchParams } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { FilterSection } from "./filter-section"; import { setSearch } from "@renderer/features"; import { useTranslation } from "react-i18next"; From d6b5fd1b9fbcaf8ad2a0cc1546abfb71162b118c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 22 Dec 2024 21:21:05 -0300 Subject: [PATCH 34/62] feat: cloud ui --- src/locales/en/translation.json | 7 +++- src/locales/pt-BR/translation.json | 7 +++- .../achievements/achievement-panel.css.ts | 12 +++++- .../pages/achievements/achievement-panel.tsx | 38 ++++++++++++++----- .../hydra-cloud/hydra-cloud-modal.tsx | 8 +--- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b5576cd13..05cb2c05c 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -387,7 +387,8 @@ "hidden_achievement_tooltip": "This is a hidden achievement", "achievement_earn_points": "Earn {{points}} points with this achievement", "earned_points": "Earned points:", - "available_points": "Available points:" + "available_points": "Available points:", + "how_to_earn_achievements_points": "How to earn achievements points?" }, "hydra_cloud": { "subscription_tour_title": "Hydra Cloud Subscription", @@ -397,6 +398,8 @@ "animated_profile_picture": "Animated profile pictures", "premium_support": "Premium Support", "show_and_compare_achievements": "Show and compare your achievements to other users", - "animated_profile_banner": "Animated profile banner" + "animated_profile_banner": "Animated profile banner", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "You've just discovered a Hydra Cloud feature!" } } diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index b3f4977de..74617a2e1 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -384,16 +384,19 @@ "hidden_achievement_tooltip": "Está é uma conquista oculta", "achievement_earn_points": "Ganhe {{points}} pontos com essa conquista", "earned_points": "Pontos ganhos:", - "available_points": "Pontos disponíveis:" + "available_points": "Pontos disponíveis:", + "how_to_earn_achievements_points": "Como desbloquear pontos nas conquistas?" }, "hydra_cloud": { "subscription_tour_title": "Assinatura Hydra Cloud", + "hydra_cloud": "Hydra Cloud", "subscribe_now": "Inscreva-se agora", "cloud_achievements": "Salvamento de conquistas em nuvem", "animated_profile_picture": "Fotos de perfil animadas", "premium_support": "Suporte Premium", "show_and_compare_achievements": "Exiba e compare suas conquistas com outros usuários", "animated_profile_banner": "Banner animado no perfil", - "cloud_saving": "Saves de jogos em nuvem" + "cloud_saving": "Saves de jogos em nuvem", + "hydra_cloud_feature_found": "Você descobriu uma funcionalidade Hydra Cloud!" } } diff --git a/src/renderer/src/pages/achievements/achievement-panel.css.ts b/src/renderer/src/pages/achievements/achievement-panel.css.ts index 1882a8ef4..f8daeab9d 100644 --- a/src/renderer/src/pages/achievements/achievement-panel.css.ts +++ b/src/renderer/src/pages/achievements/achievement-panel.css.ts @@ -8,7 +8,8 @@ export const panel = style({ padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 3}px`, backgroundColor: vars.color.background, display: "flex", - alignItems: "center", + flexDirection: "column", + alignItems: "start", justifyContent: "space-between", borderBottom: `solid 1px ${vars.color.border}`, }); @@ -59,3 +60,12 @@ export const progressBar = recipe({ }, }, }); + +export const link = style({ + textAlign: "start", + color: vars.color.body, + ":hover": { + textDecoration: "underline", + cursor: "pointer", + }, +}); diff --git a/src/renderer/src/pages/achievements/achievement-panel.tsx b/src/renderer/src/pages/achievements/achievement-panel.tsx index b1233bd0f..398a3a94d 100644 --- a/src/renderer/src/pages/achievements/achievement-panel.tsx +++ b/src/renderer/src/pages/achievements/achievement-panel.tsx @@ -1,10 +1,10 @@ -import { useContext } from "react"; import { useTranslation } from "react-i18next"; -import { gameDetailsContext } from "@renderer/context"; -import * as styles from "./achievement-panel.css"; - import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; import { UserAchievement } from "@types"; +import { useSubscription } from "@renderer/hooks/use-subscription"; +import { useUserDetails } from "@renderer/hooks"; +import { vars } from "@renderer/theme.css"; +import * as styles from "./achievement-panel.css"; export interface AchievementPanelProps { achievements: UserAchievement[]; @@ -12,6 +12,8 @@ export interface AchievementPanelProps { export function AchievementPanel({ achievements }: AchievementPanelProps) { const { t } = useTranslation("achievement"); + const { hasActiveSubscription } = useUserDetails(); + const { showHydraCloudModal } = useSubscription(); const achievementsPointsTotal = achievements.reduce( (acc, achievement) => acc + (achievement.points ?? 0), @@ -24,16 +26,32 @@ export function AchievementPanel({ achievements }: AchievementPanelProps) { 0 ); - const {} = useContext(gameDetailsContext); - - return ( - <> + if (!hasActiveSubscription) { + return (
    {t("earned_points")} - {achievementsPointsEarnedSum} / {achievementsPointsTotal} + ??? / ???
    + +
    + ); + } + + return ( +
    +
    + {t("earned_points")} + {achievementsPointsEarnedSum} / {achievementsPointsTotal}
    - +
    ); } diff --git a/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx index 1f90669f7..b28eff92c 100644 --- a/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx +++ b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx @@ -15,11 +15,7 @@ export const HydraCloudModal = ({ visible, onClose }: HydraCloudModalProps) => { }; return ( - +
    { gap: `${SPACING_UNIT * 2}px`, }} > - Você descobriu uma funcionalidade Hydra Cloud! + {t("hydra_cloud_feature_found")}
    From 4d0a75e61f5986da563457bd27e94eeb2f0c5c71 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 22 Dec 2024 21:25:32 -0300 Subject: [PATCH 35/62] feat: refactor --- src/renderer/src/app.tsx | 5 +---- .../src/pages/achievements/compared-achievement-panel.tsx | 4 ---- src/renderer/src/pages/game-details/sidebar/sidebar.tsx | 1 - .../src/pages/profile/profile-content/friends-box.tsx | 1 - 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index a8d0ea060..3a489810b 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -26,8 +26,6 @@ import { import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; import { downloadSourcesWorker } from "./workers"; -import { repacksContext } from "./context"; -import { logger } from "./logger"; import { useSubscription } from "./hooks/use-subscription"; import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-modal"; @@ -60,8 +58,7 @@ export function App() { clearUserDetails, } = useUserDetails(); - const { hideHydraCloudModal, showHydraCloudModal, isHydraCloudModalVisible } = - useSubscription(); + const { hideHydraCloudModal, isHydraCloudModalVisible } = useSubscription(); const dispatch = useAppDispatch(); diff --git a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx index 3ac76d8da..f331b5c86 100644 --- a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx +++ b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx @@ -1,6 +1,4 @@ -import { useContext } from "react"; import { useTranslation } from "react-i18next"; -import { gameDetailsContext } from "@renderer/context"; import * as styles from "./achievement-panel.css"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; @@ -16,8 +14,6 @@ export function ComparedAchievementPanel({ }: ComparedAchievementPanelProps) { const { t } = useTranslation("achievement"); - const {} = useContext(gameDetailsContext); - return (
    Date: Mon, 23 Dec 2024 00:42:08 +0000 Subject: [PATCH 36/62] feat: removing publishers --- src/main/events/catalogue/search-games.ts | 7 +- src/preload/index.ts | 4 +- src/renderer/src/components/header/header.tsx | 6 +- src/renderer/src/declaration.d.ts | 5 +- src/renderer/src/features/catalogue-search.ts | 14 ++-- .../src/pages/catalogue/catalogue.tsx | 69 +++++++++++-------- .../src/pages/catalogue/filter-section.tsx | 4 ++ .../src/workers/download-sources.worker.ts | 1 + 8 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/main/events/catalogue/search-games.ts b/src/main/events/catalogue/search-games.ts index 40b3b033c..09ac2cee5 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -2,13 +2,16 @@ import type { CatalogueSearchPayload } from "@types"; import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; +const PAGE_SIZE = 12; + const searchGames = async ( _event: Electron.IpcMainInvokeEvent, - payload: CatalogueSearchPayload + payload: CatalogueSearchPayload, + page: number ) => { return HydraApi.post( "/catalogue/search", - { ...payload, take: 24, skip: 0 }, + { ...payload, take: page * PAGE_SIZE, skip: (page - 1) * PAGE_SIZE }, { needsAuth: false } ); }; diff --git a/src/preload/index.ts b/src/preload/index.ts index 86414e1a6..86158bf9b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -37,8 +37,8 @@ contextBridge.exposeInMainWorld("electron", { }, /* Catalogue */ - searchGames: (payload: CatalogueSearchPayload) => - ipcRenderer.invoke("searchGames", payload), + searchGames: (payload: CatalogueSearchPayload, page: number) => + ipcRenderer.invoke("searchGames", payload, page), getCatalogue: (category: CatalogueCategory) => ipcRenderer.invoke("getCatalogue", category), getGameShopDetails: (objectId: string, shop: GameShop, language: string) => diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index 97ecbdc2d..739ddd1d0 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -7,7 +7,7 @@ import { useAppDispatch, useAppSelector } from "@renderer/hooks"; import * as styles from "./header.css"; import { AutoUpdateSubHeader } from "./auto-update-sub-header"; -import { setSearch } from "@renderer/features"; +import { setFilters } from "@renderer/features"; const pathTitle: Record = { "/": "home", @@ -27,7 +27,7 @@ export function Header() { ); const searchValue = useAppSelector( - (state) => state.catalogueSearch.value.title + (state) => state.catalogueSearch.filters.title ); const dispatch = useAppDispatch(); @@ -59,7 +59,7 @@ export function Header() { }; const handleSearch = (value: string) => { - dispatch(setSearch({ title: value })); + dispatch(setFilters({ title: value })); if (!location.pathname.startsWith("/catalogue")) { navigate("/catalogue"); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index c8033d7e8..b1a911f77 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -49,7 +49,10 @@ declare global { ) => () => Electron.IpcRenderer; /* Catalogue */ - searchGames: (payload: CatalogueSearchPayload) => Promise; + searchGames: ( + payload: CatalogueSearchPayload, + page: number + ) => Promise<{ edges: any[]; count: number }>; getCatalogue: (category: CatalogueCategory) => Promise; getGameShopDetails: ( objectId: string, diff --git a/src/renderer/src/features/catalogue-search.ts b/src/renderer/src/features/catalogue-search.ts index a6318a262..9cddd038f 100644 --- a/src/renderer/src/features/catalogue-search.ts +++ b/src/renderer/src/features/catalogue-search.ts @@ -4,11 +4,11 @@ import type { PayloadAction } from "@reduxjs/toolkit"; import type { CatalogueSearchPayload } from "@types"; export interface CatalogueSearchState { - value: CatalogueSearchPayload; + filters: CatalogueSearchPayload; } const initialState: CatalogueSearchState = { - value: { + filters: { title: "", downloadSourceFingerprints: [], tags: [], @@ -22,16 +22,16 @@ export const catalogueSearchSlice = createSlice({ name: "catalogueSearch", initialState, reducers: { - setSearch: ( + setFilters: ( state, action: PayloadAction> ) => { - state.value = { ...state.value, ...action.payload }; + state.filters = { ...state.filters, ...action.payload }; }, - clearSearch: (state) => { - state.value = initialState.value; + clearFilters: (state) => { + state.filters = initialState.filters; }, }, }); -export const { setSearch, clearSearch } = catalogueSearchSlice.actions; +export const { setFilters, clearFilters } = catalogueSearchSlice.actions; diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 2a120b057..82457f7d9 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -1,4 +1,4 @@ -import { Badge } from "@renderer/components"; +import { Badge, Button } from "@renderer/components"; import type { DownloadSource } from "@types"; @@ -14,7 +14,7 @@ import { steamUrlBuilder } from "@shared"; import { buildGameDetailsPath } from "@renderer/helpers"; import { useNavigate } from "react-router-dom"; import { FilterSection } from "./filter-section"; -import { setSearch } from "@renderer/features"; +import { setFilters } from "@renderer/features"; import { useTranslation } from "react-i18next"; import axios from "axios"; import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; @@ -35,12 +35,15 @@ export default function Catalogue() { const navigate = useNavigate(); const [downloadSources, setDownloadSources] = useState([]); - const [games, setGames] = useState([]); const [isLoading, setIsLoading] = useState(true); const [publishers, setPublishers] = useState([]); const [developers, setDevelopers] = useState([]); - const filters = useAppSelector((state) => state.catalogueSearch.value); + const [results, setResults] = useState([]); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + + const { filters } = useAppSelector((state) => state.catalogueSearch); const dispatch = useAppDispatch(); @@ -49,7 +52,7 @@ export default function Catalogue() { const { getRepacksForObjectId } = useRepacks(); useEffect(() => { - setGames([]); + setResults([]); setIsLoading(true); abortControllerRef.current?.abort(); @@ -57,18 +60,19 @@ export default function Catalogue() { abortControllerRef.current = abortController; window.electron - .searchGames(filters) - .then((games) => { + .searchGames(filters, page) + .then((response) => { if (abortController.signal.aborted) { return; } - setGames(games); + setResults(response.edges); + setTotalPages(Math.ceil(response.count / 12)); }) .finally(() => { setIsLoading(false); }); - }, [filters]); + }, [filters, page, dispatch]); useEffect(() => { window.electron.getDevelopers().then((developers) => { @@ -81,14 +85,14 @@ export default function Catalogue() { }, []); const gamesWithRepacks = useMemo(() => { - return games.map((game) => { + return results.map((game) => { const repacks = getRepacksForObjectId(game.objectId); const uniqueRepackers = Array.from( new Set(repacks.map((repack) => repack.repacker)) ); return { ...game, repacks: uniqueRepackers }; }); - }, [games, getRepacksForObjectId]); + }, [results, getRepacksForObjectId]); useEffect(() => { downloadSourcesTable.toArray().then((sources) => { @@ -264,27 +268,32 @@ export default function Catalogue() { )) )} - {/*
    - - -
    */} + {totalPages > 1 && ( +
    + {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
    + )}
    dispatch(setSearch({ genres: [] }))} + onClear={() => dispatch(setFilters({ genres: [] }))} color={filterCategoryColors.genres} onSelect={(value) => { if (filters.genres.includes(value)) { dispatch( - setSearch({ + setFilters({ genres: filters.genres.filter((genre) => genre !== value), }) ); } else { - dispatch(setSearch({ genres: [...filters.genres, value] })); + dispatch(setFilters({ genres: [...filters.genres, value] })); } }} items={[ @@ -329,17 +338,17 @@ export default function Catalogue() { dispatch(setSearch({ tags: [] }))} + onClear={() => dispatch(setFilters({ tags: [] }))} onSelect={(value) => { if (filters.tags.includes(Number(value))) { dispatch( - setSearch({ + setFilters({ tags: filters.tags.filter((tag) => tag !== Number(value)), }) ); } else { dispatch( - setSearch({ tags: [...filters.tags, Number(value)] }) + setFilters({ tags: [...filters.tags, Number(value)] }) ); } }} @@ -358,12 +367,12 @@ export default function Catalogue() { title={t("download_sources")} color={filterCategoryColors.downloadSourceFingerprints} onClear={() => - dispatch(setSearch({ downloadSourceFingerprints: [] })) + dispatch(setFilters({ downloadSourceFingerprints: [] })) } onSelect={(value) => { if (filters.downloadSourceFingerprints.includes(value)) { dispatch( - setSearch({ + setFilters({ downloadSourceFingerprints: filters.downloadSourceFingerprints.filter( (fingerprint) => fingerprint !== value @@ -372,7 +381,7 @@ export default function Catalogue() { ); } else { dispatch( - setSearch({ + setFilters({ downloadSourceFingerprints: [ ...filters.downloadSourceFingerprints, value, @@ -393,11 +402,11 @@ export default function Catalogue() { dispatch(setSearch({ developers: [] }))} + onClear={() => dispatch(setFilters({ developers: [] }))} onSelect={(value) => { if (filters.developers.includes(value)) { dispatch( - setSearch({ + setFilters({ developers: filters.developers.filter( (developer) => developer !== value ), @@ -405,7 +414,7 @@ export default function Catalogue() { ); } else { dispatch( - setSearch({ developers: [...filters.developers, value] }) + setFilters({ developers: [...filters.developers, value] }) ); } }} @@ -419,11 +428,11 @@ export default function Catalogue() { dispatch(setSearch({ publishers: [] }))} + onClear={() => dispatch(setFilters({ publishers: [] }))} onSelect={(value) => { if (filters.publishers.includes(value)) { dispatch( - setSearch({ + setFilters({ publishers: filters.publishers.filter( (publisher) => publisher !== value ), @@ -431,7 +440,7 @@ export default function Catalogue() { ); } else { dispatch( - setSearch({ publishers: [...filters.publishers, value] }) + setFilters({ publishers: [...filters.publishers, value] }) ); } }} diff --git a/src/renderer/src/pages/catalogue/filter-section.tsx b/src/renderer/src/pages/catalogue/filter-section.tsx index e82b9ded2..4039ab598 100644 --- a/src/renderer/src/pages/catalogue/filter-section.tsx +++ b/src/renderer/src/pages/catalogue/filter-section.tsx @@ -45,6 +45,10 @@ export function FilterSection({ const { formatNumber } = useFormat(); + if (!items.length) { + return null; + } + return (
    diff --git a/src/renderer/src/workers/download-sources.worker.ts b/src/renderer/src/workers/download-sources.worker.ts index 185afed5d..dd43b6824 100644 --- a/src/renderer/src/workers/download-sources.worker.ts +++ b/src/renderer/src/workers/download-sources.worker.ts @@ -156,6 +156,7 @@ self.onmessage = async (event: MessageEvent) => { const existingRepacks = await repacksTable.toArray(); for (const downloadSource of downloadSources) { + console.log(downloadSource); if (!downloadSource.fingerprint) { await deleteDownloadSource(downloadSource.id); await importDownloadSource(downloadSource.url); From b439a1af3f5cdd1b817cfc82506ac7f0164be34b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 00:46:11 +0000 Subject: [PATCH 37/62] feat: removing publishers --- .../src/pages/settings/settings-download-sources.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index f5c01a21c..aabbab27f 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -13,8 +13,9 @@ import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { settingsContext } from "@renderer/context"; import { downloadSourcesTable } from "@renderer/dexie"; import { downloadSourcesWorker } from "@renderer/workers"; -import { clearSearch, setSearch } from "@renderer/features"; import { useNavigate } from "react-router-dom"; +import { clearFilters } from "@renderer/features"; +import { setFilters } from "@renderer/features"; export function SettingsDownloadSources() { const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] = @@ -103,8 +104,8 @@ export function SettingsDownloadSources() { }; const navigateToCatalogue = (fingerprint: string) => { - dispatch(clearSearch()); - dispatch(setSearch({ downloadSourceFingerprints: [fingerprint] })); + dispatch(clearFilters()); + dispatch(setFilters({ downloadSourceFingerprints: [fingerprint] })); navigate("/catalogue"); }; From 214f7d3523d9ab57eb1d6e77ee9638456d40ae12 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 00:47:39 +0000 Subject: [PATCH 38/62] feat: removing publishers --- src/renderer/src/components/header/header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index 739ddd1d0..f53eb6e8e 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -120,7 +120,7 @@ export function Header() { {searchValue && (
    ) : (
    )} diff --git a/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx index b28eff92c..fd44ce30e 100644 --- a/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx +++ b/src/renderer/src/pages/shared-modals/hydra-cloud/hydra-cloud-modal.tsx @@ -3,11 +3,16 @@ import { SPACING_UNIT } from "@renderer/theme.css"; import { useTranslation } from "react-i18next"; export interface HydraCloudModalProps { + feature: string; visible: boolean; onClose: () => void; } -export const HydraCloudModal = ({ visible, onClose }: HydraCloudModalProps) => { +export const HydraCloudModal = ({ + feature, + visible, + onClose, +}: HydraCloudModalProps) => { const { t } = useTranslation("hydra_cloud"); const handleClickOpenCheckout = () => { @@ -17,6 +22,7 @@ export const HydraCloudModal = ({ visible, onClose }: HydraCloudModalProps) => { return (
    { }} > {t("hydra_cloud_feature_found")} - +
    ); diff --git a/src/types/index.ts b/src/types/index.ts index 54a3993ea..7c0aea2b5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -14,6 +14,11 @@ export type GameShop = "steam" | "epic"; export type FriendRequestAction = "ACCEPTED" | "REFUSED" | "CANCEL"; +export type HydraCloudFeature = + | "achievements" + | "backup" + | "achievements-points"; + export interface GameRepack { id: number; title: string; From 4476b1b216ba2425ccd4c4f8eb28a3cbae37302a Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 01:32:45 +0000 Subject: [PATCH 41/62] feat: removing publishers --- src/renderer/src/app.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index fab239c2a..95edba5d8 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -26,6 +26,7 @@ import { import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; import { downloadSourcesWorker } from "./workers"; +import { downloadSourcesTable } from "./dexie"; export interface AppProps { children: React.ReactNode; @@ -205,6 +206,18 @@ export function App() { const newRepacksCount = event.data; window.electron.publishNewRepacksNotification(newRepacksCount); updateRepacks(); + + downloadSourcesTable.toArray().then((downloadSources) => { + downloadSources + .filter((source) => !source.fingerprint) + .forEach((downloadSource) => { + window.electron + .putDownloadSource(downloadSource.objectIds) + .then(({ fingerprint }) => { + downloadSourcesTable.update(downloadSource.id, { fingerprint }); + }); + }); + }); }; downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); From 8f169ee11390cad21610f816bb151d21bf60cd5f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:00:10 -0300 Subject: [PATCH 42/62] fix: achievement panel when owner does not have the game --- .../pages/achievements/compared-achievement-panel.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx index f331b5c86..8eba2b60f 100644 --- a/src/renderer/src/pages/achievements/compared-achievement-panel.tsx +++ b/src/renderer/src/pages/achievements/compared-achievement-panel.tsx @@ -4,6 +4,7 @@ import * as styles from "./achievement-panel.css"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; import { ComparedAchievements } from "@types"; import { SPACING_UNIT } from "@renderer/theme.css"; +import { useUserDetails } from "@renderer/hooks"; export interface ComparedAchievementPanelProps { achievements: ComparedAchievements; @@ -13,16 +14,14 @@ export function ComparedAchievementPanel({ achievements, }: ComparedAchievementPanelProps) { const { t } = useTranslation("achievement"); + const { hasActiveSubscription } = useUserDetails(); return (
    @@ -30,10 +29,10 @@ export function ComparedAchievementPanel({ {t("available_points")} {" "} {achievements.achievementsPointsTotal}
    - {achievements.owner.achievementsPointsEarnedSum !== undefined && ( + {hasActiveSubscription && (
    - {achievements.owner.achievementsPointsEarnedSum} + {achievements.owner.achievementsPointsEarnedSum ?? 0}
    )}
    From e02875ad56769b391279bd91c66cae87225d0078 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:08:56 -0300 Subject: [PATCH 43/62] feat: improve friends playing game box --- .../profile/profile-content/friends-box.tsx | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/pages/profile/profile-content/friends-box.tsx b/src/renderer/src/pages/profile/profile-content/friends-box.tsx index 51ee25d2c..6cc40b231 100644 --- a/src/renderer/src/pages/profile/profile-content/friends-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/friends-box.tsx @@ -2,7 +2,7 @@ import { userProfileContext } from "@renderer/context"; import { useFormat } from "@renderer/hooks"; import { useContext } from "react"; import { useTranslation } from "react-i18next"; - +import SteamLogo from "@renderer/assets/steam-logo.svg?react"; import * as styles from "./profile-content.css"; import { Avatar, Link } from "@renderer/components"; @@ -13,6 +13,21 @@ export function FriendsBox() { const { numberFormatter } = useFormat(); + const getGameImage = (game: { iconUrl: string | null; title: string }) => { + if (game.iconUrl) { + return ( + {game.title} + ); + } + + return ; + }; + if (!userProfile?.friends.length) return null; return ( @@ -27,7 +42,14 @@ export function FriendsBox() {
      {userProfile?.friends.map((friend) => ( -
    • +
    • -
      +
      {friend.displayName} {friend.currentGame && ( -

      {t("playing", { game: friend.currentGame.title })}

      +
      + {getGameImage(friend.currentGame)} + {friend.currentGame.title} +
      )}
      From 1ff33be36f63f8199f8cacc3f956431e2f3c36f3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:19:32 -0300 Subject: [PATCH 44/62] feat: remove download warning --- .../game-details/modals/download-settings-modal.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx index 35982b19e..191d9ac16 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx @@ -8,7 +8,7 @@ import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react"; import { Downloader, formatBytes, getDownloadersForUris } from "@shared"; import type { GameRepack } from "@types"; -import { SPACING_UNIT, vars } from "@renderer/theme.css"; +import { SPACING_UNIT } from "@renderer/theme.css"; import { DOWNLOADER_NAME } from "@renderer/constants"; import { useAppSelector, useToast } from "@renderer/hooks"; @@ -159,16 +159,6 @@ export function DownloadSettingsModal({ ))}
      - - {selectedDownloader != null && - selectedDownloader !== Downloader.Torrent && ( -

      - - {t("warning")} - {" "} - {t("hydra_needs_to_remain_open")} -

      - )}
    Date: Mon, 23 Dec 2024 18:57:41 +0000 Subject: [PATCH 45/62] feat: adding remove function to filter tags --- src/main/events/catalogue/search-games.ts | 7 +- src/preload/index.ts | 6 +- src/renderer/src/components/header/header.tsx | 8 +- src/renderer/src/declaration.d.ts | 3 +- src/renderer/src/hooks/use-catalogue.ts | 54 ++ src/renderer/src/hooks/use-repacks.ts | 8 +- .../src/pages/catalogue/catalogue.scss | 31 - .../src/pages/catalogue/catalogue.tsx | 554 +++++++----------- .../src/pages/catalogue/filter-item.tsx | 50 ++ .../src/pages/catalogue/game-item.scss | 48 ++ .../src/pages/catalogue/game-item.tsx | 50 ++ .../src/pages/catalogue/pagination.tsx | 83 +++ .../cloud-sync-modal/cloud-sync-modal.tsx | 11 +- .../settings/settings-download-sources.css.ts | 14 + .../settings/settings-download-sources.tsx | 10 +- src/renderer/src/scss/globals.scss | 2 +- src/renderer/src/theme.css.ts | 2 +- src/types/index.ts | 6 + 18 files changed, 561 insertions(+), 386 deletions(-) create mode 100644 src/renderer/src/hooks/use-catalogue.ts create mode 100644 src/renderer/src/pages/catalogue/filter-item.tsx create mode 100644 src/renderer/src/pages/catalogue/game-item.scss create mode 100644 src/renderer/src/pages/catalogue/game-item.tsx create mode 100644 src/renderer/src/pages/catalogue/pagination.tsx diff --git a/src/main/events/catalogue/search-games.ts b/src/main/events/catalogue/search-games.ts index 09ac2cee5..8b22101dc 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -2,16 +2,15 @@ import type { CatalogueSearchPayload } from "@types"; import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; -const PAGE_SIZE = 12; - const searchGames = async ( _event: Electron.IpcMainInvokeEvent, payload: CatalogueSearchPayload, - page: number + take: number, + skip: number ) => { return HydraApi.post( "/catalogue/search", - { ...payload, take: page * PAGE_SIZE, skip: (page - 1) * PAGE_SIZE }, + { ...payload, take, skip }, { needsAuth: false } ); }; diff --git a/src/preload/index.ts b/src/preload/index.ts index 86158bf9b..85f5f8578 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -37,8 +37,8 @@ contextBridge.exposeInMainWorld("electron", { }, /* Catalogue */ - searchGames: (payload: CatalogueSearchPayload, page: number) => - ipcRenderer.invoke("searchGames", payload, page), + searchGames: (payload: CatalogueSearchPayload, take: number, skip: number) => + ipcRenderer.invoke("searchGames", payload, take, skip), getCatalogue: (category: CatalogueCategory) => ipcRenderer.invoke("getCatalogue", category), getGameShopDetails: (objectId: string, shop: GameShop, language: string) => @@ -65,8 +65,6 @@ contextBridge.exposeInMainWorld("electron", { listener ); }, - getPublishers: () => ipcRenderer.invoke("getPublishers"), - getDevelopers: () => ipcRenderer.invoke("getDevelopers"), /* User preferences */ getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index f53eb6e8e..fd4e6510b 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { ArrowLeftIcon, SearchIcon, XIcon } from "@primer/octicons-react"; @@ -66,6 +66,12 @@ export function Header() { } }; + useEffect(() => { + if (!location.pathname.startsWith("/catalogue")) { + dispatch(setFilters({ title: "" })); + } + }, [location.pathname, dispatch]); + return ( <>
    Promise<{ edges: any[]; count: number }>; getCatalogue: (category: CatalogueCategory) => Promise; getGameShopDetails: ( diff --git a/src/renderer/src/hooks/use-catalogue.ts b/src/renderer/src/hooks/use-catalogue.ts new file mode 100644 index 000000000..9c774e831 --- /dev/null +++ b/src/renderer/src/hooks/use-catalogue.ts @@ -0,0 +1,54 @@ +import axios from "axios"; +import { useCallback, useEffect, useState } from "react"; + +export const externalResourcesInstance = axios.create({ + baseURL: import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL, +}); + +export function useCatalogue() { + const [steamGenres, setSteamGenres] = useState>({}); + const [steamUserTags, setSteamUserTags] = useState< + Record> + >({}); + + const [steamPublishers, setSteamPublishers] = useState([]); + const [steamDevelopers, setSteamDevelopers] = useState([]); + + const getSteamUserTags = useCallback(() => { + externalResourcesInstance.get("/steam-user-tags.json").then((response) => { + setSteamUserTags(response.data); + }); + }, []); + + const getSteamGenres = useCallback(() => { + externalResourcesInstance.get("/steam-genres.json").then((response) => { + setSteamGenres(response.data); + }); + }, []); + + const getSteamPublishers = useCallback(() => { + externalResourcesInstance.get("/steam-publishers.json").then((response) => { + setSteamPublishers(response.data); + }); + }, []); + + const getSteamDevelopers = useCallback(() => { + externalResourcesInstance.get("/steam-developers.json").then((response) => { + setSteamDevelopers(response.data); + }); + }, []); + + useEffect(() => { + getSteamUserTags(); + getSteamGenres(); + getSteamPublishers(); + getSteamDevelopers(); + }, [ + getSteamUserTags, + getSteamGenres, + getSteamPublishers, + getSteamDevelopers, + ]); + + return { steamGenres, steamUserTags, steamPublishers, steamDevelopers }; +} diff --git a/src/renderer/src/hooks/use-repacks.ts b/src/renderer/src/hooks/use-repacks.ts index 1c160be6d..dbc918b99 100644 --- a/src/renderer/src/hooks/use-repacks.ts +++ b/src/renderer/src/hooks/use-repacks.ts @@ -19,7 +19,13 @@ export function useRepacks() { const updateRepacks = useCallback(() => { repacksTable.toArray().then((repacks) => { dispatch( - setRepacks(repacks.filter((repack) => Array.isArray(repack.objectIds))) + setRepacks( + JSON.parse( + JSON.stringify( + repacks.filter((repack) => Array.isArray(repack.objectIds)) + ) + ) + ) ); }); }, [dispatch]); diff --git a/src/renderer/src/pages/catalogue/catalogue.scss b/src/renderer/src/pages/catalogue/catalogue.scss index e1ba3113f..18cc18bea 100644 --- a/src/renderer/src/pages/catalogue/catalogue.scss +++ b/src/renderer/src/pages/catalogue/catalogue.scss @@ -1,17 +1,5 @@ @use "../../scss/globals.scss"; -@keyframes gradientBorder { - 0% { - border-image-source: linear-gradient(0deg, #16b195 50%, #3e62c0 100%); - } - 50% { - border-image-source: linear-gradient(90deg, #3e62c0 50%, #16b195 100%); - } - 100% { - border-image-source: linear-gradient(180deg, #16b195 50%, #3e62c0 100%); - } -} - .catalogue { display: flex; flex-direction: column; @@ -19,25 +7,6 @@ width: 100%; padding: 16px; - &__game-item { - background-color: globals.$dark-background-color; - width: 100%; - color: #fff; - display: flex; - align-items: center; - overflow: hidden; - position: relative; - border-radius: 4px; - border: 1px solid globals.$border-color; - cursor: pointer; - gap: 12px; - transition: all ease 0.2s; - - &:hover { - background-color: rgba(255, 255, 255, 0.05); - } - } - &__filters-container { width: 270px; min-width: 270px; diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 82457f7d9..5a1005d1f 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -1,23 +1,25 @@ -import { Badge, Button } from "@renderer/components"; - import type { DownloadSource } from "@types"; -import { useAppDispatch, useAppSelector, useRepacks } from "@renderer/hooks"; +import { + useAppDispatch, + useAppSelector, + useFormat, + useRepacks, +} from "@renderer/hooks"; import { useEffect, useMemo, useRef, useState } from "react"; -import { XIcon } from "@primer/octicons-react"; import "./catalogue.scss"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { downloadSourcesTable } from "@renderer/dexie"; -import { steamUrlBuilder } from "@shared"; -import { buildGameDetailsPath } from "@renderer/helpers"; -import { useNavigate } from "react-router-dom"; import { FilterSection } from "./filter-section"; import { setFilters } from "@renderer/features"; import { useTranslation } from "react-i18next"; -import axios from "axios"; import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; +import { Pagination } from "./pagination"; +import { useCatalogue } from "@renderer/hooks/use-catalogue"; +import { GameItem } from "./game-item"; +import { FilterItem } from "./filter-item"; const filterCategoryColors = { genres: "hsl(262deg 50% 47%)", @@ -27,21 +29,23 @@ const filterCategoryColors = { publishers: "hsl(200deg 50% 30%)", }; +const PAGE_SIZE = 20; + export default function Catalogue() { const abortControllerRef = useRef(null); - const [steamUserTags, setSteamUserTags] = useState({}); - - const navigate = useNavigate(); + const { steamGenres, steamUserTags, steamDevelopers, steamPublishers } = + useCatalogue(); const [downloadSources, setDownloadSources] = useState([]); const [isLoading, setIsLoading] = useState(true); - const [publishers, setPublishers] = useState([]); - const [developers, setDevelopers] = useState([]); const [results, setResults] = useState([]); + const [page, setPage] = useState(1); - const [totalPages, setTotalPages] = useState(1); + const [itemsCount, setItemsCount] = useState(0); + + const { formatNumber } = useFormat(); const { filters } = useAppSelector((state) => state.catalogueSearch); @@ -60,61 +64,155 @@ export default function Catalogue() { abortControllerRef.current = abortController; window.electron - .searchGames(filters, page) + .searchGames(filters, PAGE_SIZE, (page - 1) * PAGE_SIZE) .then((response) => { if (abortController.signal.aborted) { return; } setResults(response.edges); - setTotalPages(Math.ceil(response.count / 12)); - }) - .finally(() => { + setItemsCount(response.count); setIsLoading(false); }); }, [filters, page, dispatch]); - useEffect(() => { - window.electron.getDevelopers().then((developers) => { - setDevelopers(developers); - }); - - window.electron.getPublishers().then((publishers) => { - setPublishers(publishers); - }); - }, []); - - const gamesWithRepacks = useMemo(() => { - return results.map((game) => { - const repacks = getRepacksForObjectId(game.objectId); - const uniqueRepackers = Array.from( - new Set(repacks.map((repack) => repack.repacker)) - ); - return { ...game, repacks: uniqueRepackers }; - }); - }, [results, getRepacksForObjectId]); - useEffect(() => { downloadSourcesTable.toArray().then((sources) => { setDownloadSources(sources.filter((source) => !!source.fingerprint)); }); }, [getRepacksForObjectId]); - useEffect(() => { - axios - .get( - `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/steam-user-tags.json` - ) - .then((response) => { - const language = i18n.language.split("-")[0]; - - if (response.data[language]) { - setSteamUserTags(response.data[language]); - } else { - setSteamUserTags(response.data["en"]); - } - }); - }, [i18n.language]); + const language = i18n.language.split("-")[0]; + + const steamGenresMapping = useMemo>(() => { + if (!steamGenres[language]) return {}; + + return steamGenres[language].reduce((prev, genre, index) => { + prev[genre] = steamGenres["en"][index]; + return prev; + }, {}); + }, [steamGenres, language]); + + const steamGenresFilterItems = useMemo(() => { + return Object.entries(steamGenresMapping) + .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) + .map(([key, value]) => ({ + label: key, + value: value, + checked: filters.genres.includes(value), + })); + }, [steamGenresMapping, filters.genres]); + + const steamUserTagsFilterItems = useMemo(() => { + if (!steamUserTags[language]) return []; + + return Object.entries(steamUserTags[language]) + .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) + .map(([key, value]) => ({ + label: key, + value: value.toString(), + checked: filters.tags.includes(value), + })); + }, [steamUserTags, filters.tags, language]); + + const groupedFilters = useMemo(() => { + return [ + ...filters.genres.map((genre) => ({ + label: Object.keys(steamGenresMapping).find( + (key) => steamGenresMapping[key] === genre + ) as string, + orbColor: filterCategoryColors.genres, + key: "genres", + value: genre, + })), + + ...filters.tags.map((tag) => ({ + label: Object.keys(steamUserTags[language]).find( + (key) => steamUserTags[language][key] === tag + ) as string, + orbColor: filterCategoryColors.tags, + key: "tags", + value: tag, + })), + + ...filters.downloadSourceFingerprints.map((fingerprint) => ({ + label: downloadSources.find( + (source) => source.fingerprint === fingerprint + )?.name as string, + orbColor: filterCategoryColors.downloadSourceFingerprints, + key: "downloadSourceFingerprints", + value: fingerprint, + })), + + ...filters.developers.map((developer) => ({ + label: developer, + orbColor: filterCategoryColors.developers, + key: "developers", + value: developer, + })), + + ...filters.publishers.map((publisher) => ({ + label: publisher, + orbColor: filterCategoryColors.publishers, + key: "publishers", + value: publisher, + })), + ]; + }, [filters, steamUserTags, steamGenresMapping, language, downloadSources]); + + const filterSections = useMemo(() => { + return [ + { + title: t("genres"), + items: steamGenresFilterItems, + key: "genres", + }, + { + title: t("tags"), + items: steamUserTagsFilterItems, + key: "tags", + }, + { + title: t("download_sources"), + items: downloadSources.map((source) => ({ + label: source.name, + value: source.fingerprint, + checked: filters.downloadSourceFingerprints.includes( + source.fingerprint + ), + })), + key: "downloadSourceFingerprints", + }, + { + title: t("developers"), + items: steamDevelopers.map((developer) => ({ + label: developer, + value: developer, + checked: filters.developers.includes(developer), + })), + key: "developers", + }, + { + title: t("publishers"), + items: steamPublishers.map((publisher) => ({ + label: publisher, + value: publisher, + checked: filters.publishers.includes(publisher), + })), + key: "publishers", + }, + ]; + }, [ + downloadSources, + filters.developers, + filters.downloadSourceFingerprints, + filters.publishers, + steamDevelopers, + steamGenresFilterItems, + steamPublishers, + steamUserTagsFilterItems, + t, + ]); return (
    @@ -127,65 +225,35 @@ export default function Catalogue() { }} >
    - {filters.genres.map((genre) => ( - -
    -
    - - {genre} -
    - - ))} - -
  • -
    - Action - -
  • + {groupedFilters.map((filter) => ( +
  • + { + dispatch( + setFilters({ + [filter.key]: filters[filter.key].filter( + (item) => item !== filter.value + ), + }) + ); + }} + /> +
  • + ))} +
    - - {/* */}
    ) : ( - gamesWithRepacks.map((game, i) => ( - - )) + results.map((game) => ) )} - {totalPages > 1 && ( -
    - {Array.from({ length: 3 }).map((_, i) => ( - - ))} -
    - )} +
    + {formatNumber(itemsCount)} resultados + + +
    - dispatch(setFilters({ genres: [] }))} - color={filterCategoryColors.genres} - onSelect={(value) => { - if (filters.genres.includes(value)) { - dispatch( - setFilters({ - genres: filters.genres.filter((genre) => genre !== value), - }) - ); - } else { - dispatch(setFilters({ genres: [...filters.genres, value] })); - } - }} - items={[ - "Action", - "Strategy", - "RPG", - "Casual", - "Racing", - "Sports", - "Indie", - "Adventure", - "Simulation", - "Massively Multiplayer", - "Free to Play", - "Accounting", - "Animation & Modeling", - "Audio Production", - "Design & Illustration", - "Education", - "Photo Editing", - "Software Training", - "Utilities", - "Video Production", - "Web Publishing", - "Game Development", - "Early Access", - "Sexual Content", - "Nudity", - "Violent", - "Gore", - "Documentary", - "Tutorial", - ] - .sort() - .map((genre) => ({ - label: genre, - value: genre, - checked: filters.genres.includes(genre), - }))} - /> - - dispatch(setFilters({ tags: [] }))} - onSelect={(value) => { - if (filters.tags.includes(Number(value))) { - dispatch( - setFilters({ - tags: filters.tags.filter((tag) => tag !== Number(value)), - }) - ); - } else { - dispatch( - setFilters({ tags: [...filters.tags, Number(value)] }) - ); - } - }} - items={ - Object.entries(steamUserTags) - .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) - .map(([key, value]) => ({ - label: key, - value: value, - checked: filters.tags.includes(value as number), - })) as any - } - /> - - - dispatch(setFilters({ downloadSourceFingerprints: [] })) - } - onSelect={(value) => { - if (filters.downloadSourceFingerprints.includes(value)) { - dispatch( - setFilters({ - downloadSourceFingerprints: - filters.downloadSourceFingerprints.filter( - (fingerprint) => fingerprint !== value - ), - }) - ); - } else { - dispatch( - setFilters({ - downloadSourceFingerprints: [ - ...filters.downloadSourceFingerprints, - value, - ], - }) - ); - } - }} - items={downloadSources.map((downloadSource) => ({ - label: downloadSource.name, - value: downloadSource.fingerprint, - checked: filters.downloadSourceFingerprints.includes( - downloadSource.fingerprint - ), - }))} - /> - - dispatch(setFilters({ developers: [] }))} - onSelect={(value) => { - if (filters.developers.includes(value)) { - dispatch( - setFilters({ - developers: filters.developers.filter( - (developer) => developer !== value - ), - }) - ); - } else { - dispatch( - setFilters({ developers: [...filters.developers, value] }) - ); - } - }} - items={developers.map((developer) => ({ - label: developer, - value: developer, - checked: filters.developers.includes(developer), - }))} - /> - - dispatch(setFilters({ publishers: [] }))} - onSelect={(value) => { - if (filters.publishers.includes(value)) { - dispatch( - setFilters({ - publishers: filters.publishers.filter( - (publisher) => publisher !== value - ), - }) - ); - } else { - dispatch( - setFilters({ publishers: [...filters.publishers, value] }) - ); - } - }} - items={publishers.map((publisher) => ({ - label: publisher, - value: publisher, - checked: filters.publishers.includes(publisher), - }))} - /> + {filterSections.map((section) => ( + dispatch(setFilters({ [section.key]: [] }))} + color={filterCategoryColors[section.key]} + onSelect={(value) => { + if (filters[section.key].includes(value)) { + dispatch( + setFilters({ + [section.key]: filters[ + section.key as + | "genres" + | "tags" + | "downloadSourceFingerprints" + | "developers" + | "publishers" + ].filter((item) => item !== value), + }) + ); + } else { + dispatch( + setFilters({ + [section.key]: [...filters[section.key], value], + }) + ); + } + }} + items={section.items} + /> + ))}
    diff --git a/src/renderer/src/pages/catalogue/filter-item.tsx b/src/renderer/src/pages/catalogue/filter-item.tsx new file mode 100644 index 000000000..2413bee98 --- /dev/null +++ b/src/renderer/src/pages/catalogue/filter-item.tsx @@ -0,0 +1,50 @@ +import { vars } from "@renderer/theme.css"; +import { XIcon } from "@primer/octicons-react"; + +interface FilterItemProps { + filter: string; + orbColor: string; + onRemove: () => void; +} + +export function FilterItem({ filter, orbColor, onRemove }: FilterItemProps) { + return ( +
    +
    + {filter} + +
    + ); +} diff --git a/src/renderer/src/pages/catalogue/game-item.scss b/src/renderer/src/pages/catalogue/game-item.scss new file mode 100644 index 000000000..83d182f4c --- /dev/null +++ b/src/renderer/src/pages/catalogue/game-item.scss @@ -0,0 +1,48 @@ +@use "../../scss/globals.scss"; + +.game-item { + background-color: globals.$dark-background-color; + width: 100%; + color: #fff; + display: flex; + align-items: center; + overflow: hidden; + position: relative; + border-radius: 4px; + border: 1px solid globals.$border-color; + cursor: pointer; + gap: calc(globals.$spacing-unit * 2); + transition: all ease 0.2s; + + &:hover { + background-color: rgba(255, 255, 255, 0.05); + } + + &__cover { + width: 200px; + height: 100%; + object-fit: cover; + border-right: 1px solid globals.$border-color; + } + + &__details { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + padding: calc(globals.$spacing-unit * 2) 0; + } + + &__genres { + color: globals.$body-color; + font-size: 12px; + text-align: left; + margin-bottom: 4px; + } + + &__repackers { + display: flex; + gap: globals.$spacing-unit; + flex-wrap: wrap; + } +} diff --git a/src/renderer/src/pages/catalogue/game-item.tsx b/src/renderer/src/pages/catalogue/game-item.tsx new file mode 100644 index 000000000..06f309987 --- /dev/null +++ b/src/renderer/src/pages/catalogue/game-item.tsx @@ -0,0 +1,50 @@ +import { Badge } from "@renderer/components"; +import { buildGameDetailsPath } from "@renderer/helpers"; +import { useRepacks } from "@renderer/hooks"; +import { steamUrlBuilder } from "@shared"; +import { useMemo } from "react"; +import { useNavigate } from "react-router-dom"; + +import "./game-item.scss"; + +export interface GameItemProps { + game: any; +} + +export function GameItem({ game }: GameItemProps) { + const navigate = useNavigate(); + + const { getRepacksForObjectId } = useRepacks(); + + const repacks = getRepacksForObjectId(game.objectId); + + const uniqueRepackers = useMemo(() => { + return Array.from(new Set(repacks.map((repack) => repack.repacker))); + }, [repacks]); + + return ( + + ); +} diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx new file mode 100644 index 000000000..05ac8c4d8 --- /dev/null +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -0,0 +1,83 @@ +import { Button } from "@renderer/components/button/button"; +import { ChevronLeftIcon, ChevronRightIcon } from "@primer/octicons-react"; + +interface PaginationProps { + page: number; + totalPages: number; + onPageChange: (page: number) => void; +} + +export function Pagination({ + page, + totalPages, + onPageChange, +}: PaginationProps) { + if (totalPages <= 1) return null; + + // Number of visible pages + const visiblePages = 3; + + // Calculate the start and end of the visible range + let startPage = Math.max(1, page - 1); // Shift range slightly back + let endPage = startPage + visiblePages - 1; + + // Adjust the range if we're near the start or end + if (endPage > totalPages) { + endPage = totalPages; + startPage = Math.max(1, endPage - visiblePages + 1); + } + + return ( +
    + {/* Previous Button */} + + +
    + ... +
    + + {/* Page Buttons */} + {Array.from( + { length: endPage - startPage + 1 }, + (_, i) => startPage + i + ).map((pageNumber) => ( + + ))} + + {/* Next Button */} + +
    + ); +} diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx index be5cfd4d7..b4665f15c 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx @@ -14,7 +14,7 @@ import { TrashIcon, UploadIcon, } from "@primer/octicons-react"; -import { useToast } from "@renderer/hooks"; +import { useAppSelector, useToast } from "@renderer/hooks"; import { useTranslation } from "react-i18next"; import { AxiosProgressEvent } from "axios"; import { formatDownloadProgress } from "@renderer/helpers"; @@ -145,6 +145,9 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { const disableActions = uploadingBackup || restoringBackup || deletingArtifact; + const userDetails = useAppSelector((state) => state.userDetails.userDetails); + const backupsPerGameLimit = userDetails?.quirks.backupsPerGameLimit ?? 0; + return ( = 2 + artifacts.length >= backupsPerGameLimit } > @@ -199,7 +202,9 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { }} >

    {t("backups")}

    - {artifacts.length} / 2 + + {artifacts.length} / {backupsPerGameLimit} +
    diff --git a/src/renderer/src/pages/settings/settings-download-sources.css.ts b/src/renderer/src/pages/settings/settings-download-sources.css.ts index 0e88631d2..caa93ce82 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.css.ts +++ b/src/renderer/src/pages/settings/settings-download-sources.css.ts @@ -42,3 +42,17 @@ export const downloadSourcesHeader = style({ justifyContent: "space-between", alignItems: "center", }); + +export const navigateToCatalogueButton = style({ + display: "flex", + alignItems: "center", + gap: `${SPACING_UNIT}px`, + color: vars.color.muted, + textDecoration: "underline", + cursor: "pointer", + + ":disabled": { + cursor: "default", + textDecoration: "none", + }, +}); diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index aabbab27f..89900ea77 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -163,14 +163,8 @@ export function SettingsDownloadSources() {
    From 26a35a902ca5589d2c78d553b8e1539270a7cc6b Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 23 Dec 2024 18:15:48 -0300 Subject: [PATCH 54/62] feat: enhance pagination component with first and last page buttons --- .../src/pages/catalogue/pagination.tsx | 71 +++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index c2e510b22..377cbad9a 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -1,5 +1,6 @@ import { Button } from "@renderer/components/button/button"; import { ChevronLeftIcon, ChevronRightIcon } from "@primer/octicons-react"; +import { useFormat } from "@renderer/hooks/use-format"; interface PaginationProps { page: number; @@ -27,6 +28,8 @@ export function Pagination({ startPage = Math.max(1, endPage - visiblePages + 1); } + const { formatNumber } = useFormat(); + return (
    {page > 2 && ( -
    - ... -
    + <> + {/* initial page */} + + + {/* ellipsis */} +
    + ... +
    + )} {/* Page Buttons */} @@ -68,21 +84,34 @@ export function Pagination({ style={{ width: 40, maxWidth: 40, maxHeight: 40 }} onClick={() => onPageChange(pageNumber)} > - {pageNumber} + {formatNumber(pageNumber)} ))} {page < totalPages - 1 && ( -
    - ... -
    + <> + {/* ellipsis */} +
    + ... +
    + + {/* last page */} + + )} {/* Next Button */} From 502f3b4aa7e9d66657b963d10bc90760c92149a3 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 23 Dec 2024 18:19:22 -0300 Subject: [PATCH 55/62] refactor: move formatNumber hook usage to before early return --- src/renderer/src/pages/catalogue/pagination.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index 377cbad9a..3669e902c 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -13,6 +13,8 @@ export function Pagination({ totalPages, onPageChange, }: PaginationProps) { + const { formatNumber } = useFormat(); + if (totalPages <= 1) return null; // Number of visible pages @@ -28,8 +30,6 @@ export function Pagination({ startPage = Math.max(1, endPage - visiblePages + 1); } - const { formatNumber } = useFormat(); - return (
    Date: Mon, 23 Dec 2024 21:37:23 +0000 Subject: [PATCH 56/62] chore: moving tags to redux --- src/renderer/src/features/catalogue-search.ts | 23 +++++++++++++++++-- src/renderer/src/hooks/use-catalogue.ts | 17 +++++++------- .../src/pages/catalogue/catalogue.tsx | 9 +++++--- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/features/catalogue-search.ts b/src/renderer/src/features/catalogue-search.ts index 95fccb294..ad6ae3f34 100644 --- a/src/renderer/src/features/catalogue-search.ts +++ b/src/renderer/src/features/catalogue-search.ts @@ -6,6 +6,8 @@ import type { CatalogueSearchPayload } from "@types"; export interface CatalogueSearchState { filters: CatalogueSearchPayload; page: number; + steamUserTags: Record>; + steamGenres: Record; } const initialState: CatalogueSearchState = { @@ -17,6 +19,8 @@ const initialState: CatalogueSearchState = { genres: [], developers: [], }, + steamUserTags: {}, + steamGenres: {}, page: 1, }; @@ -41,8 +45,23 @@ export const catalogueSearchSlice = createSlice({ clearPage: (state) => { state.page = initialState.page; }, + setTags: ( + state, + action: PayloadAction>> + ) => { + state.steamUserTags = action.payload; + }, + setGenres: (state, action: PayloadAction>) => { + state.steamGenres = action.payload; + }, }, }); -export const { setFilters, clearFilters, setPage, clearPage } = - catalogueSearchSlice.actions; +export const { + setFilters, + clearFilters, + setPage, + clearPage, + setTags, + setGenres, +} = catalogueSearchSlice.actions; diff --git a/src/renderer/src/hooks/use-catalogue.ts b/src/renderer/src/hooks/use-catalogue.ts index 9c774e831..1d0aeb575 100644 --- a/src/renderer/src/hooks/use-catalogue.ts +++ b/src/renderer/src/hooks/use-catalogue.ts @@ -1,30 +1,29 @@ import axios from "axios"; import { useCallback, useEffect, useState } from "react"; +import { useAppDispatch } from "./redux"; +import { setGenres, setTags } from "@renderer/features"; export const externalResourcesInstance = axios.create({ baseURL: import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL, }); export function useCatalogue() { - const [steamGenres, setSteamGenres] = useState>({}); - const [steamUserTags, setSteamUserTags] = useState< - Record> - >({}); + const dispatch = useAppDispatch(); const [steamPublishers, setSteamPublishers] = useState([]); const [steamDevelopers, setSteamDevelopers] = useState([]); const getSteamUserTags = useCallback(() => { externalResourcesInstance.get("/steam-user-tags.json").then((response) => { - setSteamUserTags(response.data); + dispatch(setTags(response.data)); }); - }, []); + }, [dispatch]); const getSteamGenres = useCallback(() => { externalResourcesInstance.get("/steam-genres.json").then((response) => { - setSteamGenres(response.data); + dispatch(setGenres(response.data)); }); - }, []); + }, [dispatch]); const getSteamPublishers = useCallback(() => { externalResourcesInstance.get("/steam-publishers.json").then((response) => { @@ -50,5 +49,5 @@ export function useCatalogue() { getSteamDevelopers, ]); - return { steamGenres, steamUserTags, steamPublishers, steamDevelopers }; + return { steamPublishers, steamDevelopers }; } diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index eb51f856e..d54eb28a9 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -34,8 +34,11 @@ const PAGE_SIZE = 20; export default function Catalogue() { const abortControllerRef = useRef(null); - const { steamGenres, steamUserTags, steamDevelopers, steamPublishers } = - useCatalogue(); + const { steamDevelopers, steamPublishers } = useCatalogue(); + + const { steamGenres, steamUserTags } = useAppSelector( + (state) => state.catalogueSearch + ); const [downloadSources, setDownloadSources] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -128,7 +131,7 @@ export default function Catalogue() { ...filters.tags.map((tag) => ({ label: Object.keys(steamUserTags[language]).find( (key) => steamUserTags[language][key] === tag - ) as string, + ), orbColor: filterCategoryColors.tags, key: "tags", value: tag, From 4aa7df65f5684b8d4c572839bcb5099c617f0944 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 21:38:17 +0000 Subject: [PATCH 57/62] chore: moving tags to redux --- src/renderer/src/pages/catalogue/catalogue.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index d54eb28a9..523d7c520 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -240,7 +240,7 @@ export default function Catalogue() { {groupedFilters.map((filter) => (
  • { dispatch( From a499aae31ffce97f0cf1ac992c50dd34407e9d26 Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Mon, 23 Dec 2024 18:49:18 -0300 Subject: [PATCH 58/62] feat: smooth scrolling on pagination --- src/renderer/src/pages/catalogue/catalogue.scss | 2 ++ src/renderer/src/pages/catalogue/catalogue.tsx | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/catalogue/catalogue.scss b/src/renderer/src/pages/catalogue/catalogue.scss index 18cc18bea..701652df6 100644 --- a/src/renderer/src/pages/catalogue/catalogue.scss +++ b/src/renderer/src/pages/catalogue/catalogue.scss @@ -1,11 +1,13 @@ @use "../../scss/globals.scss"; .catalogue { + overflow-y: auto; display: flex; flex-direction: column; gap: calc(globals.$spacing-unit * 2); width: 100%; padding: 16px; + scroll-behavior: smooth; &__filters-container { width: 270px; diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index eb51f856e..cdc212acf 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -33,6 +33,7 @@ const PAGE_SIZE = 20; export default function Catalogue() { const abortControllerRef = useRef(null); + const cataloguePageRef = useRef(null); const { steamGenres, steamUserTags, steamDevelopers, steamPublishers } = useCatalogue(); @@ -214,7 +215,7 @@ export default function Catalogue() { ]); return ( -
    +
    dispatch(setPage(page))} + onPageChange={(page) => { + dispatch(setPage(page)); + if (cataloguePageRef.current) { + cataloguePageRef.current.scrollTop = 0; + } + }} />
    From d072a14aa349f49b9fbc198d1fdd3f56d8e2c003 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 21:52:01 +0000 Subject: [PATCH 59/62] feat: adding translation for catalogue --- src/locales/en/translation.json | 11 +++++++++-- src/locales/es/translation.json | 11 +++++++++-- src/locales/pt-BR/translation.json | 10 +++++++--- src/locales/ru/translation.json | 11 +++++++++-- src/renderer/src/pages/catalogue/catalogue.tsx | 4 +++- src/renderer/src/pages/catalogue/filter-section.tsx | 12 +++++++----- 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index c0d2d5452..9492871da 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -46,8 +46,15 @@ "checking_files": "Checking {{title}} files… ({{percentage}} complete)" }, "catalogue": { - "next_page": "Next page", - "previous_page": "Previous page" + "search": "Filter…", + "developers": "Developers", + "genres": "Genres", + "tags": "Tags", + "publishers": "Publishers", + "download_sources": "Download sources", + "result_count": "{{count}} results", + "filter_count": "{{count}} available", + "clear_filters": "Clear {{count}} selected" }, "game_details": { "open_download_options": "Open download options", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index a9261c9d6..f63f4f2d3 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -46,8 +46,15 @@ "checking_files": "Verificando archivos de {{title}}… ({{percentage}} completado)" }, "catalogue": { - "next_page": "Siguiente página", - "previous_page": "Pagina anterior" + "search": "Filtrar…", + "developers": "Desarrolladores", + "genres": "Géneros", + "tags": "Marcadores", + "publishers": "Distribuidoras", + "download_sources": "Fuentes de descarga", + "result_count": "{{count}} resultados", + "filter_count": "{{count}} disponibles", + "clear_filters": "Limpiar {{count}} seleccionados" }, "game_details": { "open_download_options": "Ver opciones de descargas", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index de33fd142..f53036610 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -284,11 +284,15 @@ "instructions": "Verifique a forma correta de instalar algum deles no seu distro Linux, garantindo assim a execução normal do jogo" }, "catalogue": { - "search": "Pesquisar…", + "search": "Filtrar…", "developers": "Desenvolvedores", "genres": "Gêneros", - "tags": "Tags", - "download_sources": "Fontes de download" + "tags": "Marcadores", + "publishers": "Distribuidoras", + "download_sources": "Fontes de download", + "result_count": "{{count}} resultados", + "filter_count": "{{count}} disponíveis", + "clear_filters": "Limpar {{count}} selecionados" }, "modal": { "close": "Botão de fechar" diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 81b5c8908..4abb4321b 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -46,8 +46,15 @@ "checking_files": "Проверка файлов {{title}}… ({{percentage}} завершено)" }, "catalogue": { - "next_page": "Следующая страница", - "previous_page": "Предыдущая страница" + "search": "Фильтр…", + "developers": "Разработчики", + "genres": "Жанры", + "tags": "Маркеры", + "publishers": "Издательства", + "download_sources": "Источники загрузки", + "result_count": "{{count}} результатов", + "filter_count": "{{count}} доступных", + "clear_filters": "Очистить {{count}} выбранных" }, "game_details": { "open_download_options": "Открыть источники", diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 523d7c520..5faf581cd 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -301,7 +301,9 @@ export default function Catalogue() { marginTop: 16, }} > - {formatNumber(itemsCount)} resultados + + {t("result_count", { count: formatNumber(itemsCount) })} + { if (search.length > 0) { @@ -64,7 +67,6 @@ export function FilterSection({ style={{ fontSize: 16, fontWeight: 500, - color: "#fff", }} > {title} @@ -78,22 +80,22 @@ export function FilterSection({ fontSize: 12, marginBottom: 12, display: "block", - color: "#fff", + color: vars.color.body, cursor: "pointer", textDecoration: "underline", }} onClick={onClear} > - Limpar {formatNumber(selectedItemsCount)} selecionados + {t("clear_filters", { count: formatNumber(selectedItemsCount) })} ) : ( - {formatNumber(items.length)} disponíveis + {t("filter_count", { count: formatNumber(items.length) })} )} onSearch(e.target.value)} value={search} containerProps={{ style: { marginBottom: 16 } }} From 7d277e01333fbbfc1b365827523e021860dd30bd Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 21:54:01 +0000 Subject: [PATCH 60/62] feat: adding translation for catalogue --- src/locales/en/translation.json | 6 +++--- src/locales/es/translation.json | 6 +++--- src/locales/pt-BR/translation.json | 6 +++--- src/locales/ru/translation.json | 6 +++--- src/renderer/src/pages/catalogue/catalogue.tsx | 4 +++- src/renderer/src/pages/catalogue/filter-section.tsx | 8 ++++++-- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 9492871da..f9a683bf3 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -52,9 +52,9 @@ "tags": "Tags", "publishers": "Publishers", "download_sources": "Download sources", - "result_count": "{{count}} results", - "filter_count": "{{count}} available", - "clear_filters": "Clear {{count}} selected" + "result_count": "{{resultCount}} results", + "filter_count": "{{filterCount}} available", + "clear_filters": "Clear {{filterCount}} selected" }, "game_details": { "open_download_options": "Open download options", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index f63f4f2d3..b3dd42f8c 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -52,9 +52,9 @@ "tags": "Marcadores", "publishers": "Distribuidoras", "download_sources": "Fuentes de descarga", - "result_count": "{{count}} resultados", - "filter_count": "{{count}} disponibles", - "clear_filters": "Limpiar {{count}} seleccionados" + "result_count": "{{resultCount}} resultados", + "filter_count": "{{filterCount}} disponibles", + "clear_filters": "Limpiar {{filterCount}} seleccionados" }, "game_details": { "open_download_options": "Ver opciones de descargas", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index f53036610..46f7e70f7 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -290,9 +290,9 @@ "tags": "Marcadores", "publishers": "Distribuidoras", "download_sources": "Fontes de download", - "result_count": "{{count}} resultados", - "filter_count": "{{count}} disponíveis", - "clear_filters": "Limpar {{count}} selecionados" + "result_count": "{{resultCount}} resultados", + "filter_count": "{{filterCount}} disponíveis", + "clear_filters": "Limpar {{filterCount}} selecionados" }, "modal": { "close": "Botão de fechar" diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 4abb4321b..741f5e16f 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -52,9 +52,9 @@ "tags": "Маркеры", "publishers": "Издательства", "download_sources": "Источники загрузки", - "result_count": "{{count}} результатов", - "filter_count": "{{count}} доступных", - "clear_filters": "Очистить {{count}} выбранных" + "result_count": "{{resultCount}} результатов", + "filter_count": "{{filterCount}} доступных", + "clear_filters": "Очистить {{filterCount}} выбранных" }, "game_details": { "open_download_options": "Открыть источники", diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 5faf581cd..93779cf94 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -302,7 +302,9 @@ export default function Catalogue() { }} > - {t("result_count", { count: formatNumber(itemsCount) })} + {t("result_count", { + resultCount: formatNumber(itemsCount), + })} - {t("clear_filters", { count: formatNumber(selectedItemsCount) })} + {t("clear_filters", { + filterCount: formatNumber(selectedItemsCount), + })} ) : ( - {t("filter_count", { count: formatNumber(items.length) })} + {t("filter_count", { + filterCount: formatNumber(items.length), + })} )} From a06333f71697c7324ea9114e46b59d5596b2373f Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 23 Dec 2024 22:45:40 +0000 Subject: [PATCH 61/62] feat: genre translation --- .../src/pages/catalogue/game-item.tsx | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/catalogue/game-item.tsx b/src/renderer/src/pages/catalogue/game-item.tsx index 06f309987..22d493f12 100644 --- a/src/renderer/src/pages/catalogue/game-item.tsx +++ b/src/renderer/src/pages/catalogue/game-item.tsx @@ -1,11 +1,12 @@ import { Badge } from "@renderer/components"; import { buildGameDetailsPath } from "@renderer/helpers"; -import { useRepacks } from "@renderer/hooks"; +import { useAppSelector, useRepacks } from "@renderer/hooks"; import { steamUrlBuilder } from "@shared"; import { useMemo } from "react"; import { useNavigate } from "react-router-dom"; import "./game-item.scss"; +import { useTranslation } from "react-i18next"; export interface GameItemProps { game: any; @@ -14,14 +15,36 @@ export interface GameItemProps { export function GameItem({ game }: GameItemProps) { const navigate = useNavigate(); + const { i18n } = useTranslation(); + + const { steamGenres } = useAppSelector((state) => state.catalogueSearch); + const { getRepacksForObjectId } = useRepacks(); const repacks = getRepacksForObjectId(game.objectId); + const language = i18n.language.split("-")[0]; + const uniqueRepackers = useMemo(() => { return Array.from(new Set(repacks.map((repack) => repack.repacker))); }, [repacks]); + const genres = useMemo(() => { + return game.genres?.map((genre) => { + const index = steamGenres["en"].findIndex( + (steamGenre) => steamGenre === genre + ); + + if (steamGenres[language] && steamGenres[language][index]) { + return steamGenres[language][index]; + } + + return genre; + }); + }, [game.genres, language, steamGenres]); + + console.log(game.genres); + return (