mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
fix: state fix and remake checking for overlap
This commit is contained in:
@@ -29,19 +29,24 @@ export function EditGameModal({
|
||||
const { showSuccessToast, showErrorToast } = useToast();
|
||||
|
||||
const [gameName, setGameName] = useState("");
|
||||
const [iconPath, setIconPath] = useState("");
|
||||
const [logoPath, setLogoPath] = useState("");
|
||||
const [heroPath, setHeroPath] = useState("");
|
||||
const [iconDisplayPath, setIconDisplayPath] = useState("");
|
||||
const [logoDisplayPath, setLogoDisplayPath] = useState("");
|
||||
const [heroDisplayPath, setHeroDisplayPath] = useState("");
|
||||
const [assetPaths, setAssetPaths] = useState({
|
||||
icon: "",
|
||||
logo: "",
|
||||
hero: "",
|
||||
});
|
||||
const [assetDisplayPaths, setAssetDisplayPaths] = useState({
|
||||
icon: "",
|
||||
logo: "",
|
||||
hero: "",
|
||||
});
|
||||
const [defaultUrls, setDefaultUrls] = useState({
|
||||
icon: null as string | null,
|
||||
logo: null as string | null,
|
||||
hero: null as string | null,
|
||||
});
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [selectedAssetType, setSelectedAssetType] = useState<AssetType>("icon");
|
||||
|
||||
const [defaultIconUrl, setDefaultIconUrl] = useState<string | null>(null);
|
||||
const [defaultLogoUrl, setDefaultLogoUrl] = useState<string | null>(null);
|
||||
const [defaultHeroUrl, setDefaultHeroUrl] = useState<string | null>(null);
|
||||
|
||||
const isCustomGame = (game: LibraryGame | Game): boolean => {
|
||||
return game.shop === "custom";
|
||||
};
|
||||
@@ -51,32 +56,36 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
const setCustomGameAssets = useCallback((game: LibraryGame | Game) => {
|
||||
setIconPath(extractLocalPath(game.iconUrl));
|
||||
setLogoPath(extractLocalPath(game.logoImageUrl));
|
||||
setHeroPath(extractLocalPath(game.libraryHeroImageUrl));
|
||||
setIconDisplayPath(extractLocalPath(game.iconUrl));
|
||||
setLogoDisplayPath(extractLocalPath(game.logoImageUrl));
|
||||
setHeroDisplayPath(extractLocalPath(game.libraryHeroImageUrl));
|
||||
setAssetPaths({
|
||||
icon: extractLocalPath(game.iconUrl),
|
||||
logo: extractLocalPath(game.logoImageUrl),
|
||||
hero: extractLocalPath(game.libraryHeroImageUrl),
|
||||
});
|
||||
setAssetDisplayPaths({
|
||||
icon: extractLocalPath(game.iconUrl),
|
||||
logo: extractLocalPath(game.logoImageUrl),
|
||||
hero: extractLocalPath(game.libraryHeroImageUrl),
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setNonCustomGameAssets = useCallback(
|
||||
(game: LibraryGame) => {
|
||||
setIconPath(extractLocalPath(game.customIconUrl));
|
||||
setLogoPath(extractLocalPath(game.customLogoImageUrl));
|
||||
setHeroPath(extractLocalPath(game.customHeroImageUrl));
|
||||
setIconDisplayPath(extractLocalPath(game.customIconUrl));
|
||||
setLogoDisplayPath(extractLocalPath(game.customLogoImageUrl));
|
||||
setHeroDisplayPath(extractLocalPath(game.customHeroImageUrl));
|
||||
setAssetPaths({
|
||||
icon: extractLocalPath(game.customIconUrl),
|
||||
logo: extractLocalPath(game.customLogoImageUrl),
|
||||
hero: extractLocalPath(game.customHeroImageUrl),
|
||||
});
|
||||
setAssetDisplayPaths({
|
||||
icon: extractLocalPath(game.customIconUrl),
|
||||
logo: extractLocalPath(game.customLogoImageUrl),
|
||||
hero: extractLocalPath(game.customHeroImageUrl),
|
||||
});
|
||||
|
||||
setDefaultIconUrl(shopDetails?.assets?.iconUrl || game.iconUrl || null);
|
||||
setDefaultLogoUrl(
|
||||
shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null
|
||||
);
|
||||
setDefaultHeroUrl(
|
||||
shopDetails?.assets?.libraryHeroImageUrl ||
|
||||
game.libraryHeroImageUrl ||
|
||||
null
|
||||
);
|
||||
setDefaultUrls({
|
||||
icon: shopDetails?.assets?.iconUrl || game.iconUrl || null,
|
||||
logo: shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null,
|
||||
hero: shopDetails?.assets?.libraryHeroImageUrl || game.libraryHeroImageUrl || null,
|
||||
});
|
||||
},
|
||||
[shopDetails]
|
||||
);
|
||||
@@ -102,64 +111,23 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
const getAssetPath = (assetType: AssetType): string => {
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
return iconPath;
|
||||
case "logo":
|
||||
return logoPath;
|
||||
case "hero":
|
||||
return heroPath;
|
||||
}
|
||||
return assetPaths[assetType];
|
||||
};
|
||||
|
||||
const getAssetDisplayPath = (assetType: AssetType): string => {
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
return iconDisplayPath;
|
||||
case "logo":
|
||||
return logoDisplayPath;
|
||||
case "hero":
|
||||
return heroDisplayPath;
|
||||
}
|
||||
return assetDisplayPaths[assetType];
|
||||
};
|
||||
|
||||
const setAssetPath = (assetType: AssetType, path: string): void => {
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
setIconPath(path);
|
||||
break;
|
||||
case "logo":
|
||||
setLogoPath(path);
|
||||
break;
|
||||
case "hero":
|
||||
setHeroPath(path);
|
||||
break;
|
||||
}
|
||||
setAssetPaths(prev => ({ ...prev, [assetType]: path }));
|
||||
};
|
||||
|
||||
const setAssetDisplayPath = (assetType: AssetType, path: string): void => {
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
setIconDisplayPath(path);
|
||||
break;
|
||||
case "logo":
|
||||
setLogoDisplayPath(path);
|
||||
break;
|
||||
case "hero":
|
||||
setHeroDisplayPath(path);
|
||||
break;
|
||||
}
|
||||
setAssetDisplayPaths(prev => ({ ...prev, [assetType]: path }));
|
||||
};
|
||||
|
||||
const getDefaultUrl = (assetType: AssetType): string | null => {
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
return defaultIconUrl;
|
||||
case "logo":
|
||||
return defaultLogoUrl;
|
||||
case "hero":
|
||||
return defaultHeroUrl;
|
||||
}
|
||||
return defaultUrls[assetType];
|
||||
};
|
||||
|
||||
const handleSelectAsset = async (assetType: AssetType) => {
|
||||
@@ -324,10 +292,10 @@ export function EditGameModal({
|
||||
|
||||
// Helper function to prepare custom game assets
|
||||
const prepareCustomGameAssets = (game: LibraryGame | Game) => {
|
||||
const iconUrl = iconPath ? `local:${iconPath}` : game.iconUrl;
|
||||
const logoImageUrl = logoPath ? `local:${logoPath}` : game.logoImageUrl;
|
||||
const libraryHeroImageUrl = heroPath
|
||||
? `local:${heroPath}`
|
||||
const iconUrl = assetPaths.icon ? `local:${assetPaths.icon}` : game.iconUrl;
|
||||
const logoImageUrl = assetPaths.logo ? `local:${assetPaths.logo}` : game.logoImageUrl;
|
||||
const libraryHeroImageUrl = assetPaths.hero
|
||||
? `local:${assetPaths.hero}`
|
||||
: game.libraryHeroImageUrl;
|
||||
|
||||
return { iconUrl, logoImageUrl, libraryHeroImageUrl };
|
||||
@@ -336,9 +304,9 @@ export function EditGameModal({
|
||||
// Helper function to prepare non-custom game assets
|
||||
const prepareNonCustomGameAssets = () => {
|
||||
return {
|
||||
customIconUrl: iconPath ? `local:${iconPath}` : null,
|
||||
customLogoImageUrl: logoPath ? `local:${logoPath}` : null,
|
||||
customHeroImageUrl: heroPath ? `local:${heroPath}` : null,
|
||||
customIconUrl: assetPaths.icon ? `local:${assetPaths.icon}` : null,
|
||||
customLogoImageUrl: assetPaths.logo ? `local:${assetPaths.logo}` : null,
|
||||
customHeroImageUrl: assetPaths.hero ? `local:${assetPaths.hero}` : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -407,9 +375,11 @@ export function EditGameModal({
|
||||
if (isCustomGame(game)) {
|
||||
setCustomGameAssets(game);
|
||||
// Clear default URLs for custom games
|
||||
setDefaultIconUrl(null);
|
||||
setDefaultLogoUrl(null);
|
||||
setDefaultHeroUrl(null);
|
||||
setDefaultUrls({
|
||||
icon: null,
|
||||
logo: null,
|
||||
hero: null,
|
||||
});
|
||||
} else {
|
||||
setNonCustomGameAssets(game as LibraryGame);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
display: flex;
|
||||
transition: all ease 0.2s;
|
||||
cursor: grab;
|
||||
container-type: inline-size;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
@@ -160,6 +161,25 @@
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
&-long {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&-short {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// When the card is narrow (less than 180px), show short format
|
||||
@container (max-width: 180px) {
|
||||
&-long {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-short {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__manual-playtime {
|
||||
color: globals.$warning-color;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UserGame } from "@types";
|
||||
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
||||
import { useFormat, useToast } from "@renderer/hooks";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useCallback, useContext, useState, useEffect, useRef } from "react";
|
||||
import { useCallback, useContext, useState } from "react";
|
||||
import {
|
||||
buildGameAchievementPath,
|
||||
buildGameDetailsPath,
|
||||
@@ -43,9 +43,7 @@ export function UserLibraryGameCard({
|
||||
const navigate = useNavigate();
|
||||
const [isTooltipHovered, setIsTooltipHovered] = useState(false);
|
||||
const [isPinning, setIsPinning] = useState(false);
|
||||
const [useShortFormat, setUseShortFormat] = useState(false);
|
||||
const cardRef = useRef<HTMLLIElement>(null);
|
||||
const playtimeRef = useRef<HTMLElement>(null);
|
||||
|
||||
|
||||
const getStatsItemCount = useCallback(() => {
|
||||
let statsCount = 1;
|
||||
@@ -82,64 +80,25 @@ export function UserLibraryGameCard({
|
||||
};
|
||||
|
||||
const formatPlayTime = useCallback(
|
||||
(playTimeInSeconds = 0) => {
|
||||
(playTimeInSeconds = 0, isShort = false) => {
|
||||
const minutes = playTimeInSeconds / 60;
|
||||
|
||||
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
|
||||
return t("amount_minutes", {
|
||||
return t(isShort ? "amount_minutes_short" : "amount_minutes", {
|
||||
amount: minutes.toFixed(0),
|
||||
});
|
||||
}
|
||||
|
||||
const hours = minutes / 60;
|
||||
return t("amount_hours", { amount: numberFormatter.format(hours) });
|
||||
const hoursKey = isShort ? "amount_hours_short" : "amount_hours";
|
||||
const hoursAmount = isShort ? Math.floor(hours) : numberFormatter.format(hours);
|
||||
|
||||
return t(hoursKey, { amount: hoursAmount });
|
||||
},
|
||||
[numberFormatter, t]
|
||||
);
|
||||
|
||||
const formatPlayTimeShort = useCallback(
|
||||
(playTimeInSeconds = 0) => {
|
||||
const minutes = playTimeInSeconds / 60;
|
||||
|
||||
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
|
||||
return t("amount_minutes_short", {
|
||||
amount: minutes.toFixed(0),
|
||||
});
|
||||
}
|
||||
|
||||
const hours = minutes / 60;
|
||||
return t("amount_hours_short", { amount: Math.floor(hours) });
|
||||
},
|
||||
[t]
|
||||
);
|
||||
|
||||
const checkForOverlap = useCallback(() => {
|
||||
if (!cardRef.current || !playtimeRef.current) return;
|
||||
|
||||
const cardWidth = cardRef.current.offsetWidth;
|
||||
const hasButtons = game.isFavorite || isMe;
|
||||
|
||||
if (hasButtons && cardWidth < 180) {
|
||||
setUseShortFormat(true);
|
||||
} else {
|
||||
setUseShortFormat(false);
|
||||
}
|
||||
}, [game.isFavorite, isMe]);
|
||||
|
||||
useEffect(() => {
|
||||
checkForOverlap();
|
||||
|
||||
const handleResize = () => {
|
||||
checkForOverlap();
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [checkForOverlap]);
|
||||
|
||||
useEffect(() => {
|
||||
checkForOverlap();
|
||||
}, [game.isFavorite, isMe, checkForOverlap]);
|
||||
|
||||
const toggleGamePinned = async () => {
|
||||
setIsPinning(true);
|
||||
@@ -166,7 +125,6 @@ export function UserLibraryGameCard({
|
||||
return (
|
||||
<>
|
||||
<li
|
||||
ref={cardRef}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className="user-library-game__wrapper"
|
||||
@@ -204,8 +162,7 @@ export function UserLibraryGameCard({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<small
|
||||
ref={playtimeRef}
|
||||
<div
|
||||
className="user-library-game__playtime"
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-content={
|
||||
@@ -223,11 +180,13 @@ export function UserLibraryGameCard({
|
||||
) : (
|
||||
<ClockIcon size={11} />
|
||||
)}
|
||||
{useShortFormat
|
||||
? formatPlayTimeShort(game.playTimeInSeconds)
|
||||
: formatPlayTime(game.playTimeInSeconds)
|
||||
}
|
||||
</small>
|
||||
<span className="user-library-game__playtime-long">
|
||||
{formatPlayTime(game.playTimeInSeconds)}
|
||||
</span>
|
||||
<span className="user-library-game__playtime-short">
|
||||
{formatPlayTime(game.playTimeInSeconds, true)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{userProfile?.hasActiveSubscription &&
|
||||
game.achievementCount > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user