fix: state fix and remake checking for overlap

This commit is contained in:
Moyasee
2025-09-30 00:52:46 +03:00
parent bd053a1635
commit 9689c19863
3 changed files with 93 additions and 144 deletions

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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 && (