mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-26 20:31:03 +00:00
feat: added pin/unpin to the game card in profile
This commit is contained in:
@@ -14,6 +14,7 @@ export interface UserProfileContext {
|
|||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
userStats: UserStats | null;
|
userStats: UserStats | null;
|
||||||
getUserProfile: () => Promise<void>;
|
getUserProfile: () => Promise<void>;
|
||||||
|
getUserLibraryGames: () => Promise<void>;
|
||||||
setSelectedBackgroundImage: React.Dispatch<React.SetStateAction<string>>;
|
setSelectedBackgroundImage: React.Dispatch<React.SetStateAction<string>>;
|
||||||
backgroundImage: string;
|
backgroundImage: string;
|
||||||
badges: Badge[];
|
badges: Badge[];
|
||||||
@@ -29,6 +30,7 @@ export const userProfileContext = createContext<UserProfileContext>({
|
|||||||
isMe: false,
|
isMe: false,
|
||||||
userStats: null,
|
userStats: null,
|
||||||
getUserProfile: async () => {},
|
getUserProfile: async () => {},
|
||||||
|
getUserLibraryGames: async () => {},
|
||||||
setSelectedBackgroundImage: () => {},
|
setSelectedBackgroundImage: () => {},
|
||||||
backgroundImage: "",
|
backgroundImage: "",
|
||||||
badges: [],
|
badges: [],
|
||||||
@@ -149,6 +151,7 @@ export function UserProfileContextProvider({
|
|||||||
heroBackground,
|
heroBackground,
|
||||||
isMe,
|
isMe,
|
||||||
getUserProfile,
|
getUserProfile,
|
||||||
|
getUserLibraryGames,
|
||||||
setSelectedBackgroundImage,
|
setSelectedBackgroundImage,
|
||||||
backgroundImage: getBackgroundImageUrl(),
|
backgroundImage: getBackgroundImageUrl(),
|
||||||
userStats,
|
userStats,
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ export function ProfileContent() {
|
|||||||
const hasPinnedGames = pinnedGames.length > 0;
|
const hasPinnedGames = pinnedGames.length > 0;
|
||||||
const hasAnyGames = hasGames || hasPinnedGames;
|
const hasAnyGames = hasGames || hasPinnedGames;
|
||||||
|
|
||||||
const shouldShowRightContent = hasAnyGames || userProfile.friends.length > 0;
|
const shouldShowRightContent =
|
||||||
|
hasAnyGames || userProfile.friends.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="profile-content__section">
|
<section className="profile-content__section">
|
||||||
@@ -127,7 +128,9 @@ export function ProfileContent() {
|
|||||||
<div className="profile-content__section-header">
|
<div className="profile-content__section-header">
|
||||||
<h2>{t("library")}</h2>
|
<h2>{t("library")}</h2>
|
||||||
{userStats && (
|
{userStats && (
|
||||||
<span>{numberFormatter.format(userStats.libraryCount)}</span>
|
<span>
|
||||||
|
{numberFormatter.format(userStats.libraryCount)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -65,18 +65,45 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__favorite-icon {
|
&__actions-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
color: #ff6b6b;
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__favorite-icon {
|
||||||
|
color: white;
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 4px;
|
padding: 6px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 2;
|
}
|
||||||
|
|
||||||
|
&__pin-button {
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__playtime {
|
&__playtime {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { UserGame } from "@types";
|
import { UserGame } from "@types";
|
||||||
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
||||||
import { useFormat } from "@renderer/hooks";
|
import { useFormat, useToast } from "@renderer/hooks";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useCallback, useContext, useState } from "react";
|
import { useCallback, useContext, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
TrophyIcon,
|
TrophyIcon,
|
||||||
AlertFillIcon,
|
AlertFillIcon,
|
||||||
HeartFillIcon,
|
HeartFillIcon,
|
||||||
|
PinIcon,
|
||||||
|
PinSlashIcon,
|
||||||
} from "@primer/octicons-react";
|
} from "@primer/octicons-react";
|
||||||
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
|
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
@@ -33,11 +35,14 @@ export function UserLibraryGameCard({
|
|||||||
onMouseEnter,
|
onMouseEnter,
|
||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
}: UserLibraryGameCardProps) {
|
}: UserLibraryGameCardProps) {
|
||||||
const { userProfile } = useContext(userProfileContext);
|
const { userProfile, isMe, getUserLibraryGames } = useContext(userProfileContext);
|
||||||
const { t } = useTranslation("user_profile");
|
const { t } = useTranslation("user_profile");
|
||||||
|
const { t: tGame } = useTranslation("game_details");
|
||||||
const { numberFormatter } = useFormat();
|
const { numberFormatter } = useFormat();
|
||||||
|
const { showSuccessToast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isTooltipHovered, setIsTooltipHovered] = useState(false);
|
const [isTooltipHovered, setIsTooltipHovered] = useState(false);
|
||||||
|
const [isPinning, setIsPinning] = useState(false);
|
||||||
|
|
||||||
const getStatsItemCount = useCallback(() => {
|
const getStatsItemCount = useCallback(() => {
|
||||||
let statsCount = 1;
|
let statsCount = 1;
|
||||||
@@ -89,6 +94,30 @@ export function UserLibraryGameCard({
|
|||||||
[numberFormatter, t]
|
[numberFormatter, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const toggleGamePinned = async () => {
|
||||||
|
setIsPinning(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (game.isPinned) {
|
||||||
|
await window.electron.removeGameFromPinned(game.shop, game.objectId).then(() => {
|
||||||
|
showSuccessToast(tGame("game_removed_from_pinned"));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await window.electron.addGameToPinned(game.shop, game.objectId).then(() => {
|
||||||
|
showSuccessToast(tGame("game_added_to_pinned"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a small delay to allow server synchronization before refreshing
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// Refresh the library games to update the UI
|
||||||
|
await getUserLibraryGames();
|
||||||
|
} finally {
|
||||||
|
setIsPinning(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<li
|
<li
|
||||||
@@ -103,9 +132,26 @@ export function UserLibraryGameCard({
|
|||||||
onClick={() => navigate(buildUserGameDetailsPath(game))}
|
onClick={() => navigate(buildUserGameDetailsPath(game))}
|
||||||
>
|
>
|
||||||
<div className="user-library-game__overlay">
|
<div className="user-library-game__overlay">
|
||||||
{game.isFavorite && (
|
{(game.isFavorite || isMe) && (
|
||||||
<div className="user-library-game__favorite-icon">
|
<div className="user-library-game__actions-container">
|
||||||
<HeartFillIcon size={14} />
|
{game.isFavorite && (
|
||||||
|
<div className="user-library-game__favorite-icon">
|
||||||
|
<HeartFillIcon size={12} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isMe && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="user-library-game__pin-button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleGamePinned();
|
||||||
|
}}
|
||||||
|
disabled={isPinning}
|
||||||
|
>
|
||||||
|
{game.isPinned ? <PinSlashIcon size={12} /> : <PinIcon size={12} />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<small
|
<small
|
||||||
|
|||||||
Reference in New Issue
Block a user