mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
Merge branch 'main' into patch-2
This commit is contained in:
@@ -210,6 +210,8 @@
|
||||
"download_error_not_cached_on_hydra": "This download is not available on Nimbus.",
|
||||
"game_removed_from_favorites": "Game removed from favorites",
|
||||
"game_added_to_favorites": "Game added to favorites",
|
||||
"game_removed_from_pinned": "Game removed from pinned",
|
||||
"game_added_to_pinned": "Game added to pinned",
|
||||
"automatically_extract_downloaded_files": "Automatically extract downloaded files",
|
||||
"create_start_menu_shortcut": "Create Start Menu shortcut",
|
||||
"invalid_wine_prefix_path": "Invalid Wine prefix path",
|
||||
@@ -451,6 +453,7 @@
|
||||
"last_time_played": "Last played {{period}}",
|
||||
"activity": "Recent Activity",
|
||||
"library": "Library",
|
||||
"pinned": "Pinned",
|
||||
"total_play_time": "Total playtime",
|
||||
"manual_playtime_tooltip": "This playtime has been manually updated",
|
||||
"no_recent_activity_title": "Hmmm… nothing here",
|
||||
|
||||
@@ -16,6 +16,8 @@ import "./hardware/check-folder-write-permission";
|
||||
import "./library/add-game-to-library";
|
||||
import "./library/add-game-to-favorites";
|
||||
import "./library/remove-game-from-favorites";
|
||||
import "./library/add-game-to-pinned";
|
||||
import "./library/remove-game-from-pinned";
|
||||
import "./library/create-game-shortcut";
|
||||
import "./library/close-game";
|
||||
import "./library/delete-game-folder";
|
||||
@@ -64,6 +66,7 @@ import "./auth/sign-out";
|
||||
import "./auth/open-auth-window";
|
||||
import "./auth/get-session-hash";
|
||||
import "./user/get-user";
|
||||
import "./user/get-user-library";
|
||||
import "./user/get-blocked-users";
|
||||
import "./user/block-user";
|
||||
import "./user/unblock-user";
|
||||
|
||||
29
src/main/events/library/add-game-to-pinned.ts
Normal file
29
src/main/events/library/add-game-to-pinned.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { HydraApi } from "@main/services";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const addGameToPinned = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
if (!game) return;
|
||||
|
||||
const response = await HydraApi.put(`/profile/games/${shop}/${objectId}/pin`);
|
||||
|
||||
try {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
pinned: true,
|
||||
pinnedDate: new Date(response.pinnedDate),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to update game pinned status: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("addGameToPinned", addGameToPinned);
|
||||
29
src/main/events/library/remove-game-from-pinned.ts
Normal file
29
src/main/events/library/remove-game-from-pinned.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { HydraApi } from "@main/services";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const removeGameFromPinned = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
if (!game) return;
|
||||
|
||||
HydraApi.put(`/profile/games/${shop}/${objectId}/unpin`).catch(() => {});
|
||||
|
||||
try {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
pinned: false,
|
||||
pinnedDate: null,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to update game pinned status: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("removeGameFromPinned", removeGameFromPinned);
|
||||
23
src/main/events/user/get-user-library.ts
Normal file
23
src/main/events/user/get-user-library.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import type { UserLibraryResponse } from "@types";
|
||||
|
||||
const getUserLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
userId: string,
|
||||
take: number = 12,
|
||||
skip: number = 0
|
||||
): Promise<UserLibraryResponse | null> => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
params.append("take", take.toString());
|
||||
params.append("skip", skip.toString());
|
||||
|
||||
const queryString = params.toString();
|
||||
const baseUrl = `/users/${userId}/library`;
|
||||
const url = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||
|
||||
return HydraApi.get<UserLibraryResponse>(url).catch(() => null);
|
||||
};
|
||||
|
||||
registerEvent("getUserLibrary", getUserLibrary);
|
||||
@@ -8,6 +8,7 @@ type ProfileGame = {
|
||||
playTimeInMilliseconds: number;
|
||||
hasManuallyUpdatedPlaytime: boolean;
|
||||
isFavorite?: boolean;
|
||||
isPinned?: boolean;
|
||||
} & ShopAssets;
|
||||
|
||||
export const mergeWithRemoteGames = async () => {
|
||||
@@ -36,6 +37,7 @@ export const mergeWithRemoteGames = async () => {
|
||||
lastTimePlayed: updatedLastTimePlayed,
|
||||
playTimeInMilliseconds: updatedPlayTime,
|
||||
favorite: game.isFavorite ?? localGame.favorite,
|
||||
pinned: game.isPinned ?? localGame.pinned,
|
||||
});
|
||||
} else {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
@@ -49,6 +51,7 @@ export const mergeWithRemoteGames = async () => {
|
||||
hasManuallyUpdatedPlaytime: game.hasManuallyUpdatedPlaytime,
|
||||
isDeleted: false,
|
||||
favorite: game.isFavorite ?? false,
|
||||
pinned: game.isPinned ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ export const uploadGamesBatch = async () => {
|
||||
shop: game.shop,
|
||||
lastTimePlayed: game.lastTimePlayed,
|
||||
isFavorite: game.favorite,
|
||||
isPinned: game.pinned ?? false,
|
||||
};
|
||||
})
|
||||
).catch(() => {});
|
||||
|
||||
@@ -143,6 +143,10 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
ipcRenderer.invoke("addGameToFavorites", shop, objectId),
|
||||
removeGameFromFavorites: (shop: GameShop, objectId: string) =>
|
||||
ipcRenderer.invoke("removeGameFromFavorites", shop, objectId),
|
||||
addGameToPinned: (shop: GameShop, objectId: string) =>
|
||||
ipcRenderer.invoke("addGameToPinned", shop, objectId),
|
||||
removeGameFromPinned: (shop: GameShop, objectId: string) =>
|
||||
ipcRenderer.invoke("removeGameFromPinned", shop, objectId),
|
||||
updateLaunchOptions: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
@@ -366,6 +370,8 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
|
||||
/* User */
|
||||
getUser: (userId: string) => ipcRenderer.invoke("getUser", userId),
|
||||
getUserLibrary: (userId: string, take?: number, skip?: number) =>
|
||||
ipcRenderer.invoke("getUserLibrary", userId, take, skip),
|
||||
blockUser: (userId: string) => ipcRenderer.invoke("blockUser", userId),
|
||||
unblockUser: (userId: string) => ipcRenderer.invoke("unblockUser", userId),
|
||||
getUserFriends: (userId: string, take: number, skip: number) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { darkenColor } from "@renderer/helpers";
|
||||
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||
import type { Badge, UserProfile, UserStats } from "@types";
|
||||
import type { Badge, UserProfile, UserStats, UserGame } from "@types";
|
||||
import { average } from "color.js";
|
||||
|
||||
import { createContext, useCallback, useEffect, useState } from "react";
|
||||
@@ -17,6 +17,8 @@ export interface UserProfileContext {
|
||||
setSelectedBackgroundImage: React.Dispatch<React.SetStateAction<string>>;
|
||||
backgroundImage: string;
|
||||
badges: Badge[];
|
||||
libraryGames: UserGame[];
|
||||
pinnedGames: UserGame[];
|
||||
}
|
||||
|
||||
export const DEFAULT_USER_PROFILE_BACKGROUND = "#151515B3";
|
||||
@@ -30,6 +32,8 @@ export const userProfileContext = createContext<UserProfileContext>({
|
||||
setSelectedBackgroundImage: () => {},
|
||||
backgroundImage: "",
|
||||
badges: [],
|
||||
libraryGames: [],
|
||||
pinnedGames: [],
|
||||
});
|
||||
|
||||
const { Provider } = userProfileContext;
|
||||
@@ -49,6 +53,8 @@ export function UserProfileContextProvider({
|
||||
const [userStats, setUserStats] = useState<UserStats | null>(null);
|
||||
|
||||
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
|
||||
const [libraryGames, setLibraryGames] = useState<UserGame[]>([]);
|
||||
const [pinnedGames, setPinnedGames] = useState<UserGame[]>([]);
|
||||
const [badges, setBadges] = useState<Badge[]>([]);
|
||||
const [heroBackground, setHeroBackground] = useState(
|
||||
DEFAULT_USER_PROFILE_BACKGROUND
|
||||
@@ -85,8 +91,25 @@ export function UserProfileContextProvider({
|
||||
});
|
||||
}, [userId]);
|
||||
|
||||
const getUserLibraryGames = useCallback(async () => {
|
||||
try {
|
||||
const response = await window.electron.getUserLibrary(userId);
|
||||
if (response) {
|
||||
setLibraryGames(response.library);
|
||||
setPinnedGames(response.pinnedGames);
|
||||
} else {
|
||||
setLibraryGames([]);
|
||||
setPinnedGames([]);
|
||||
}
|
||||
} catch (error) {
|
||||
setLibraryGames([]);
|
||||
setPinnedGames([]);
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
const getUserProfile = useCallback(async () => {
|
||||
getUserStats();
|
||||
getUserLibraryGames();
|
||||
|
||||
return window.electron.getUser(userId).then((userProfile) => {
|
||||
if (userProfile) {
|
||||
@@ -102,7 +125,7 @@ export function UserProfileContextProvider({
|
||||
navigate(-1);
|
||||
}
|
||||
});
|
||||
}, [navigate, getUserStats, showErrorToast, userId, t]);
|
||||
}, [navigate, getUserStats, getUserLibraryGames, showErrorToast, userId, t]);
|
||||
|
||||
const getBadges = useCallback(async () => {
|
||||
const badges = await window.electron.getBadges();
|
||||
@@ -111,6 +134,8 @@ export function UserProfileContextProvider({
|
||||
|
||||
useEffect(() => {
|
||||
setUserProfile(null);
|
||||
setLibraryGames([]);
|
||||
setPinnedGames([]);
|
||||
setHeroBackground(DEFAULT_USER_PROFILE_BACKGROUND);
|
||||
|
||||
getUserProfile();
|
||||
@@ -128,6 +153,8 @@ export function UserProfileContextProvider({
|
||||
backgroundImage: getBackgroundImageUrl(),
|
||||
userStats,
|
||||
badges,
|
||||
libraryGames,
|
||||
pinnedGames,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
8
src/renderer/src/declaration.d.ts
vendored
8
src/renderer/src/declaration.d.ts
vendored
@@ -37,6 +37,7 @@ import type {
|
||||
ShopDetailsWithAssets,
|
||||
AchievementCustomNotificationPosition,
|
||||
AchievementNotificationInfo,
|
||||
UserLibraryResponse,
|
||||
} from "@types";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type disk from "diskusage";
|
||||
@@ -126,6 +127,8 @@ declare global {
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => Promise<void>;
|
||||
addGameToPinned: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
removeGameFromPinned: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
updateLaunchOptions: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
@@ -287,6 +290,11 @@ declare global {
|
||||
|
||||
/* User */
|
||||
getUser: (userId: string) => Promise<UserProfile | null>;
|
||||
getUserLibrary: (
|
||||
userId: string,
|
||||
take?: number,
|
||||
skip?: number
|
||||
) => Promise<UserLibraryResponse>;
|
||||
blockUser: (userId: string) => Promise<void>;
|
||||
unblockUser: (userId: string) => Promise<void>;
|
||||
getUserFriends: (
|
||||
|
||||
@@ -3,11 +3,18 @@ import {
|
||||
GearIcon,
|
||||
HeartFillIcon,
|
||||
HeartIcon,
|
||||
PinIcon,
|
||||
PinSlashIcon,
|
||||
PlayIcon,
|
||||
PlusCircleIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { Button } from "@renderer/components";
|
||||
import { useDownload, useLibrary, useToast } from "@renderer/hooks";
|
||||
import {
|
||||
useDownload,
|
||||
useLibrary,
|
||||
useToast,
|
||||
useUserDetails,
|
||||
} from "@renderer/hooks";
|
||||
import { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { gameDetailsContext } from "@renderer/context";
|
||||
@@ -19,6 +26,7 @@ export function HeroPanelActions() {
|
||||
useState(false);
|
||||
|
||||
const { isGameDeleting } = useDownload();
|
||||
const { userDetails } = useUserDetails();
|
||||
|
||||
const {
|
||||
game,
|
||||
@@ -82,6 +90,29 @@ export function HeroPanelActions() {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleGamePinned = async () => {
|
||||
setToggleLibraryGameDisabled(true);
|
||||
|
||||
try {
|
||||
if (game?.pinned && objectId) {
|
||||
await window.electron.removeGameFromPinned(shop, objectId).then(() => {
|
||||
showSuccessToast(t("game_removed_from_pinned"));
|
||||
});
|
||||
} else {
|
||||
if (!objectId) return;
|
||||
|
||||
await window.electron.addGameToPinned(shop, objectId).then(() => {
|
||||
showSuccessToast(t("game_added_to_pinned"));
|
||||
});
|
||||
}
|
||||
|
||||
updateLibrary();
|
||||
updateGame();
|
||||
} finally {
|
||||
setToggleLibraryGameDisabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openGame = async () => {
|
||||
if (game) {
|
||||
if (game.executablePath) {
|
||||
@@ -198,6 +229,17 @@ export function HeroPanelActions() {
|
||||
{game.favorite ? <HeartFillIcon /> : <HeartIcon />}
|
||||
</Button>
|
||||
|
||||
{userDetails && (
|
||||
<Button
|
||||
onClick={toggleGamePinned}
|
||||
theme="outline"
|
||||
disabled={deleting}
|
||||
className="hero-panel-actions__action"
|
||||
>
|
||||
{game.pinned ? <PinSlashIcon /> : <PinIcon />}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={() => setShowGameOptionsModal(true)}
|
||||
theme="outline"
|
||||
|
||||
@@ -11,7 +11,6 @@ import "./hero-panel-playtime.scss";
|
||||
|
||||
export function HeroPanelPlaytime() {
|
||||
const [lastTimePlayed, setLastTimePlayed] = useState("");
|
||||
|
||||
|
||||
const { game, isGameRunning } = useContext(gameDetailsContext);
|
||||
const { t } = useTranslation("game_details");
|
||||
@@ -89,7 +88,7 @@ export function HeroPanelPlaytime() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<p
|
||||
<p
|
||||
className="hero-panel-playtime__play-time"
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-content={
|
||||
@@ -97,11 +96,15 @@ export function HeroPanelPlaytime() {
|
||||
? t("manual_playtime_tooltip")
|
||||
: undefined
|
||||
}
|
||||
data-tooltip-id={game.hasManuallyUpdatedPlaytime ? "manual-playtime-warning" : undefined}
|
||||
data-tooltip-id={
|
||||
game.hasManuallyUpdatedPlaytime
|
||||
? "manual-playtime-warning"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{game.hasManuallyUpdatedPlaytime && (
|
||||
<AlertFillIcon
|
||||
size={16}
|
||||
<AlertFillIcon
|
||||
size={16}
|
||||
className="hero-panel-playtime__manual-warning"
|
||||
/>
|
||||
)}
|
||||
@@ -119,7 +122,7 @@ export function HeroPanelPlaytime() {
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
{game.hasManuallyUpdatedPlaytime && (
|
||||
<Tooltip
|
||||
id="manual-playtime-warning"
|
||||
@@ -127,7 +130,6 @@ export function HeroPanelPlaytime() {
|
||||
zIndex: 9999,
|
||||
}}
|
||||
openOnClick={false}
|
||||
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -58,6 +58,34 @@
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
display: flex;
|
||||
gap: calc(globals.$spacing-unit);
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&__tab {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
padding: calc(globals.$spacing-unit) calc(globals.$spacing-unit * 2);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all ease 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: white;
|
||||
border-bottom-color: #c9aa71;
|
||||
}
|
||||
}
|
||||
|
||||
&__games-grid {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
||||
@@ -16,7 +16,8 @@ import "./profile-content.scss";
|
||||
const GAME_STATS_ANIMATION_DURATION_IN_MS = 3500;
|
||||
|
||||
export function ProfileContent() {
|
||||
const { userProfile, isMe, userStats } = useContext(userProfileContext);
|
||||
const { userProfile, isMe, userStats, libraryGames, pinnedGames } =
|
||||
useContext(userProfileContext);
|
||||
const [statsIndex, setStatsIndex] = useState(0);
|
||||
const [isAnimationRunning, setIsAnimationRunning] = useState(true);
|
||||
const statsAnimation = useRef(-1);
|
||||
@@ -79,7 +80,8 @@ export function ProfileContent() {
|
||||
return <LockedProfile />;
|
||||
}
|
||||
|
||||
const hasGames = userProfile?.libraryGames.length > 0;
|
||||
const hasGames = libraryGames.length > 0;
|
||||
const hasPinnedGames = pinnedGames.length > 0;
|
||||
|
||||
const shouldShowRightContent = hasGames || userProfile.friends.length > 0;
|
||||
|
||||
@@ -98,16 +100,36 @@ export function ProfileContent() {
|
||||
|
||||
{hasGames && (
|
||||
<>
|
||||
{hasPinnedGames && (
|
||||
<div style={{ marginBottom: "2rem" }}>
|
||||
<div className="profile-content__section-header">
|
||||
<h2>{t("pinned")}</h2>
|
||||
<span>{pinnedGames.length}</span>
|
||||
</div>
|
||||
|
||||
<ul className="profile-content__games-grid">
|
||||
{pinnedGames?.map((game) => (
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
key={game.objectId}
|
||||
statIndex={statsIndex}
|
||||
onMouseEnter={handleOnMouseEnterGameCard}
|
||||
onMouseLeave={handleOnMouseLeaveGameCard}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="profile-content__section-header">
|
||||
<h2>{t("library")}</h2>
|
||||
|
||||
{userStats && (
|
||||
<span>{numberFormatter.format(userStats.libraryCount)}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul className="profile-content__games-grid">
|
||||
{userProfile?.libraryGames?.map((game) => (
|
||||
{libraryGames?.map((game) => (
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
key={game.objectId}
|
||||
@@ -139,6 +161,8 @@ export function ProfileContent() {
|
||||
numberFormatter,
|
||||
t,
|
||||
statsIndex,
|
||||
libraryGames,
|
||||
pinnedGames,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -65,6 +65,20 @@
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
&__favorite-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
color: #ff6b6b;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__playtime {
|
||||
background-color: globals.$background-color;
|
||||
color: globals.$muted-color;
|
||||
|
||||
@@ -9,7 +9,12 @@ import {
|
||||
formatDownloadProgress,
|
||||
} from "@renderer/helpers";
|
||||
import { userProfileContext } from "@renderer/context";
|
||||
import { ClockIcon, TrophyIcon, AlertFillIcon } from "@primer/octicons-react";
|
||||
import {
|
||||
ClockIcon,
|
||||
TrophyIcon,
|
||||
AlertFillIcon,
|
||||
HeartFillIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -98,6 +103,11 @@ export function UserLibraryGameCard({
|
||||
onClick={() => navigate(buildUserGameDetailsPath(game))}
|
||||
>
|
||||
<div className="user-library-game__overlay">
|
||||
{game.isFavorite && (
|
||||
<div className="user-library-game__favorite-icon">
|
||||
<HeartFillIcon size={14} />
|
||||
</div>
|
||||
)}
|
||||
<small
|
||||
className="user-library-game__playtime"
|
||||
data-tooltip-place="top"
|
||||
|
||||
@@ -71,8 +71,17 @@ export type UserGame = {
|
||||
achievementCount: number;
|
||||
achievementsPointsEarnedSum: number;
|
||||
hasManuallyUpdatedPlaytime: boolean;
|
||||
isFavorite: boolean;
|
||||
isPinned: boolean;
|
||||
pinnedDate?: Date | null;
|
||||
} & ShopAssets;
|
||||
|
||||
export interface UserLibraryResponse {
|
||||
totalCount: number;
|
||||
library: UserGame[];
|
||||
pinnedGames: UserGame[];
|
||||
}
|
||||
|
||||
export interface GameRunning {
|
||||
id: string;
|
||||
title: string;
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface Game {
|
||||
executablePath?: string | null;
|
||||
launchOptions?: string | null;
|
||||
favorite?: boolean;
|
||||
pinned?: boolean;
|
||||
pinnedDate?: Date | null;
|
||||
automaticCloudSync?: boolean;
|
||||
hasManuallyUpdatedPlaytime?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user