mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-23 19:01:02 +00:00
feat: get image assets from api
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { CatalogueCategory } from "@shared";
|
||||
import { ShopAssets } from "@types";
|
||||
|
||||
const getCatalogue = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -11,7 +12,7 @@ const getCatalogue = async (
|
||||
skip: "0",
|
||||
});
|
||||
|
||||
return HydraApi.get(
|
||||
return HydraApi.get<ShopAssets[]>(
|
||||
`/catalogue/${category}?${params.toString()}`,
|
||||
{},
|
||||
{ needsAuth: false }
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { getSteamAppDetails, logger } from "@main/services";
|
||||
|
||||
import type { ShopDetails, GameShop } from "@types";
|
||||
import type { ShopDetails, GameShop, ShopDetailsWithAssets } from "@types";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
import { gamesShopCacheSublevel, levelKeys } from "@main/level";
|
||||
import {
|
||||
gamesShopAssetsSublevel,
|
||||
gamesShopCacheSublevel,
|
||||
levelKeys,
|
||||
} from "@main/level";
|
||||
|
||||
const getLocalizedSteamAppDetails = async (
|
||||
objectId: string,
|
||||
@@ -14,22 +17,7 @@ const getLocalizedSteamAppDetails = async (
|
||||
return getSteamAppDetails(objectId, language);
|
||||
}
|
||||
|
||||
return getSteamAppDetails(objectId, language).then(
|
||||
async (localizedAppDetails) => {
|
||||
const steamGame = await steamGamesWorker.run(Number(objectId), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
if (steamGame && localizedAppDetails) {
|
||||
return {
|
||||
...localizedAppDetails,
|
||||
name: steamGame.name,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
return getSteamAppDetails(objectId, language);
|
||||
};
|
||||
|
||||
const getGameShopDetails = async (
|
||||
@@ -37,31 +25,40 @@ const getGameShopDetails = async (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
language: string
|
||||
): Promise<ShopDetails | null> => {
|
||||
): Promise<ShopDetailsWithAssets | null> => {
|
||||
if (shop === "steam") {
|
||||
const cachedData = await gamesShopCacheSublevel.get(
|
||||
levelKeys.gameShopCacheItem(shop, objectId, language)
|
||||
);
|
||||
const [cachedData, cachedAssets] = await Promise.all([
|
||||
gamesShopCacheSublevel.get(
|
||||
levelKeys.gameShopCacheItem(shop, objectId, language)
|
||||
),
|
||||
gamesShopAssetsSublevel.get(levelKeys.game(shop, objectId)),
|
||||
]);
|
||||
|
||||
const appDetails = getLocalizedSteamAppDetails(objectId, language).then(
|
||||
(result) => {
|
||||
const appDetails: Promise<ShopDetailsWithAssets | null> =
|
||||
getLocalizedSteamAppDetails(objectId, language).then((result) => {
|
||||
if (result) {
|
||||
result.name = cachedAssets?.title ?? result.name;
|
||||
|
||||
gamesShopCacheSublevel
|
||||
.put(levelKeys.gameShopCacheItem(shop, objectId, language), result)
|
||||
.catch((err) => {
|
||||
logger.error("Could not cache game details", err);
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
...cachedAssets,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (cachedData) {
|
||||
return {
|
||||
...cachedData,
|
||||
objectId,
|
||||
} as ShopDetails;
|
||||
...cachedAssets,
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.resolve(appDetails);
|
||||
|
||||
16
src/main/events/catalogue/save-game-shop-assets.ts
Normal file
16
src/main/events/catalogue/save-game-shop-assets.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { GameShop, ShopAssets } from "@types";
|
||||
import { gamesShopAssetsSublevel, levelKeys } from "@main/level";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const saveGameShopAssets = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
assets: ShopAssets
|
||||
): Promise<void> => {
|
||||
if (shop === "steam") {
|
||||
await gamesShopAssetsSublevel.put(levelKeys.game(shop, objectId), assets);
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("saveGameShopAssets", saveGameShopAssets);
|
||||
@@ -3,6 +3,7 @@ import { ipcMain } from "electron";
|
||||
|
||||
import "./catalogue/get-catalogue";
|
||||
import "./catalogue/get-game-shop-details";
|
||||
import "./catalogue/save-game-shop-assets";
|
||||
import "./catalogue/get-how-long-to-beat";
|
||||
import "./catalogue/get-random-game";
|
||||
import "./catalogue/search-games";
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
import { createGame } from "@main/services/library-sync";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
import { updateLocalUnlockedAchievements } from "@main/services/achievements/update-local-unlocked-achivements";
|
||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
import {
|
||||
downloadsSublevel,
|
||||
gamesShopAssetsSublevel,
|
||||
gamesSublevel,
|
||||
levelKeys,
|
||||
} from "@main/level";
|
||||
|
||||
const addGameToLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -17,6 +18,8 @@ const addGameToLibrary = async (
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
let game = await gamesSublevel.get(gameKey);
|
||||
|
||||
const gameAssets = await gamesShopAssetsSublevel.get(gameKey);
|
||||
|
||||
if (game) {
|
||||
await downloadsSublevel.del(gameKey);
|
||||
|
||||
@@ -25,17 +28,9 @@ const addGameToLibrary = async (
|
||||
isDeleted: false,
|
||||
});
|
||||
} else {
|
||||
const steamGame = await steamGamesWorker.run(Number(objectId), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
|
||||
: null;
|
||||
|
||||
game = {
|
||||
title,
|
||||
iconUrl,
|
||||
iconUrl: gameAssets?.iconUrl ?? null,
|
||||
objectId,
|
||||
shop,
|
||||
remoteId: null,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { LibraryGame } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { downloadsSublevel, gamesSublevel } from "@main/level";
|
||||
import {
|
||||
downloadsSublevel,
|
||||
gamesShopAssetsSublevel,
|
||||
gamesSublevel,
|
||||
} from "@main/level";
|
||||
|
||||
const getLibrary = async (): Promise<LibraryGame[]> => {
|
||||
return gamesSublevel
|
||||
@@ -12,11 +16,13 @@ const getLibrary = async (): Promise<LibraryGame[]> => {
|
||||
.filter(([_key, game]) => game.isDeleted === false)
|
||||
.map(async ([key, game]) => {
|
||||
const download = await downloadsSublevel.get(key);
|
||||
const gameAssets = await gamesShopAssetsSublevel.get(key);
|
||||
|
||||
return {
|
||||
id: key,
|
||||
...game,
|
||||
download: download ?? null,
|
||||
...gameAssets,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import type { Download, StartGameDownloadPayload } from "@types";
|
||||
import { DownloadManager, HydraApi, logger } from "@main/services";
|
||||
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
import { createGame } from "@main/services/library-sync";
|
||||
import { Downloader, DownloadError, steamUrlBuilder } from "@shared";
|
||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
import { Downloader, DownloadError } from "@shared";
|
||||
import {
|
||||
downloadsSublevel,
|
||||
gamesShopAssetsSublevel,
|
||||
gamesSublevel,
|
||||
levelKeys,
|
||||
} from "@main/level";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
const startGameDownload = async (
|
||||
@@ -36,27 +39,20 @@ const startGameDownload = async (
|
||||
}
|
||||
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
const gameAssets = await gamesShopAssetsSublevel.get(gameKey);
|
||||
|
||||
/* Delete any previous download */
|
||||
await downloadsSublevel.del(gameKey);
|
||||
|
||||
if (game?.isDeleted) {
|
||||
if (game) {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
isDeleted: false,
|
||||
});
|
||||
} else {
|
||||
const steamGame = await steamGamesWorker.run(Number(objectId), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
|
||||
: null;
|
||||
|
||||
await gamesSublevel.put(gameKey, {
|
||||
title,
|
||||
iconUrl,
|
||||
iconUrl: gameAssets?.iconUrl ?? null,
|
||||
objectId,
|
||||
shop,
|
||||
remoteId: null,
|
||||
|
||||
11
src/main/level/sublevels/game-shop-assets.ts
Normal file
11
src/main/level/sublevels/game-shop-assets.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { ShopAssets } from "@types";
|
||||
|
||||
import { db } from "../level";
|
||||
import { levelKeys } from "./keys";
|
||||
|
||||
export const gamesShopAssetsSublevel = db.sublevel<string, ShopAssets>(
|
||||
levelKeys.gameShopAssets,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./downloads";
|
||||
export * from "./games";
|
||||
export * from "./game-shop-assets";
|
||||
export * from "./game-shop-cache";
|
||||
export * from "./game-achievements";
|
||||
export * from "./keys";
|
||||
|
||||
@@ -6,6 +6,7 @@ export const levelKeys = {
|
||||
user: "user",
|
||||
auth: "auth",
|
||||
themes: "themes",
|
||||
gameShopAssets: "gameShopAssets",
|
||||
gameShopCache: "gameShopCache",
|
||||
gameShopCacheItem: (shop: GameShop, objectId: string, language: string) =>
|
||||
`${shop}:${objectId}:${language}`,
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { ShopAssets } from "@types";
|
||||
import { HydraApi } from "../hydra-api";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { gamesShopAssetsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
type ProfileGame = {
|
||||
id: string;
|
||||
lastTimePlayed: Date | null;
|
||||
playTimeInMilliseconds: number;
|
||||
} & ShopAssets;
|
||||
|
||||
export const mergeWithRemoteGames = async () => {
|
||||
return HydraApi.get("/profile/games")
|
||||
return HydraApi.get<ProfileGame[]>("/profile/games")
|
||||
.then(async (response) => {
|
||||
for (const game of response) {
|
||||
const localGame = await gamesSublevel.get(
|
||||
@@ -40,6 +47,21 @@ export const mergeWithRemoteGames = async () => {
|
||||
isDeleted: false,
|
||||
});
|
||||
}
|
||||
|
||||
await gamesShopAssetsSublevel.put(
|
||||
levelKeys.game(game.shop, game.objectId),
|
||||
{
|
||||
shop: game.shop,
|
||||
objectId: game.objectId,
|
||||
title: game.title,
|
||||
coverImageUrl: game.coverImageUrl,
|
||||
libraryHeroImageUrl: game.libraryHeroImageUrl,
|
||||
libraryImageUrl: game.libraryImageUrl,
|
||||
logoImageUrl: game.logoImageUrl,
|
||||
iconUrl: game.iconUrl,
|
||||
logoPosition: game.logoPosition,
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import path from "node:path";
|
||||
import steamGamesWorkerPath from "./steam-games.worker?modulePath";
|
||||
|
||||
import Piscina from "piscina";
|
||||
|
||||
import { seedsPath } from "@main/constants";
|
||||
|
||||
export const steamGamesWorker = new Piscina({
|
||||
filename: steamGamesWorkerPath,
|
||||
workerData: {
|
||||
steamGamesPath: path.join(seedsPath, "steam-games.json"),
|
||||
},
|
||||
maxThreads: 1,
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { SteamGame } from "@types";
|
||||
import { slice } from "lodash-es";
|
||||
import fs from "node:fs";
|
||||
|
||||
import { workerData } from "node:worker_threads";
|
||||
|
||||
const { steamGamesPath } = workerData;
|
||||
|
||||
const data = fs.readFileSync(steamGamesPath, "utf-8");
|
||||
|
||||
const steamGames = JSON.parse(data) as SteamGame[];
|
||||
|
||||
export const getById = (id: number) =>
|
||||
steamGames.find((game) => game.id === id);
|
||||
|
||||
export const list = ({ limit, offset }: { limit: number; offset: number }) =>
|
||||
slice(steamGames, offset, offset + limit);
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
Theme,
|
||||
FriendRequestSync,
|
||||
ShortcutLocation,
|
||||
ShopAssets,
|
||||
} from "@types";
|
||||
import type { AuthPage, CatalogueCategory } from "@shared";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
@@ -64,6 +65,8 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
ipcRenderer.invoke("searchGames", payload, take, skip),
|
||||
getCatalogue: (category: CatalogueCategory) =>
|
||||
ipcRenderer.invoke("getCatalogue", category),
|
||||
saveGameShopAssets: (objectId: string, shop: GameShop, assets: ShopAssets) =>
|
||||
ipcRenderer.invoke("saveGameShopAssets", objectId, shop, assets),
|
||||
getGameShopDetails: (objectId: string, shop: GameShop, language: string) =>
|
||||
ipcRenderer.invoke("getGameShopDetails", objectId, shop, language),
|
||||
getRandomGame: () => ipcRenderer.invoke("getRandomGame"),
|
||||
|
||||
@@ -28,34 +28,38 @@ export function Hero() {
|
||||
});
|
||||
}, [i18n.language]);
|
||||
|
||||
const handleClick = async (game: TrendingGame) => {
|
||||
await window.electron.saveGameShopAssets(game.objectId, game.shop, game);
|
||||
|
||||
navigate(game.uri);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton className="hero" />;
|
||||
}
|
||||
|
||||
if (featuredGameDetails?.length) {
|
||||
return featuredGameDetails.map((game, index) => (
|
||||
return featuredGameDetails.map((game) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(game.uri)}
|
||||
onClick={() => handleClick(game)}
|
||||
className="hero"
|
||||
key={index}
|
||||
key={game.uri}
|
||||
>
|
||||
<div className="hero__backdrop">
|
||||
<img
|
||||
src={game.background}
|
||||
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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
12
src/renderer/src/declaration.d.ts
vendored
12
src/renderer/src/declaration.d.ts
vendored
@@ -3,7 +3,6 @@ import type {
|
||||
AppUpdaterEvent,
|
||||
GameShop,
|
||||
HowLongToBeatCategory,
|
||||
ShopDetails,
|
||||
Steam250Game,
|
||||
DownloadProgress,
|
||||
SeedingStatus,
|
||||
@@ -34,6 +33,8 @@ import type {
|
||||
Auth,
|
||||
ShortcutLocation,
|
||||
CatalogueSearchResult,
|
||||
ShopAssets,
|
||||
ShopDetailsWithAssets,
|
||||
} from "@types";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type disk from "diskusage";
|
||||
@@ -71,12 +72,17 @@ declare global {
|
||||
take: number,
|
||||
skip: number
|
||||
) => Promise<{ edges: CatalogueSearchResult[]; count: number }>;
|
||||
getCatalogue: (category: CatalogueCategory) => Promise<any[]>;
|
||||
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,7 +137,7 @@ export function AchievementsContent({
|
||||
}, [dispatch, gameTitle]);
|
||||
|
||||
const handleHeroLoad = async () => {
|
||||
const output = await average(steamUrlBuilder.libraryHero(objectId!), {
|
||||
const output = await average(shopDetails?.libraryHeroImageUrl ?? "", {
|
||||
amount: 1,
|
||||
format: "hex",
|
||||
});
|
||||
@@ -179,7 +185,7 @@ export function AchievementsContent({
|
||||
return (
|
||||
<div className="achievements-content__achievements-list">
|
||||
<img
|
||||
src={steamUrlBuilder.libraryHero(objectId)}
|
||||
src={shopDetails?.libraryHeroImageUrl ?? ""}
|
||||
className="achievements-content__achievements-list__image"
|
||||
alt={gameTitle}
|
||||
onLoad={handleHeroLoad}
|
||||
@@ -205,7 +211,7 @@ export function AchievementsContent({
|
||||
to={buildGameDetailsPath({ shop, objectId, title: gameTitle })}
|
||||
>
|
||||
<img
|
||||
src={steamUrlBuilder.logo(objectId)}
|
||||
src={shopDetails?.logoImageUrl ?? ""}
|
||||
className="achievements-content__achievements-list__section__container__hero__content__game-logo"
|
||||
alt={gameTitle}
|
||||
/>
|
||||
|
||||
@@ -43,11 +43,19 @@ export function GameItem({ game }: GameItemProps) {
|
||||
});
|
||||
}, [game.genres, language, steamGenres]);
|
||||
|
||||
const handleNavigateToGameDetails = async () => {
|
||||
await window.electron.saveGameShopAssets(game.objectId, game.shop, {
|
||||
...game,
|
||||
});
|
||||
|
||||
navigate(buildGameDetailsPath(game));
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="game-item"
|
||||
onClick={() => navigate(buildGameDetailsPath(game))}
|
||||
onClick={handleNavigateToGameDetails}
|
||||
>
|
||||
<img
|
||||
className="game-item__cover"
|
||||
|
||||
@@ -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,7 +59,7 @@ export function GameDetailsContent() {
|
||||
const [backdropOpacity, setBackdropOpacity] = useState(1);
|
||||
|
||||
const handleHeroLoad = async () => {
|
||||
const output = await average(steamUrlBuilder.libraryHero(objectId!), {
|
||||
const output = await average(shopDetails?.libraryHeroImageUrl ?? "", {
|
||||
amount: 1,
|
||||
format: "hex",
|
||||
});
|
||||
@@ -100,7 +100,7 @@ export function GameDetailsContent() {
|
||||
<section className="game-details__container">
|
||||
<div ref={heroRef} className="game-details__hero">
|
||||
<img
|
||||
src={steamUrlBuilder.libraryHero(objectId!)}
|
||||
src={shopDetails?.libraryHeroImageUrl ?? ""}
|
||||
className="game-details__hero-image"
|
||||
alt={game?.title}
|
||||
onLoad={handleHeroLoad}
|
||||
@@ -119,7 +119,7 @@ export function GameDetailsContent() {
|
||||
>
|
||||
<div className="game-details__hero-content">
|
||||
<img
|
||||
src={steamUrlBuilder.logo(objectId!)}
|
||||
src={shopDetails?.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]: [],
|
||||
@@ -92,6 +94,12 @@ export default function Home() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickGameCard = async (game: ShopAssets) => {
|
||||
await window.electron.saveGameShopAssets(game.objectId, game.shop, game);
|
||||
|
||||
navigate(buildGameDetailsPath(game));
|
||||
};
|
||||
|
||||
return (
|
||||
<SkeletonTheme baseColor="#1c1c1c" highlightColor="#444">
|
||||
<section className="home__content">
|
||||
@@ -175,7 +183,7 @@ export default function Home() {
|
||||
<GameCard
|
||||
key={result.objectId}
|
||||
game={result}
|
||||
onClick={() => navigate(buildGameDetailsPath(result))}
|
||||
onClick={() => handleClickGameCard(result)}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
|
||||
@@ -36,10 +36,24 @@ export interface DownloadSource {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface ShopAssets {
|
||||
objectId: string;
|
||||
shop: GameShop;
|
||||
title: string;
|
||||
coverImageUrl: string;
|
||||
libraryHeroImageUrl: string;
|
||||
libraryImageUrl: string;
|
||||
logoImageUrl: string;
|
||||
iconUrl: string | null;
|
||||
logoPosition: string | null;
|
||||
}
|
||||
|
||||
export type ShopDetails = SteamAppDetails & {
|
||||
objectId: string;
|
||||
};
|
||||
|
||||
export type ShopDetailsWithAssets = ShopDetails & Partial<ShopAssets>;
|
||||
|
||||
export interface TorrentFile {
|
||||
path: string;
|
||||
length: number;
|
||||
@@ -228,14 +242,8 @@ export interface GameStats {
|
||||
playerCount: number;
|
||||
}
|
||||
|
||||
export interface TrendingGame {
|
||||
objectId: string;
|
||||
shop: GameShop;
|
||||
title: string;
|
||||
export interface TrendingGame extends ShopAssets {
|
||||
description: string | null;
|
||||
background: string;
|
||||
logo: string;
|
||||
logoPosition: string | null;
|
||||
uri: string;
|
||||
}
|
||||
|
||||
@@ -329,17 +337,19 @@ export interface CatalogueSearchResult {
|
||||
installCount: number;
|
||||
achievementCount: number;
|
||||
iconUrl: string;
|
||||
coverImageUrl: string;
|
||||
libraryHeroImageUrl: string;
|
||||
libraryImageUrl: string;
|
||||
logoImageUrl: string;
|
||||
coverImageUrl: string;
|
||||
logoPosition: string | null;
|
||||
shopData: string;
|
||||
}
|
||||
|
||||
export interface LibraryGame extends Game {
|
||||
id: string;
|
||||
download: Download | null;
|
||||
}
|
||||
export type LibraryGame = Game &
|
||||
Partial<ShopAssets> & {
|
||||
id: string;
|
||||
download: Download | null;
|
||||
};
|
||||
|
||||
export * from "./game.types";
|
||||
export * from "./steam.types";
|
||||
|
||||
Reference in New Issue
Block a user