From 2e82c29f4c76e8c4dd818f9d83149465a82617b7 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:12:36 -0300 Subject: [PATCH 1/6] feat: searching from api --- src/main/events/catalogue/get-random-game.ts | 8 +-- src/main/events/catalogue/search-games.ts | 18 +++++- src/main/events/helpers/search-games.ts | 29 ++++----- src/main/services/hydra-api.ts | 60 ++++++++++++++----- src/main/services/repacks-manager.ts | 5 ++ src/main/workers/steam-games.worker.ts | 23 +------ .../src/pages/home/search-results.tsx | 2 +- 7 files changed, 83 insertions(+), 62 deletions(-) diff --git a/src/main/events/catalogue/get-random-game.ts b/src/main/events/catalogue/get-random-game.ts index 69f57800..0f0341b4 100644 --- a/src/main/events/catalogue/get-random-game.ts +++ b/src/main/events/catalogue/get-random-game.ts @@ -3,7 +3,7 @@ import { shuffle } from "lodash-es"; import { getSteam250List } from "@main/services"; import { registerEvent } from "../register-event"; -import { searchSteamGames } from "../helpers/search-games"; +import { getSteamGameById } from "../helpers/search-games"; import type { Steam250Game } from "@types"; const state = { games: Array(), index: 0 }; @@ -12,11 +12,9 @@ const filterGames = async (games: Steam250Game[]) => { const results: Steam250Game[] = []; for (const game of games) { - const catalogue = await searchSteamGames({ query: game.title }); - - if (catalogue.length) { - const [steamGame] = catalogue; + const steamGame = await getSteamGameById(game.objectID); + if (steamGame) { if (steamGame.repacks.length) { results.push(game); } diff --git a/src/main/events/catalogue/search-games.ts b/src/main/events/catalogue/search-games.ts index ec397599..504b0209 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -1,10 +1,24 @@ import { registerEvent } from "../register-event"; -import { searchSteamGames } from "../helpers/search-games"; +import { getSteamGameById } from "../helpers/search-games"; import { CatalogueEntry } from "@types"; +import { HydraApi } from "@main/services"; const searchGamesEvent = async ( _event: Electron.IpcMainInvokeEvent, query: string -): Promise => searchSteamGames({ query, limit: 12 }); +): Promise => { + const games = await HydraApi.get< + { objectId: string; title: string; shop: string }[] + >("/games/search", { title: query, take: 12, skip: 0 }, { needsAuth: false }); + + const steamGames = await Promise.all( + games.map((game) => getSteamGameById(game.objectId)) + ); + const filteredGames = steamGames.filter( + (game) => game !== null + ) as CatalogueEntry[]; + + return filteredGames; +}; registerEvent("searchGames", searchGamesEvent); diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index 74be1f07..c5878dcb 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -1,6 +1,3 @@ -import { orderBy } from "lodash-es"; -import flexSearch from "flexsearch"; - import type { GameShop, CatalogueEntry, SteamGame } from "@types"; import { getSteamAppAsset } from "@main/helpers"; @@ -23,20 +20,18 @@ export const convertSteamGameToCatalogueEntry = ( repacks: [], }); -export const searchSteamGames = async ( - options: flexSearch.SearchOptions -): Promise => { - const steamGames = (await steamGamesWorker.run(options, { - name: "search", - })) as SteamGame[]; +export const getSteamGameById = async ( + objectId: string +): Promise => { + const steamGame = await steamGamesWorker.run(Number(objectId), { + name: "getById", + }); - const result = RepacksManager.findRepacksForCatalogueEntries( - steamGames.map((game) => convertSteamGameToCatalogueEntry(game)) - ); + if (!steamGame) return null; - return orderBy( - result, - [({ repacks }) => repacks.length, "repacks"], - ["desc"] - ); + const catalogueEntry = convertSteamGameToCatalogueEntry(steamGame); + + const result = RepacksManager.findRepacksForCatalogueEntry(catalogueEntry); + + return result; }; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 120d27ac..bbf390d0 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -7,6 +7,10 @@ import { clearGamesRemoteIds } from "./library-sync/clear-games-remote-id"; import { logger } from "./logger"; import { UserNotLoggedInError } from "@shared"; +interface HydraApiOptions { + needsAuth: boolean; +} + export class HydraApi { private static instance: AxiosInstance; @@ -204,50 +208,76 @@ export class HydraApi { throw err; }; - static async get(url: string, params?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async get( + url: string, + params?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .get(url, { params, ...this.getAxiosConfig() }) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async post(url: string, data?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async post( + url: string, + data?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .post(url, data, this.getAxiosConfig()) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async put(url: string, data?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async put( + url: string, + data?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .put(url, data, this.getAxiosConfig()) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async patch(url: string, data?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async patch( + url: string, + data?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .patch(url, data, this.getAxiosConfig()) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async delete(url: string) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async delete(url: string, options?: HydraApiOptions) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .delete(url, this.getAxiosConfig()) .then((response) => response.data) diff --git a/src/main/services/repacks-manager.ts b/src/main/services/repacks-manager.ts index 93157d6c..933d7431 100644 --- a/src/main/services/repacks-manager.ts +++ b/src/main/services/repacks-manager.ts @@ -49,6 +49,11 @@ export class RepacksManager { .map((index) => this.repacks[index]); } + public static findRepacksForCatalogueEntry(entry: CatalogueEntry) { + const repacks = this.search({ query: formatName(entry.title) }); + return { ...entry, repacks }; + } + public static findRepacksForCatalogueEntries(entries: CatalogueEntry[]) { return entries.map((entry) => { const repacks = this.search({ query: formatName(entry.title) }); diff --git a/src/main/workers/steam-games.worker.ts b/src/main/workers/steam-games.worker.ts index ad399943..9085082b 100644 --- a/src/main/workers/steam-games.worker.ts +++ b/src/main/workers/steam-games.worker.ts @@ -1,36 +1,15 @@ import { SteamGame } from "@types"; -import { orderBy, slice } from "lodash-es"; -import flexSearch from "flexsearch"; +import { slice } from "lodash-es"; import fs from "node:fs"; -import { formatName } from "@shared"; import { workerData } from "node:worker_threads"; -const steamGamesIndex = new flexSearch.Index({ - tokenize: "reverse", -}); - const { steamGamesPath } = workerData; const data = fs.readFileSync(steamGamesPath, "utf-8"); const steamGames = JSON.parse(data) as SteamGame[]; -for (let i = 0; i < steamGames.length; i++) { - const steamGame = steamGames[i]; - - const formattedName = formatName(steamGame.name); - - steamGamesIndex.add(i, formattedName); -} - -export const search = (options: flexSearch.SearchOptions) => { - const results = steamGamesIndex.search(options); - const games = results.map((index) => steamGames[index]); - - return orderBy(games, ["name"], ["asc"]); -}; - export const getById = (id: number) => steamGames.find((game) => game.id === id); diff --git a/src/renderer/src/pages/home/search-results.tsx b/src/renderer/src/pages/home/search-results.tsx index 30b3ea68..cc7a2a97 100644 --- a/src/renderer/src/pages/home/search-results.tsx +++ b/src/renderer/src/pages/home/search-results.tsx @@ -48,7 +48,7 @@ export function SearchResults() { .finally(() => { setIsLoading(false); }); - }, 300); + }, 500); debouncedFunc.current(); }, [searchParams, dispatch]); From b8c8e534b4631f1a3724b3ba84b7b650ae194e28 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:35:14 -0300 Subject: [PATCH 2/6] feat: refactor --- src/main/events/catalogue/search-games.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/events/catalogue/search-games.ts b/src/main/events/catalogue/search-games.ts index 504b0209..ebe601f2 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -1,7 +1,7 @@ import { registerEvent } from "../register-event"; -import { getSteamGameById } from "../helpers/search-games"; +import { convertSteamGameToCatalogueEntry } from "../helpers/search-games"; import { CatalogueEntry } from "@types"; -import { HydraApi } from "@main/services"; +import { HydraApi, RepacksManager } from "@main/services"; const searchGamesEvent = async ( _event: Electron.IpcMainInvokeEvent, @@ -11,14 +11,15 @@ const searchGamesEvent = async ( { objectId: string; title: string; shop: string }[] >("/games/search", { title: query, take: 12, skip: 0 }, { needsAuth: false }); - const steamGames = await Promise.all( - games.map((game) => getSteamGameById(game.objectId)) - ); - const filteredGames = steamGames.filter( - (game) => game !== null - ) as CatalogueEntry[]; + const steamGames = games.map((game) => { + return convertSteamGameToCatalogueEntry({ + id: Number(game.objectId), + name: game.title, + clientIcon: null, + }); + }); - return filteredGames; + return RepacksManager.findRepacksForCatalogueEntries(steamGames); }; registerEvent("searchGames", searchGamesEvent); From 5c363810c8ccdbf2db442104dfd32929e41c021e Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:43:09 -0300 Subject: [PATCH 3/6] feat: search updates --- .../src/pages/home/search-results.tsx | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/pages/home/search-results.tsx b/src/renderer/src/pages/home/search-results.tsx index cc7a2a97..aedab79b 100644 --- a/src/renderer/src/pages/home/search-results.tsx +++ b/src/renderer/src/pages/home/search-results.tsx @@ -6,7 +6,7 @@ import type { CatalogueEntry } from "@types"; import type { DebouncedFunc } from "lodash"; import { debounce } from "lodash"; -import { InboxIcon } from "@primer/octicons-react"; +import { InboxIcon, SearchIcon } from "@primer/octicons-react"; import { clearSearch } from "@renderer/features"; import { useAppDispatch } from "@renderer/hooks"; import { useEffect, useRef, useState } from "react"; @@ -25,6 +25,7 @@ export function SearchResults() { const [searchResults, setSearchResults] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [showMessage, setShowMessage] = useState(false); const debouncedFunc = useRef void> | null>(null); @@ -35,19 +36,40 @@ export function SearchResults() { navigate(buildGameDetailsPath(game)); }; + const fetchGames = (query: string) => { + window.electron + .searchGames(query) + .then((results) => { + console.log( + "original query: ", + query, + "current value: ", + searchParams.get("query") + ); + if (query != searchParams.get("query")) return; + setSearchResults(results); + }) + .finally(() => { + setIsLoading(false); + }); + }; + useEffect(() => { setIsLoading(true); if (debouncedFunc.current) debouncedFunc.current.cancel(); debouncedFunc.current = debounce(() => { - window.electron - .searchGames(searchParams.get("query") ?? "") - .then((results) => { - setSearchResults(results); - }) - .finally(() => { - setIsLoading(false); - }); + const query = searchParams.get("query") ?? ""; + + if (query.length < 3) { + setIsLoading(false); + setShowMessage(true); + setSearchResults([]); + return; + } + + setShowMessage(false); + fetchGames(query); }, 500); debouncedFunc.current(); @@ -75,6 +97,14 @@ export function SearchResults() { )} + {!isLoading && showMessage && ( +
+ + +

{"Comece a digitar para pesquisar…"}

+
+ )} + {!isLoading && searchResults.length === 0 && (
From 0fce444df8ec0b6a326f940e691119c59a3dd7ff Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:12:42 -0300 Subject: [PATCH 4/6] feat: abort controller and i18n --- src/locales/en/translation.json | 3 +- src/locales/pt-BR/translation.json | 3 +- src/locales/pt-PT/translation.json | 3 +- .../src/pages/home/search-results.tsx | 45 +++++++++---------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index ae9c2712..6979854b 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -7,7 +7,8 @@ "featured": "Featured", "trending": "Trending", "surprise_me": "Surprise me", - "no_results": "No results found" + "no_results": "No results found", + "start_typing": "Starting typing to search..." }, "sidebar": { "catalogue": "Catalogue", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 1adac376..d3b0f0a4 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -7,7 +7,8 @@ "featured": "Destaques", "trending": "Populares", "surprise_me": "Surpreenda-me", - "no_results": "Nenhum resultado encontrado" + "no_results": "Nenhum resultado encontrado", + "start_typing": "Comece a digitar para pesquisar…" }, "sidebar": { "catalogue": "Catálogo", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 67f99921..3384bdf7 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -7,7 +7,8 @@ "featured": "Destaques", "trending": "Populares", "surprise_me": "Surpreende-me", - "no_results": "Nenhum resultado encontrado" + "no_results": "Nenhum resultado encontrado", + "start_typing": "Comece a digitar para pesquisar…" }, "sidebar": { "catalogue": "Catálogo", diff --git a/src/renderer/src/pages/home/search-results.tsx b/src/renderer/src/pages/home/search-results.tsx index aedab79b..abd58535 100644 --- a/src/renderer/src/pages/home/search-results.tsx +++ b/src/renderer/src/pages/home/search-results.tsx @@ -25,9 +25,10 @@ export function SearchResults() { const [searchResults, setSearchResults] = useState([]); const [isLoading, setIsLoading] = useState(false); - const [showMessage, setShowMessage] = useState(false); + const [showTypingMessage, setShowTypingMessage] = useState(false); const debouncedFunc = useRef void> | null>(null); + const abortControllerRef = useRef(null); const navigate = useNavigate(); @@ -36,40 +37,36 @@ export function SearchResults() { navigate(buildGameDetailsPath(game)); }; - const fetchGames = (query: string) => { - window.electron - .searchGames(query) - .then((results) => { - console.log( - "original query: ", - query, - "current value: ", - searchParams.get("query") - ); - if (query != searchParams.get("query")) return; - setSearchResults(results); - }) - .finally(() => { - setIsLoading(false); - }); - }; - 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); - setShowMessage(true); + setShowTypingMessage(true); setSearchResults([]); return; } - setShowMessage(false); - fetchGames(query); + setShowTypingMessage(false); + window.electron + .searchGames(query) + .then((results) => { + if (abortController.signal.aborted) return; + + setSearchResults(results); + setIsLoading(false); + }) + .catch(() => { + setIsLoading(false); + }); }, 500); debouncedFunc.current(); @@ -97,11 +94,11 @@ export function SearchResults() { )} - {!isLoading && showMessage && ( + {!isLoading && showTypingMessage && (
-

{"Comece a digitar para pesquisar…"}

+

{t("start_typing")}

)} From b0164b694848452af415d88915c3a0b915c29b82 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:15:24 -0300 Subject: [PATCH 5/6] feat: simplify conditional --- src/main/events/catalogue/get-random-game.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/events/catalogue/get-random-game.ts b/src/main/events/catalogue/get-random-game.ts index 0f0341b4..72b93c33 100644 --- a/src/main/events/catalogue/get-random-game.ts +++ b/src/main/events/catalogue/get-random-game.ts @@ -14,10 +14,8 @@ const filterGames = async (games: Steam250Game[]) => { for (const game of games) { const steamGame = await getSteamGameById(game.objectID); - if (steamGame) { - if (steamGame.repacks.length) { - results.push(game); - } + if (steamGame?.repacks.length) { + results.push(game); } } From ad3c2df02469173f4149d0b7d9d8992d0eb97ca6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:42:44 -0300 Subject: [PATCH 6/6] feat: refactor --- .../src/pages/home/search-results.tsx | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/pages/home/search-results.tsx b/src/renderer/src/pages/home/search-results.tsx index abd58535..4ca72487 100644 --- a/src/renderer/src/pages/home/search-results.tsx +++ b/src/renderer/src/pages/home/search-results.tsx @@ -72,6 +72,32 @@ export function SearchResults() { 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 (
@@ -94,21 +120,7 @@ export function SearchResults() { )}
- {!isLoading && showTypingMessage && ( -
- - -

{t("start_typing")}

-
- )} - - {!isLoading && searchResults.length === 0 && ( -
- - -

{t("no_results")}

-
- )} + {noResultsContent()}
);