mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Merge branch 'feat/new-catalogue' into feat/achievements-points
This commit is contained in:
commit
e53f4808d5
9 changed files with 96 additions and 32 deletions
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": "Открыть источники",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 } }}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue