mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-18 08:43:57 +00:00
Merge branch 'main' of github.com:hydralauncher/hydra into feat/HYD-781
This commit is contained in:
@@ -9,7 +9,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { Badge } from "../badge/badge";
|
||||
import { useCallback, useState, useMemo } from "react";
|
||||
import { useFormat, useRepacks } from "@renderer/hooks";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
|
||||
export interface GameCardProps
|
||||
extends React.DetailedHTMLProps<
|
||||
@@ -63,7 +62,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
||||
>
|
||||
<div className="game-card__backdrop">
|
||||
<img
|
||||
src={steamUrlBuilder.library(game.objectId)}
|
||||
src={game.libraryImageUrl}
|
||||
alt={game.title}
|
||||
className="game-card__cover"
|
||||
loading="lazy"
|
||||
|
||||
@@ -33,29 +33,27 @@ export function Hero() {
|
||||
}
|
||||
|
||||
if (featuredGameDetails?.length) {
|
||||
return featuredGameDetails.map((game, index) => (
|
||||
return featuredGameDetails.map((game) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(game.uri)}
|
||||
className="hero"
|
||||
key={index}
|
||||
key={game.uri}
|
||||
>
|
||||
<div className="hero__backdrop">
|
||||
<img
|
||||
src={game.background}
|
||||
alt={game.description}
|
||||
src={game.libraryHeroImageUrl}
|
||||
alt={game.description ?? ""}
|
||||
className="hero__media"
|
||||
/>
|
||||
|
||||
<div className="hero__content">
|
||||
{game.logo && (
|
||||
<img
|
||||
src={game.logo}
|
||||
width="250px"
|
||||
alt={game.description}
|
||||
loading="eager"
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
src={game.logoImageUrl}
|
||||
width="250px"
|
||||
alt={game.description ?? ""}
|
||||
loading="eager"
|
||||
/>
|
||||
<p className="hero__description">{game.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
GameShop,
|
||||
GameStats,
|
||||
LibraryGame,
|
||||
ShopDetails,
|
||||
ShopDetailsWithAssets,
|
||||
UserAchievement,
|
||||
} from "@types";
|
||||
|
||||
@@ -69,7 +69,9 @@ export function GameDetailsContextProvider({
|
||||
gameTitle,
|
||||
shop,
|
||||
}: Readonly<GameDetailsContextProps>) {
|
||||
const [shopDetails, setShopDetails] = useState<ShopDetails | null>(null);
|
||||
const [shopDetails, setShopDetails] = useState<ShopDetailsWithAssets | null>(
|
||||
null
|
||||
);
|
||||
const [achievements, setAchievements] = useState<UserAchievement[] | null>(
|
||||
null
|
||||
);
|
||||
@@ -79,7 +81,7 @@ export function GameDetailsContextProvider({
|
||||
|
||||
const [stats, setStats] = useState<GameStats | null>(null);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [gameColor, setGameColor] = useState("");
|
||||
const [isGameRunning, setIsGameRunning] = useState(false);
|
||||
const [showRepacksModal, setShowRepacksModal] = useState(false);
|
||||
@@ -120,7 +122,7 @@ export function GameDetailsContextProvider({
|
||||
const abortController = new AbortController();
|
||||
abortControllerRef.current = abortController;
|
||||
|
||||
window.electron
|
||||
const shopDetailsPromise = window.electron
|
||||
.getGameShopDetails(objectId, shop, getSteamLanguage(i18n.language))
|
||||
.then((result) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
@@ -135,15 +137,42 @@ export function GameDetailsContextProvider({
|
||||
) {
|
||||
setHasNSFWContentBlocked(true);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
|
||||
if (result?.assets) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
window.electron.getGameStats(objectId, shop).then((result) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
setStats(result);
|
||||
});
|
||||
const statsPromise = window.electron
|
||||
.getGameStats(objectId, shop)
|
||||
.then((result) => {
|
||||
if (abortController.signal.aborted) return null;
|
||||
setStats(result);
|
||||
return result;
|
||||
});
|
||||
|
||||
Promise.all([shopDetailsPromise, statsPromise])
|
||||
.then(([_, stats]) => {
|
||||
if (stats) {
|
||||
const assets = stats.assets;
|
||||
if (assets) {
|
||||
window.electron.saveGameShopAssets(objectId, shop, assets);
|
||||
|
||||
setShopDetails((prev) => {
|
||||
if (!prev) return null;
|
||||
console.log("assets", assets);
|
||||
return {
|
||||
...prev,
|
||||
assets,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (abortController.signal.aborted) return;
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
if (userDetails) {
|
||||
window.electron
|
||||
|
||||
@@ -3,13 +3,13 @@ import type {
|
||||
GameShop,
|
||||
GameStats,
|
||||
LibraryGame,
|
||||
ShopDetails,
|
||||
ShopDetailsWithAssets,
|
||||
UserAchievement,
|
||||
} from "@types";
|
||||
|
||||
export interface GameDetailsContext {
|
||||
game: LibraryGame | null;
|
||||
shopDetails: ShopDetails | null;
|
||||
shopDetails: ShopDetailsWithAssets | null;
|
||||
repacks: GameRepack[];
|
||||
shop: GameShop;
|
||||
gameTitle: string;
|
||||
|
||||
15
src/renderer/src/declaration.d.ts
vendored
15
src/renderer/src/declaration.d.ts
vendored
@@ -3,7 +3,6 @@ import type {
|
||||
AppUpdaterEvent,
|
||||
GameShop,
|
||||
HowLongToBeatCategory,
|
||||
ShopDetails,
|
||||
Steam250Game,
|
||||
DownloadProgress,
|
||||
SeedingStatus,
|
||||
@@ -33,6 +32,9 @@ import type {
|
||||
Badge,
|
||||
Auth,
|
||||
ShortcutLocation,
|
||||
CatalogueSearchResult,
|
||||
ShopAssets,
|
||||
ShopDetailsWithAssets,
|
||||
} from "@types";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type disk from "diskusage";
|
||||
@@ -69,13 +71,18 @@ declare global {
|
||||
payload: CatalogueSearchPayload,
|
||||
take: number,
|
||||
skip: number
|
||||
) => Promise<{ edges: any[]; count: number }>;
|
||||
getCatalogue: (category: CatalogueCategory) => Promise<any[]>;
|
||||
) => Promise<{ edges: CatalogueSearchResult[]; count: number }>;
|
||||
getCatalogue: (category: CatalogueCategory) => Promise<ShopAssets[]>;
|
||||
saveGameShopAssets: (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
assets: ShopAssets
|
||||
) => Promise<void>;
|
||||
getGameShopDetails: (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
language: string
|
||||
) => Promise<ShopDetails | null>;
|
||||
) => Promise<ShopDetailsWithAssets | null>;
|
||||
getRandomGame: () => Promise<Steam250Game>;
|
||||
getHowLongToBeat: (
|
||||
objectId: string,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { setHeaderTitle } from "@renderer/features";
|
||||
import { useAppDispatch, useUserDetails } from "@renderer/hooks";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
@@ -120,8 +119,15 @@ export function AchievementsContent({
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isHeaderStuck, setIsHeaderStuck] = useState(false);
|
||||
|
||||
const { gameTitle, objectId, shop, achievements, gameColor, setGameColor } =
|
||||
useContext(gameDetailsContext);
|
||||
const {
|
||||
gameTitle,
|
||||
objectId,
|
||||
shop,
|
||||
shopDetails,
|
||||
achievements,
|
||||
gameColor,
|
||||
setGameColor,
|
||||
} = useContext(gameDetailsContext);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -131,10 +137,13 @@ export function AchievementsContent({
|
||||
}, [dispatch, gameTitle]);
|
||||
|
||||
const handleHeroLoad = async () => {
|
||||
const output = await average(steamUrlBuilder.libraryHero(objectId!), {
|
||||
amount: 1,
|
||||
format: "hex",
|
||||
});
|
||||
const output = await average(
|
||||
shopDetails?.assets?.libraryHeroImageUrl ?? "",
|
||||
{
|
||||
amount: 1,
|
||||
format: "hex",
|
||||
}
|
||||
);
|
||||
|
||||
const backgroundColor = output
|
||||
? (new Color(output).darken(0.7).toString() as string)
|
||||
@@ -179,7 +188,7 @@ export function AchievementsContent({
|
||||
return (
|
||||
<div className="achievements-content__achievements-list">
|
||||
<img
|
||||
src={steamUrlBuilder.libraryHero(objectId)}
|
||||
src={shopDetails?.assets?.libraryHeroImageUrl ?? ""}
|
||||
className="achievements-content__achievements-list__image"
|
||||
alt={gameTitle}
|
||||
onLoad={handleHeroLoad}
|
||||
@@ -205,7 +214,7 @@ export function AchievementsContent({
|
||||
to={buildGameDetailsPath({ shop, objectId, title: gameTitle })}
|
||||
>
|
||||
<img
|
||||
src={steamUrlBuilder.logo(objectId)}
|
||||
src={shopDetails?.assets?.logoImageUrl ?? ""}
|
||||
className="achievements-content__achievements-list__section__container__hero__content__game-logo"
|
||||
alt={gameTitle}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DownloadSource } from "@types";
|
||||
import type { CatalogueSearchResult, DownloadSource } from "@types";
|
||||
|
||||
import {
|
||||
useAppDispatch,
|
||||
@@ -44,7 +44,7 @@ export default function Catalogue() {
|
||||
const [downloadSources, setDownloadSources] = useState<DownloadSource[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [results, setResults] = useState<any[]>([]);
|
||||
const [results, setResults] = useState<CatalogueSearchResult[]>([]);
|
||||
|
||||
const [itemsCount, setItemsCount] = useState(0);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Badge } from "@renderer/components";
|
||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
import { useAppSelector, useRepacks } from "@renderer/hooks";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
import { useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import "./game-item.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CatalogueSearchResult } from "@types";
|
||||
|
||||
export interface GameItemProps {
|
||||
game: any;
|
||||
game: CatalogueSearchResult;
|
||||
}
|
||||
|
||||
export function GameItem({ game }: GameItemProps) {
|
||||
@@ -51,7 +51,7 @@ export function GameItem({ game }: GameItemProps) {
|
||||
>
|
||||
<img
|
||||
className="game-item__cover"
|
||||
src={steamUrlBuilder.library(game.objectId)}
|
||||
src={game.libraryImageUrl}
|
||||
alt={game.title}
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
formatDownloadProgress,
|
||||
} from "@renderer/helpers";
|
||||
|
||||
import { Downloader, formatBytes, steamUrlBuilder } from "@shared";
|
||||
import { Downloader, formatBytes } from "@shared";
|
||||
import { DOWNLOADER_NAME } from "@renderer/constants";
|
||||
import { useAppSelector, useDownload, useLibrary } from "@renderer/hooks";
|
||||
|
||||
@@ -319,7 +319,7 @@ export function DownloadGroup({
|
||||
<div className="download-group__cover">
|
||||
<div className="download-group__cover-backdrop">
|
||||
<img
|
||||
src={steamUrlBuilder.library(game.objectId)}
|
||||
src={game.libraryImageUrl ?? ""}
|
||||
className="download-group__cover-image"
|
||||
alt={game.title}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Sidebar } from "./sidebar/sidebar";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
|
||||
import { AuthPage, steamUrlBuilder } from "@shared";
|
||||
import { AuthPage } from "@shared";
|
||||
|
||||
import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif";
|
||||
import { useUserDetails } from "@renderer/hooks";
|
||||
@@ -59,10 +59,13 @@ export function GameDetailsContent() {
|
||||
const [backdropOpacity, setBackdropOpacity] = useState(1);
|
||||
|
||||
const handleHeroLoad = async () => {
|
||||
const output = await average(steamUrlBuilder.libraryHero(objectId!), {
|
||||
amount: 1,
|
||||
format: "hex",
|
||||
});
|
||||
const output = await average(
|
||||
shopDetails?.assets?.libraryHeroImageUrl ?? "",
|
||||
{
|
||||
amount: 1,
|
||||
format: "hex",
|
||||
}
|
||||
);
|
||||
|
||||
const backgroundColor = output
|
||||
? new Color(output).darken(0.7).toString()
|
||||
@@ -100,7 +103,7 @@ export function GameDetailsContent() {
|
||||
<section className="game-details__container">
|
||||
<div ref={heroRef} className="game-details__hero">
|
||||
<img
|
||||
src={steamUrlBuilder.libraryHero(objectId!)}
|
||||
src={shopDetails?.assets?.libraryHeroImageUrl ?? ""}
|
||||
className="game-details__hero-image"
|
||||
alt={game?.title}
|
||||
onLoad={handleHeroLoad}
|
||||
@@ -119,7 +122,7 @@ export function GameDetailsContent() {
|
||||
>
|
||||
<div className="game-details__hero-content">
|
||||
<img
|
||||
src={steamUrlBuilder.logo(objectId!)}
|
||||
src={shopDetails?.assets?.logoImageUrl ?? ""}
|
||||
className="game-details__game-logo"
|
||||
alt={game?.title}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||
|
||||
import { Button, GameCard, Hero } from "@renderer/components";
|
||||
import type { Steam250Game } from "@types";
|
||||
import type { ShopAssets, Steam250Game } from "@types";
|
||||
|
||||
import flameIconStatic from "@renderer/assets/icons/flame-static.png";
|
||||
import flameIconAnimated from "@renderer/assets/icons/flame-animated.gif";
|
||||
@@ -27,7 +27,9 @@ export default function Home() {
|
||||
CatalogueCategory.Hot
|
||||
);
|
||||
|
||||
const [catalogue, setCatalogue] = useState<Record<CatalogueCategory, any[]>>({
|
||||
const [catalogue, setCatalogue] = useState<
|
||||
Record<CatalogueCategory, ShopAssets[]>
|
||||
>({
|
||||
[CatalogueCategory.Hot]: [],
|
||||
[CatalogueCategory.Weekly]: [],
|
||||
[CatalogueCategory.Achievements]: [],
|
||||
|
||||
@@ -12,7 +12,6 @@ import { userProfileContext } from "@renderer/context";
|
||||
import { ClockIcon, TrophyIcon } from "@primer/octicons-react";
|
||||
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
import "./user-library-game-card.scss";
|
||||
|
||||
interface UserLibraryGameCardProps {
|
||||
@@ -150,7 +149,7 @@ export function UserLibraryGameCard({
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={steamUrlBuilder.cover(game.objectId)}
|
||||
src={game.coverImageUrl}
|
||||
alt={game.title}
|
||||
className="user-library-game__game-image"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user