Merge branch 'feat/new-catalogue' into feat/achievements-points

This commit is contained in:
Zamitto 2024-12-23 18:55:26 -03:00
commit e53f4808d5
9 changed files with 96 additions and 32 deletions

View file

@ -46,8 +46,15 @@
"checking_files": "Checking {{title}} files… ({{percentage}} complete)" "checking_files": "Checking {{title}} files… ({{percentage}} complete)"
}, },
"catalogue": { "catalogue": {
"next_page": "Next page", "search": "Filter…",
"previous_page": "Previous page" "developers": "Developers",
"genres": "Genres",
"tags": "Tags",
"publishers": "Publishers",
"download_sources": "Download sources",
"result_count": "{{resultCount}} results",
"filter_count": "{{filterCount}} available",
"clear_filters": "Clear {{filterCount}} selected"
}, },
"game_details": { "game_details": {
"open_download_options": "Open download options", "open_download_options": "Open download options",

View file

@ -46,8 +46,15 @@
"checking_files": "Verificando archivos de {{title}}… ({{percentage}} completado)" "checking_files": "Verificando archivos de {{title}}… ({{percentage}} completado)"
}, },
"catalogue": { "catalogue": {
"next_page": "Siguiente página", "search": "Filtrar…",
"previous_page": "Pagina anterior" "developers": "Desarrolladores",
"genres": "Géneros",
"tags": "Marcadores",
"publishers": "Distribuidoras",
"download_sources": "Fuentes de descarga",
"result_count": "{{resultCount}} resultados",
"filter_count": "{{filterCount}} disponibles",
"clear_filters": "Limpiar {{filterCount}} seleccionados"
}, },
"game_details": { "game_details": {
"open_download_options": "Ver opciones de descargas", "open_download_options": "Ver opciones de descargas",

View file

@ -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" "instructions": "Verifique a forma correta de instalar algum deles no seu distro Linux, garantindo assim a execução normal do jogo"
}, },
"catalogue": { "catalogue": {
"search": "Pesquisar…", "search": "Filtrar…",
"developers": "Desenvolvedores", "developers": "Desenvolvedores",
"genres": "Gêneros", "genres": "Gêneros",
"tags": "Tags", "tags": "Marcadores",
"download_sources": "Fontes de download" "publishers": "Distribuidoras",
"download_sources": "Fontes de download",
"result_count": "{{resultCount}} resultados",
"filter_count": "{{filterCount}} disponíveis",
"clear_filters": "Limpar {{filterCount}} selecionados"
}, },
"modal": { "modal": {
"close": "Botão de fechar" "close": "Botão de fechar"

View file

@ -46,8 +46,15 @@
"checking_files": "Проверка файлов {{title}}… ({{percentage}} завершено)" "checking_files": "Проверка файлов {{title}}… ({{percentage}} завершено)"
}, },
"catalogue": { "catalogue": {
"next_page": "Следующая страница", "search": "Фильтр…",
"previous_page": "Предыдущая страница" "developers": "Разработчики",
"genres": "Жанры",
"tags": "Маркеры",
"publishers": "Издательства",
"download_sources": "Источники загрузки",
"result_count": "{{resultCount}} результатов",
"filter_count": "{{filterCount}} доступных",
"clear_filters": "Очистить {{filterCount}} выбранных"
}, },
"game_details": { "game_details": {
"open_download_options": "Открыть источники", "open_download_options": "Открыть источники",

View file

@ -6,6 +6,8 @@ import type { CatalogueSearchPayload } from "@types";
export interface CatalogueSearchState { export interface CatalogueSearchState {
filters: CatalogueSearchPayload; filters: CatalogueSearchPayload;
page: number; page: number;
steamUserTags: Record<string, Record<string, number>>;
steamGenres: Record<string, string[]>;
} }
const initialState: CatalogueSearchState = { const initialState: CatalogueSearchState = {
@ -17,6 +19,8 @@ const initialState: CatalogueSearchState = {
genres: [], genres: [],
developers: [], developers: [],
}, },
steamUserTags: {},
steamGenres: {},
page: 1, page: 1,
}; };
@ -41,8 +45,23 @@ export const catalogueSearchSlice = createSlice({
clearPage: (state) => { clearPage: (state) => {
state.page = initialState.page; state.page = initialState.page;
}, },
setTags: (
state,
action: PayloadAction<Record<string, Record<string, number>>>
) => {
state.steamUserTags = action.payload;
},
setGenres: (state, action: PayloadAction<Record<string, string[]>>) => {
state.steamGenres = action.payload;
},
}, },
}); });
export const { setFilters, clearFilters, setPage, clearPage } = export const {
catalogueSearchSlice.actions; setFilters,
clearFilters,
setPage,
clearPage,
setTags,
setGenres,
} = catalogueSearchSlice.actions;

View file

@ -1,30 +1,29 @@
import axios from "axios"; import axios from "axios";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useAppDispatch } from "./redux";
import { setGenres, setTags } from "@renderer/features";
export const externalResourcesInstance = axios.create({ export const externalResourcesInstance = axios.create({
baseURL: import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL, baseURL: import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL,
}); });
export function useCatalogue() { export function useCatalogue() {
const [steamGenres, setSteamGenres] = useState<Record<string, string[]>>({}); const dispatch = useAppDispatch();
const [steamUserTags, setSteamUserTags] = useState<
Record<string, Record<string, number>>
>({});
const [steamPublishers, setSteamPublishers] = useState<string[]>([]); const [steamPublishers, setSteamPublishers] = useState<string[]>([]);
const [steamDevelopers, setSteamDevelopers] = useState<string[]>([]); const [steamDevelopers, setSteamDevelopers] = useState<string[]>([]);
const getSteamUserTags = useCallback(() => { const getSteamUserTags = useCallback(() => {
externalResourcesInstance.get("/steam-user-tags.json").then((response) => { externalResourcesInstance.get("/steam-user-tags.json").then((response) => {
setSteamUserTags(response.data); dispatch(setTags(response.data));
}); });
}, []); }, [dispatch]);
const getSteamGenres = useCallback(() => { const getSteamGenres = useCallback(() => {
externalResourcesInstance.get("/steam-genres.json").then((response) => { externalResourcesInstance.get("/steam-genres.json").then((response) => {
setSteamGenres(response.data); dispatch(setGenres(response.data));
}); });
}, []); }, [dispatch]);
const getSteamPublishers = useCallback(() => { const getSteamPublishers = useCallback(() => {
externalResourcesInstance.get("/steam-publishers.json").then((response) => { externalResourcesInstance.get("/steam-publishers.json").then((response) => {
@ -50,5 +49,5 @@ export function useCatalogue() {
getSteamDevelopers, getSteamDevelopers,
]); ]);
return { steamGenres, steamUserTags, steamPublishers, steamDevelopers }; return { steamPublishers, steamDevelopers };
} }

View file

@ -1,11 +1,13 @@
@use "../../scss/globals.scss"; @use "../../scss/globals.scss";
.catalogue { .catalogue {
overflow-y: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: calc(globals.$spacing-unit * 2); gap: calc(globals.$spacing-unit * 2);
width: 100%; width: 100%;
padding: 16px; padding: 16px;
scroll-behavior: smooth;
&__filters-container { &__filters-container {
width: 270px; width: 270px;

View file

@ -33,9 +33,13 @@ const PAGE_SIZE = 20;
export default function Catalogue() { export default function Catalogue() {
const abortControllerRef = useRef<AbortController | null>(null); const abortControllerRef = useRef<AbortController | null>(null);
const cataloguePageRef = useRef<HTMLDivElement>(null);
const { steamGenres, steamUserTags, steamDevelopers, steamPublishers } = const { steamDevelopers, steamPublishers } = useCatalogue();
useCatalogue();
const { steamGenres, steamUserTags } = useAppSelector(
(state) => state.catalogueSearch
);
const [downloadSources, setDownloadSources] = useState<DownloadSource[]>([]); const [downloadSources, setDownloadSources] = useState<DownloadSource[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@ -128,7 +132,7 @@ export default function Catalogue() {
...filters.tags.map((tag) => ({ ...filters.tags.map((tag) => ({
label: Object.keys(steamUserTags[language]).find( label: Object.keys(steamUserTags[language]).find(
(key) => steamUserTags[language][key] === tag (key) => steamUserTags[language][key] === tag
) as string, ),
orbColor: filterCategoryColors.tags, orbColor: filterCategoryColors.tags,
key: "tags", key: "tags",
value: tag, value: tag,
@ -214,7 +218,7 @@ export default function Catalogue() {
]); ]);
return ( return (
<div className="catalogue"> <div className="catalogue" ref={cataloguePageRef}>
<div <div
style={{ style={{
display: "flex", display: "flex",
@ -237,7 +241,7 @@ export default function Catalogue() {
{groupedFilters.map((filter) => ( {groupedFilters.map((filter) => (
<li key={`${filter.key}-${filter.value}`}> <li key={`${filter.key}-${filter.value}`}>
<FilterItem <FilterItem
filter={filter.label} filter={filter.label ?? ""}
orbColor={filter.orbColor} orbColor={filter.orbColor}
onRemove={() => { onRemove={() => {
dispatch( dispatch(
@ -298,12 +302,21 @@ export default function Catalogue() {
marginTop: 16, marginTop: 16,
}} }}
> >
<span>{formatNumber(itemsCount)} resultados</span> <span style={{ fontSize: 12 }}>
{t("result_count", {
resultCount: formatNumber(itemsCount),
})}
</span>
<Pagination <Pagination
page={page} page={page}
totalPages={Math.ceil(itemsCount / PAGE_SIZE)} totalPages={Math.ceil(itemsCount / PAGE_SIZE)}
onPageChange={(page) => dispatch(setPage(page))} onPageChange={(page) => {
dispatch(setPage(page));
if (cataloguePageRef.current) {
cataloguePageRef.current.scrollTop = 0;
}
}}
/> />
</div> </div>
</div> </div>

View file

@ -3,6 +3,8 @@ import { useFormat } from "@renderer/hooks";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import List from "rc-virtual-list"; import List from "rc-virtual-list";
import { vars } from "@renderer/theme.css";
import { useTranslation } from "react-i18next";
export interface FilterSectionProps { export interface FilterSectionProps {
title: string; title: string;
@ -24,6 +26,7 @@ export function FilterSection({
onClear, onClear,
}: FilterSectionProps) { }: FilterSectionProps) {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const { t } = useTranslation("catalogue");
const filteredItems = useMemo(() => { const filteredItems = useMemo(() => {
if (search.length > 0) { if (search.length > 0) {
@ -64,7 +67,6 @@ export function FilterSection({
style={{ style={{
fontSize: 16, fontSize: 16,
fontWeight: 500, fontWeight: 500,
color: "#fff",
}} }}
> >
{title} {title}
@ -78,22 +80,26 @@ export function FilterSection({
fontSize: 12, fontSize: 12,
marginBottom: 12, marginBottom: 12,
display: "block", display: "block",
color: "#fff", color: vars.color.body,
cursor: "pointer", cursor: "pointer",
textDecoration: "underline", textDecoration: "underline",
}} }}
onClick={onClear} onClick={onClear}
> >
Limpar {formatNumber(selectedItemsCount)} selecionados {t("clear_filters", {
filterCount: formatNumber(selectedItemsCount),
})}
</button> </button>
) : ( ) : (
<span style={{ fontSize: 12, marginBottom: 12, display: "block" }}> <span style={{ fontSize: 12, marginBottom: 12, display: "block" }}>
{formatNumber(items.length)} disponíveis {t("filter_count", {
filterCount: formatNumber(items.length),
})}
</span> </span>
)} )}
<TextField <TextField
placeholder="Search..." placeholder={t("search")}
onChange={(e) => onSearch(e.target.value)} onChange={(e) => onSearch(e.target.value)}
value={search} value={search}
containerProps={{ style: { marginBottom: 16 } }} containerProps={{ style: { marginBottom: 16 } }}