mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: updating play label on hero panel
This commit is contained in:
parent
91b1341271
commit
96e11e6be9
40 changed files with 2049 additions and 745 deletions
File diff suppressed because one or more lines are too long
1
src/renderer/assets/lottie/settings.json
Normal file
1
src/renderer/assets/lottie/settings.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -15,7 +15,8 @@ export interface HeaderProps {
|
|||
}
|
||||
|
||||
const pathTitle: Record<string, string> = {
|
||||
"/": "catalogue",
|
||||
"/": "home",
|
||||
"/catalogue": "catalogue",
|
||||
"/downloads": "downloads",
|
||||
"/settings": "settings",
|
||||
};
|
||||
|
|
|
@ -30,7 +30,7 @@ export const heroMedia = style({
|
|||
transition: "all ease 0.2s",
|
||||
selectors: {
|
||||
[`${hero}:hover &`]: {
|
||||
transform: "scale(1.05)",
|
||||
transform: "scale(1.02)",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
export const downloadIconWrapper = style({
|
||||
width: "16px",
|
||||
height: "12px",
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
export const downloadIcon = style({
|
||||
width: "24px",
|
||||
position: "absolute",
|
||||
left: "-4px",
|
||||
top: "-9px",
|
||||
});
|
|
@ -2,7 +2,6 @@ import { useRef } from "react";
|
|||
import Lottie from "lottie-react";
|
||||
|
||||
import downloadingAnimation from "@renderer/assets/lottie/downloading.json";
|
||||
import * as styles from "./download-icon.css";
|
||||
|
||||
export interface DownloadIconProps {
|
||||
isDownloading: boolean;
|
||||
|
@ -12,15 +11,12 @@ export function DownloadIcon({ isDownloading }: DownloadIconProps) {
|
|||
const lottieRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div className={styles.downloadIconWrapper}>
|
||||
<Lottie
|
||||
lottieRef={lottieRef}
|
||||
animationData={downloadingAnimation}
|
||||
loop={isDownloading}
|
||||
autoplay={isDownloading}
|
||||
className={styles.downloadIcon}
|
||||
onDOMLoaded={() => lottieRef.current?.setSpeed(1.7)}
|
||||
/>
|
||||
</div>
|
||||
<Lottie
|
||||
lottieRef={lottieRef}
|
||||
animationData={downloadingAnimation}
|
||||
loop={isDownloading}
|
||||
autoplay={isDownloading}
|
||||
style={{ width: 16 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { GearIcon, ListUnorderedIcon } from "@primer/octicons-react";
|
||||
import { AppsIcon, GearIcon, HomeIcon } from "@primer/octicons-react";
|
||||
import { DownloadIcon } from "./download-icon";
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
path: "/",
|
||||
nameKey: "home",
|
||||
render: () => <HomeIcon />,
|
||||
},
|
||||
{
|
||||
path: "/catalogue",
|
||||
nameKey: "catalogue",
|
||||
render: () => <ListUnorderedIcon />,
|
||||
render: () => <AppsIcon />,
|
||||
},
|
||||
{
|
||||
path: "/downloads",
|
||||
|
|
4
src/renderer/declaration.d.ts
vendored
4
src/renderer/declaration.d.ts
vendored
|
@ -45,6 +45,10 @@ declare global {
|
|||
shop: GameShop,
|
||||
title: string
|
||||
) => Promise<HowLongToBeatCategory[] | null>;
|
||||
getGames: (
|
||||
take?: number,
|
||||
prevCursor?: number
|
||||
) => Promise<{ results: CatalogueEntry[]; cursor: number }>;
|
||||
|
||||
/* Library */
|
||||
addGameToLibrary: (
|
||||
|
|
|
@ -3,13 +3,11 @@ import type { PayloadAction } from "@reduxjs/toolkit";
|
|||
|
||||
interface WindowState {
|
||||
draggingDisabled: boolean;
|
||||
scrollingDisabled: boolean;
|
||||
headerTitle: string;
|
||||
}
|
||||
|
||||
const initialState: WindowState = {
|
||||
draggingDisabled: false,
|
||||
scrollingDisabled: false,
|
||||
headerTitle: "",
|
||||
};
|
||||
|
||||
|
@ -20,14 +18,10 @@ export const windowSlice = createSlice({
|
|||
toggleDragging: (state, action: PayloadAction<boolean>) => {
|
||||
state.draggingDisabled = action.payload;
|
||||
},
|
||||
toggleScrolling: (state, action: PayloadAction<boolean>) => {
|
||||
state.scrollingDisabled = action.payload;
|
||||
},
|
||||
setHeaderTitle: (state, action: PayloadAction<string>) => {
|
||||
state.headerTitle = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { toggleDragging, toggleScrolling, setHeaderTitle } =
|
||||
windowSlice.actions;
|
||||
export const { toggleDragging, setHeaderTitle } = windowSlice.actions;
|
||||
|
|
|
@ -19,11 +19,12 @@ import "react-loading-skeleton/dist/skeleton.css";
|
|||
|
||||
import { App } from "./app";
|
||||
import {
|
||||
Catalogue,
|
||||
Home,
|
||||
Downloads,
|
||||
GameDetails,
|
||||
SearchResults,
|
||||
Settings,
|
||||
Catalogue,
|
||||
} from "@renderer/pages";
|
||||
|
||||
import { store } from "./store";
|
||||
|
@ -41,6 +42,10 @@ const router = createHashRouter([
|
|||
children: [
|
||||
{
|
||||
path: "/",
|
||||
Component: Home,
|
||||
},
|
||||
{
|
||||
path: "/catalogue",
|
||||
Component: Catalogue,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,141 +1,113 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
|
||||
import { Button, GameCard } from "@renderer/components";
|
||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button, GameCard, Hero } from "@renderer/components";
|
||||
import type { CatalogueCategory, CatalogueEntry } from "@types";
|
||||
import type { CatalogueEntry } from "@types";
|
||||
|
||||
import starsAnimation from "@renderer/assets/lottie/stars.json";
|
||||
|
||||
import * as styles from "./catalogue.css";
|
||||
import { clearSearch } from "@renderer/features";
|
||||
import { useAppDispatch } from "@renderer/hooks";
|
||||
import { vars } from "@renderer/theme.css";
|
||||
import Lottie from "lottie-react";
|
||||
|
||||
const categories: CatalogueCategory[] = ["trending", "recently_added"];
|
||||
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";
|
||||
|
||||
export function Catalogue() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { t } = useTranslation("catalogue");
|
||||
|
||||
const [searchResults, setSearchResults] = useState<CatalogueEntry[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const contentRef = useRef<HTMLElement>(null);
|
||||
|
||||
const cursorRef = useRef<number>(0);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoadingRandomGame, setIsLoadingRandomGame] = useState(false);
|
||||
const randomGameObjectID = useRef<string | null>(null);
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const cursor = Number(searchParams.get("cursor") ?? 0);
|
||||
|
||||
const [catalogue, setCatalogue] = useState<
|
||||
Record<CatalogueCategory, CatalogueEntry[]>
|
||||
>({
|
||||
trending: [],
|
||||
recently_added: [],
|
||||
});
|
||||
|
||||
const getCatalogue = useCallback((category: CatalogueCategory) => {
|
||||
setIsLoading(true);
|
||||
|
||||
window.electron
|
||||
.getCatalogue(category)
|
||||
.then((catalogue) => {
|
||||
setCatalogue((prev) => ({ ...prev, [category]: catalogue }));
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const currentCategory = searchParams.get("category") || categories[0];
|
||||
|
||||
const handleSelectCategory = (category: CatalogueCategory) => {
|
||||
if (category !== currentCategory) {
|
||||
getCatalogue(category);
|
||||
navigate(`/?category=${category}`, { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
const getRandomGame = useCallback(() => {
|
||||
setIsLoadingRandomGame(true);
|
||||
|
||||
window.electron
|
||||
.getRandomGame()
|
||||
.then((objectID) => {
|
||||
randomGameObjectID.current = objectID;
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingRandomGame(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleRandomizerClick = () => {
|
||||
const searchParams = new URLSearchParams({
|
||||
fromRandomizer: "1",
|
||||
});
|
||||
|
||||
navigate(
|
||||
`/game/steam/${randomGameObjectID.current}?${searchParams.toString()}`
|
||||
);
|
||||
const handleGameClick = (game: CatalogueEntry) => {
|
||||
dispatch(clearSearch());
|
||||
navigate(`/game/${game.shop}/${game.objectID}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) contentRef.current.scrollTop = 0;
|
||||
setIsLoading(true);
|
||||
getCatalogue(currentCategory as CatalogueCategory);
|
||||
getRandomGame();
|
||||
}, [getCatalogue, currentCategory, getRandomGame]);
|
||||
setSearchResults([]);
|
||||
|
||||
window.electron
|
||||
.getGames(24, cursor)
|
||||
.then(({ results, cursor }) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
cursorRef.current = cursor;
|
||||
setSearchResults(results);
|
||||
resolve(null);
|
||||
}, 500);
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [dispatch, cursor, searchParams]);
|
||||
|
||||
const handleNextPage = () => {
|
||||
const params = new URLSearchParams({
|
||||
cursor: cursorRef.current.toString(),
|
||||
});
|
||||
|
||||
navigate(`/catalogue?${params.toString()}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
||||
<section className={styles.content}>
|
||||
<h2>{t("featured")}</h2>
|
||||
<section
|
||||
style={{
|
||||
padding: `16px 32px`,
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
borderBottom: `1px solid ${vars.color.borderColor}`,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() => navigate(-1)}
|
||||
theme="outline"
|
||||
disabled={cursor === 0 || isLoading}
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
{t("previous_page")}
|
||||
</Button>
|
||||
|
||||
<Hero />
|
||||
<Button onClick={handleNextPage} theme="outline" disabled={isLoading}>
|
||||
{t("next_page")}
|
||||
<ArrowRightIcon />
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<section className={styles.catalogueHeader}>
|
||||
<div className={styles.catalogueCategories}>
|
||||
{categories.map((category) => (
|
||||
<Button
|
||||
key={category}
|
||||
theme={currentCategory === category ? "primary" : "outline"}
|
||||
onClick={() => handleSelectCategory(category)}
|
||||
>
|
||||
{t(category)}
|
||||
</Button>
|
||||
<section ref={contentRef} className={styles.content}>
|
||||
<section className={styles.cards}>
|
||||
{isLoading &&
|
||||
Array.from({ length: 12 }).map((_, index) => (
|
||||
<Skeleton key={index} className={styles.cardSkeleton} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleRandomizerClick}
|
||||
theme="outline"
|
||||
disabled={isLoadingRandomGame}
|
||||
>
|
||||
<div style={{ width: 16, height: 16, position: "relative" }}>
|
||||
<Lottie
|
||||
animationData={starsAnimation}
|
||||
style={{ width: 70, position: "absolute", top: -28, left: -27 }}
|
||||
loop
|
||||
/>
|
||||
</div>
|
||||
{t("surprise_me")}
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<h2>{t(currentCategory)}</h2>
|
||||
|
||||
<section className={styles.cards({})}>
|
||||
{isLoading
|
||||
? Array.from({ length: 12 }).map((_, index) => (
|
||||
<Skeleton key={index} className={styles.cardSkeleton} />
|
||||
))
|
||||
: catalogue[currentCategory as CatalogueCategory].map((result) => (
|
||||
{!isLoading && searchResults.length > 0 && (
|
||||
<>
|
||||
{searchResults.map((game) => (
|
||||
<GameCard
|
||||
key={result.objectID}
|
||||
game={result}
|
||||
onClick={() =>
|
||||
navigate(`/game/${result.shop}/${result.objectID}`)
|
||||
}
|
||||
key={game.objectID}
|
||||
game={game}
|
||||
onClick={() => handleGameClick(game)}
|
||||
disabled={!game.repacks.length}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
</section>
|
||||
</SkeletonTheme>
|
||||
|
|
|
@ -217,16 +217,19 @@ export const howLongToBeatCategorySkeleton = style({
|
|||
|
||||
export const randomizerButton = style({
|
||||
animationName: slideIn,
|
||||
animationDuration: "0.4s",
|
||||
animationDuration: "0.2s",
|
||||
position: "fixed",
|
||||
bottom: 26 + 16,
|
||||
/* Bottom panel height + spacing */
|
||||
bottom: `${26 + SPACING_UNIT * 2}px`,
|
||||
/* Scroll bar + spacing */
|
||||
right: `${9 + SPACING_UNIT * 2}px`,
|
||||
boxShadow: "rgba(255, 255, 255, 0.1) 0px 0px 10px 3px",
|
||||
border: `solid 1px ${vars.color.borderColor}`,
|
||||
backgroundColor: vars.color.darkBackground,
|
||||
border: `solid 2px ${vars.color.borderColor}`,
|
||||
backgroundColor: vars.color.background,
|
||||
":hover": {
|
||||
backgroundColor: vars.color.background,
|
||||
boxShadow: "rgba(255, 255, 255, 0.1) 0px 0px 15px 5px",
|
||||
opacity: 1,
|
||||
opacity: "1",
|
||||
},
|
||||
":active": {
|
||||
transform: "scale(0.98)",
|
||||
|
|
|
@ -104,7 +104,9 @@ export function HeroPanel({
|
|||
window.electron
|
||||
.showOpenDialog({
|
||||
properties: ["openFile"],
|
||||
filters: [{ name: "Game executable (.exe)", extensions: ["exe"] }],
|
||||
filters: [
|
||||
{ name: "Game executable (.exe)", extensions: ["exe", "app"] },
|
||||
],
|
||||
})
|
||||
.then(({ filePaths }) => {
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
|
@ -209,11 +211,15 @@ export function HeroPanel({
|
|||
})}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{t("last_time_played", {
|
||||
period: lastTimePlayed,
|
||||
})}
|
||||
</p>
|
||||
{isGamePlaying ? (
|
||||
<p>{t("playing_now")}</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("last_time_played", {
|
||||
period: lastTimePlayed,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
|
||||
export const catalogueCategories = style({
|
||||
display: "flex",
|
||||
|
@ -23,12 +23,4 @@ export const cards = recipe({
|
|||
gap: `${SPACING_UNIT * 2}px`,
|
||||
transition: "all ease 0.2s",
|
||||
},
|
||||
variants: {
|
||||
searching: {
|
||||
true: {
|
||||
pointerEvents: "none",
|
||||
opacity: vars.opacity.disabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,13 +1,12 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||
|
||||
export const catalogueCategories = style({
|
||||
export const homeCategories = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const catalogueHeader = style({
|
||||
export const homeHeader = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
justifyContent: "space-between",
|
||||
|
@ -24,30 +23,20 @@ export const content = style({
|
|||
overflowY: "auto",
|
||||
});
|
||||
|
||||
export const cards = recipe({
|
||||
base: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(1, 1fr)",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
transition: "all ease 0.2s",
|
||||
"@media": {
|
||||
"(min-width: 768px)": {
|
||||
gridTemplateColumns: "repeat(2, 1fr)",
|
||||
},
|
||||
"(min-width: 1250px)": {
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
},
|
||||
"(min-width: 1600px)": {
|
||||
gridTemplateColumns: "repeat(4, 1fr)",
|
||||
},
|
||||
export const cards = style({
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(1, 1fr)",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
transition: "all ease 0.2s",
|
||||
"@media": {
|
||||
"(min-width: 768px)": {
|
||||
gridTemplateColumns: "repeat(2, 1fr)",
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
searching: {
|
||||
true: {
|
||||
pointerEvents: "none",
|
||||
opacity: vars.opacity.disabled,
|
||||
},
|
||||
"(min-width: 1250px)": {
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
},
|
||||
"(min-width: 1600px)": {
|
||||
gridTemplateColumns: "repeat(4, 1fr)",
|
||||
},
|
||||
},
|
||||
});
|
143
src/renderer/pages/home/home.tsx
Normal file
143
src/renderer/pages/home/home.tsx
Normal file
|
@ -0,0 +1,143 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
|
||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||
|
||||
import { Button, GameCard, Hero } from "@renderer/components";
|
||||
import type { CatalogueCategory, CatalogueEntry } from "@types";
|
||||
|
||||
import starsAnimation from "@renderer/assets/lottie/stars.json";
|
||||
|
||||
import * as styles from "./home.css";
|
||||
import { vars } from "@renderer/theme.css";
|
||||
import Lottie from "lottie-react";
|
||||
|
||||
const categories: CatalogueCategory[] = ["trending", "recently_added"];
|
||||
|
||||
export function Home() {
|
||||
const { t } = useTranslation("home");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoadingRandomGame, setIsLoadingRandomGame] = useState(false);
|
||||
const randomGameObjectID = useRef<string | null>(null);
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [catalogue, setCatalogue] = useState<
|
||||
Record<CatalogueCategory, CatalogueEntry[]>
|
||||
>({
|
||||
trending: [],
|
||||
recently_added: [],
|
||||
});
|
||||
|
||||
const getCatalogue = useCallback((category: CatalogueCategory) => {
|
||||
setIsLoading(true);
|
||||
|
||||
window.electron
|
||||
.getCatalogue(category)
|
||||
.then((catalogue) => {
|
||||
setCatalogue((prev) => ({ ...prev, [category]: catalogue }));
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const currentCategory = searchParams.get("category") || categories[0];
|
||||
|
||||
const handleSelectCategory = (category: CatalogueCategory) => {
|
||||
if (category !== currentCategory) {
|
||||
getCatalogue(category);
|
||||
navigate(`/?category=${category}`, { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
const getRandomGame = useCallback(() => {
|
||||
setIsLoadingRandomGame(true);
|
||||
|
||||
window.electron
|
||||
.getRandomGame()
|
||||
.then((objectID) => {
|
||||
randomGameObjectID.current = objectID;
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingRandomGame(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleRandomizerClick = () => {
|
||||
const searchParams = new URLSearchParams({
|
||||
fromRandomizer: "1",
|
||||
});
|
||||
|
||||
navigate(
|
||||
`/game/steam/${randomGameObjectID.current}?${searchParams.toString()}`
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
getCatalogue(currentCategory as CatalogueCategory);
|
||||
getRandomGame();
|
||||
}, [getCatalogue, currentCategory, getRandomGame]);
|
||||
|
||||
return (
|
||||
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
||||
<section className={styles.content}>
|
||||
<h2>{t("featured")}</h2>
|
||||
|
||||
<Hero />
|
||||
|
||||
<section className={styles.homeHeader}>
|
||||
<div className={styles.homeCategories}>
|
||||
{categories.map((category) => (
|
||||
<Button
|
||||
key={category}
|
||||
theme={currentCategory === category ? "primary" : "outline"}
|
||||
onClick={() => handleSelectCategory(category)}
|
||||
>
|
||||
{t(category)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleRandomizerClick}
|
||||
theme="outline"
|
||||
disabled={isLoadingRandomGame}
|
||||
>
|
||||
<div style={{ width: 16, height: 16, position: "relative" }}>
|
||||
<Lottie
|
||||
animationData={starsAnimation}
|
||||
style={{ width: 70, position: "absolute", top: -28, left: -27 }}
|
||||
loop
|
||||
/>
|
||||
</div>
|
||||
{t("surprise_me")}
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<h2>{t(currentCategory)}</h2>
|
||||
|
||||
<section className={styles.cards}>
|
||||
{isLoading
|
||||
? Array.from({ length: 12 }).map((_, index) => (
|
||||
<Skeleton key={index} className={styles.cardSkeleton} />
|
||||
))
|
||||
: catalogue[currentCategory as CatalogueCategory].map((result) => (
|
||||
<GameCard
|
||||
key={result.objectID}
|
||||
game={result}
|
||||
onClick={() =>
|
||||
navigate(`/game/${result.shop}/${result.objectID}`)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
</section>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
}
|
|
@ -13,12 +13,12 @@ import { vars } from "@renderer/theme.css";
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import * as styles from "./catalogue.css";
|
||||
import * as styles from "./home.css";
|
||||
|
||||
export function SearchResults() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { t } = useTranslation("catalogue");
|
||||
const { t } = useTranslation("home");
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [searchResults, setSearchResults] = useState<CatalogueEntry[]>([]);
|
||||
|
@ -54,7 +54,7 @@ export function SearchResults() {
|
|||
return (
|
||||
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
||||
<section className={styles.content}>
|
||||
<section className={styles.cards({ searching: false })}>
|
||||
<section className={styles.cards}>
|
||||
{isLoading &&
|
||||
Array.from({ length: 12 }).map((_, index) => (
|
||||
<Skeleton key={index} className={styles.cardSkeleton} />
|
|
@ -1,5 +1,6 @@
|
|||
export * from "./catalogue/catalogue";
|
||||
export * from "./home/home";
|
||||
export * from "./game-details/game-details";
|
||||
export * from "./downloads/downloads";
|
||||
export * from "./catalogue/search-results";
|
||||
export * from "./home/search-results";
|
||||
export * from "./settings/settings";
|
||||
export * from "./catalogue/catalogue";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue