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)"
},
"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": "{{resultCount}} results",
"filter_count": "{{filterCount}} available",
"clear_filters": "Clear {{filterCount}} selected"
},
"game_details": {
"open_download_options": "Open download options",

View file

@ -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": "{{resultCount}} resultados",
"filter_count": "{{filterCount}} disponibles",
"clear_filters": "Limpiar {{filterCount}} seleccionados"
},
"game_details": {
"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"
},
"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": "{{resultCount}} resultados",
"filter_count": "{{filterCount}} disponíveis",
"clear_filters": "Limpar {{filterCount}} selecionados"
},
"modal": {
"close": "Botão de fechar"

View file

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

View file

@ -6,6 +6,8 @@ import type { CatalogueSearchPayload } from "@types";
export interface CatalogueSearchState {
filters: CatalogueSearchPayload;
page: number;
steamUserTags: Record<string, Record<string, number>>;
steamGenres: Record<string, string[]>;
}
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<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 } =
catalogueSearchSlice.actions;
export const {
setFilters,
clearFilters,
setPage,
clearPage,
setTags,
setGenres,
} = catalogueSearchSlice.actions;

View file

@ -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<Record<string, string[]>>({});
const [steamUserTags, setSteamUserTags] = useState<
Record<string, Record<string, number>>
>({});
const dispatch = useAppDispatch();
const [steamPublishers, setSteamPublishers] = useState<string[]>([]);
const [steamDevelopers, setSteamDevelopers] = useState<string[]>([]);
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 };
}

View file

@ -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;

View file

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

View file

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