Compare commits

..

11 Commits

Author SHA1 Message Date
Zamitto
20c0d3174b test 2025-11-12 14:37:44 -03:00
Zamitto
cd3fa10bf7 chore: fix version code
Some checks failed
Build / build (ubuntu-latest) (push) Has been cancelled
Build / build (windows-2022) (push) Has been cancelled
2025-11-11 18:27:53 -03:00
Zamitto
a57cc83076 Merge branch 'release/v3.7.5' 2025-11-11 18:17:53 -03:00
Zamitto
c75a6ad439 fix: using achievement count data from api 2025-11-11 18:15:26 -03:00
Moyase
05d68fa23b Merge pull request #1856 from hydralauncher/fix/library-game-card
fix: custom assets not being showed in library page
2025-11-11 22:10:24 +02:00
Moyasee
527a65e9bc feat: remembering the view user left the library and restoring it on opening library again 2025-11-11 22:07:42 +02:00
Moyasee
fe6bb5763d fix: deleting game from context menu doesnt work in library 2025-11-11 22:03:33 +02:00
Moyasee
002dff098c fix: custom assets not being showed in library page 2025-11-11 21:50:48 +02:00
Chubby Granny Chaser
436d1b74be ci: fixing format
Some checks failed
Build Renderer / build (push) Has been cancelled
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-2022) (push) Has been cancelled
2025-11-11 18:35:18 +00:00
Chubby Granny Chaser
b89de065fe ci: fixing format 2025-11-11 18:33:28 +00:00
Chubby Granny Chaser
7fcdab07cb Merge pull request #1842 from hydralauncher/feat/displaying-new-game-update
Feat: displaying new game update
2025-11-11 18:23:42 +00:00
11 changed files with 72 additions and 39 deletions

View File

@@ -4,7 +4,6 @@ import {
downloadsSublevel, downloadsSublevel,
gamesShopAssetsSublevel, gamesShopAssetsSublevel,
gamesSublevel, gamesSublevel,
gameAchievementsSublevel,
} from "@main/level"; } from "@main/level";
const getLibrary = async (): Promise<LibraryGame[]> => { const getLibrary = async (): Promise<LibraryGame[]> => {
@@ -19,33 +18,19 @@ const getLibrary = async (): Promise<LibraryGame[]> => {
const download = await downloadsSublevel.get(key); const download = await downloadsSublevel.get(key);
const gameAssets = await gamesShopAssetsSublevel.get(key); const gameAssets = await gamesShopAssetsSublevel.get(key);
let unlockedAchievementCount = 0;
let achievementCount = 0;
try {
const achievements = await gameAchievementsSublevel.get(key);
if (achievements) {
achievementCount = achievements.achievements.length;
unlockedAchievementCount =
achievements.unlockedAchievements.length;
}
} catch {
// No achievements data for this game
}
return { return {
id: key, id: key,
...game, ...game,
download: download ?? null, download: download ?? null,
unlockedAchievementCount, unlockedAchievementCount: game.unlockedAchievementCount ?? 0,
achievementCount, achievementCount: game.achievementCount ?? 0,
// Spread gameAssets last to ensure all image URLs are properly set // Spread gameAssets last to ensure all image URLs are properly set
...gameAssets, ...gameAssets,
// Preserve custom image URLs from game if they exist // Preserve custom image URLs from game if they exist
customIconUrl: game.customIconUrl, customIconUrl: game.customIconUrl,
customLogoImageUrl: game.customLogoImageUrl, customLogoImageUrl: game.customLogoImageUrl,
customHeroImageUrl: game.customHeroImageUrl, customHeroImageUrl: game.customHeroImageUrl,
} as LibraryGame; };
}) })
); );
}); });

View File

@@ -9,6 +9,8 @@ type ProfileGame = {
hasManuallyUpdatedPlaytime: boolean; hasManuallyUpdatedPlaytime: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isPinned?: boolean; isPinned?: boolean;
achievementCount: number;
unlockedAchievementCount: number;
} & ShopAssets; } & ShopAssets;
export const mergeWithRemoteGames = async () => { export const mergeWithRemoteGames = async () => {
@@ -39,6 +41,8 @@ export const mergeWithRemoteGames = async () => {
playTimeInMilliseconds: updatedPlayTime, playTimeInMilliseconds: updatedPlayTime,
favorite: game.isFavorite ?? localGame.favorite, favorite: game.isFavorite ?? localGame.favorite,
isPinned: game.isPinned ?? localGame.isPinned, isPinned: game.isPinned ?? localGame.isPinned,
achievementCount: game.achievementCount,
unlockedAchievementCount: game.unlockedAchievementCount,
}); });
} else { } else {
await gamesSublevel.put(gameKey, { await gamesSublevel.put(gameKey, {
@@ -55,6 +59,8 @@ export const mergeWithRemoteGames = async () => {
isDeleted: false, isDeleted: false,
favorite: game.isFavorite ?? false, favorite: game.isFavorite ?? false,
isPinned: game.isPinned ?? false, isPinned: game.isPinned ?? false,
achievementCount: game.achievementCount,
unlockedAchievementCount: game.unlockedAchievementCount,
}); });
} }

View File

@@ -47,7 +47,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
> >
<div className="game-card__backdrop"> <div className="game-card__backdrop">
<img <img
src={game.libraryImageUrl} src={game.libraryImageUrl ?? undefined}
alt={game.title} alt={game.title}
className="game-card__cover" className="game-card__cover"
loading="lazy" loading="lazy"

View File

@@ -50,14 +50,14 @@ export function Hero() {
> >
<div className="hero__backdrop"> <div className="hero__backdrop">
<img <img
src={game.libraryHeroImageUrl} src={game.libraryHeroImageUrl ?? undefined}
alt={game.description ?? ""} alt={game.description ?? ""}
className="hero__media" className="hero__media"
/> />
<div className="hero__content"> <div className="hero__content">
<img <img
src={game.logoImageUrl} src={game.logoImageUrl ?? undefined}
width="250px" width="250px"
alt={game.description ?? ""} alt={game.description ?? ""}
loading="eager" loading="eager"

View File

@@ -128,7 +128,6 @@
border: 1px solid rgba(34, 197, 94, 0.5); border: 1px solid rgba(34, 197, 94, 0.5);
} }
&__section-header { &__section-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@@ -7,9 +7,25 @@ export function useLibrary() {
const library = useAppSelector((state) => state.library.value); const library = useAppSelector((state) => state.library.value);
const updateLibrary = useCallback(async () => { const updateLibrary = useCallback(async () => {
return window.electron return window.electron.getLibrary().then(async (updatedLibrary) => {
.getLibrary() const libraryWithAchievements = await Promise.all(
.then((updatedLibrary) => dispatch(setLibrary(updatedLibrary))); updatedLibrary.map(async (game) => {
const unlockedAchievements =
await window.electron.getUnlockedAchievements(
game.objectId,
game.shop
);
return {
...game,
unlockedAchievementCount:
game.unlockedAchievementCount || unlockedAchievements.length,
};
})
);
dispatch(setLibrary(libraryWithAchievements));
});
}, [dispatch]); }, [dispatch]);
return { library, updateLibrary }; return { library, updateLibrary };

View File

@@ -12,12 +12,18 @@ interface LibraryGameCardLargeProps {
) => void; ) => void;
} }
const normalizePathForCss = (url: string | null | undefined): string => {
if (!url) return "";
return url.replaceAll("\\", "/");
};
const getImageWithCustomPriority = ( const getImageWithCustomPriority = (
customUrl: string | null | undefined, customUrl: string | null | undefined,
originalUrl: string | null | undefined, originalUrl: string | null | undefined,
fallbackUrl?: string | null | undefined fallbackUrl?: string | null | undefined
) => { ) => {
return customUrl || originalUrl || fallbackUrl || ""; const selectedUrl = customUrl || originalUrl || fallbackUrl || "";
return normalizePathForCss(selectedUrl);
}; };
export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({
@@ -30,15 +36,21 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({
const backgroundImage = useMemo( const backgroundImage = useMemo(
() => () =>
getImageWithCustomPriority( getImageWithCustomPriority(
game.customHeroImageUrl,
game.libraryHeroImageUrl, game.libraryHeroImageUrl,
game.libraryImageUrl, game.libraryImageUrl ?? game.iconUrl
game.iconUrl
), ),
[game.libraryHeroImageUrl, game.libraryImageUrl, game.iconUrl] [
game.customHeroImageUrl,
game.libraryHeroImageUrl,
game.libraryImageUrl,
game.iconUrl,
]
); );
const backgroundStyle = useMemo( const backgroundStyle = useMemo(
() => ({ backgroundImage: `url(${backgroundImage})` }), () =>
backgroundImage ? { backgroundImage: `url(${backgroundImage})` } : {},
[backgroundImage] [backgroundImage]
); );
@@ -49,7 +61,7 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({
[game.unlockedAchievementCount, game.achievementCount] [game.unlockedAchievementCount, game.achievementCount]
); );
const logoImage = game.logoImageUrl; const logoImage = game.customLogoImageUrl ?? game.logoImageUrl;
return ( return (
<button <button

View File

@@ -25,12 +25,14 @@ export const LibraryGameCard = memo(function LibraryGameCard({
const { formatPlayTime, handleCardClick, handleContextMenuClick } = const { formatPlayTime, handleCardClick, handleContextMenuClick } =
useGameCard(game, onContextMenu); useGameCard(game, onContextMenu);
const coverImage = const coverImage = (
game.customIconUrl ??
game.coverImageUrl ?? game.coverImageUrl ??
game.libraryImageUrl ?? game.libraryImageUrl ??
game.libraryHeroImageUrl ?? game.libraryHeroImageUrl ??
game.iconUrl ?? game.iconUrl ??
undefined; ""
).replaceAll("\\", "/");
return ( return (
<button <button

View File

@@ -19,7 +19,10 @@ export default function Library() {
onLibraryBatchComplete?: (cb: () => void) => () => void; onLibraryBatchComplete?: (cb: () => void) => () => void;
}; };
const [viewMode, setViewMode] = useState<ViewMode>("compact"); const [viewMode, setViewMode] = useState<ViewMode>(() => {
const savedViewMode = localStorage.getItem("library-view-mode");
return (savedViewMode as ViewMode) || "compact";
});
const [filterBy, setFilterBy] = useState<FilterOption>("all"); const [filterBy, setFilterBy] = useState<FilterOption>("all");
const [contextMenu, setContextMenu] = useState<{ const [contextMenu, setContextMenu] = useState<{
game: LibraryGame | null; game: LibraryGame | null;
@@ -31,6 +34,11 @@ export default function Library() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation("library"); const { t } = useTranslation("library");
const handleViewModeChange = useCallback((mode: ViewMode) => {
setViewMode(mode);
localStorage.setItem("library-view-mode", mode);
}, []);
useEffect(() => { useEffect(() => {
dispatch(setHeaderTitle(t("library"))); dispatch(setHeaderTitle(t("library")));
const electron = (globalThis as unknown as { electron?: ElectronAPI }) const electron = (globalThis as unknown as { electron?: ElectronAPI })
@@ -71,7 +79,7 @@ export default function Library() {
); );
const handleCloseContextMenu = useCallback(() => { const handleCloseContextMenu = useCallback(() => {
setContextMenu({ game: null, visible: false, position: { x: 0, y: 0 } }); setContextMenu((prev) => ({ ...prev, visible: false }));
}, []); }, []);
const filteredLibrary = useMemo(() => { const filteredLibrary = useMemo(() => {
@@ -147,7 +155,10 @@ export default function Library() {
</div> </div>
<div className="library__controls-right"> <div className="library__controls-right">
<ViewOptions viewMode={viewMode} onViewModeChange={setViewMode} /> <ViewOptions
viewMode={viewMode}
onViewModeChange={handleViewModeChange}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -42,9 +42,9 @@ export interface ShopAssets {
shop: GameShop; shop: GameShop;
title: string; title: string;
iconUrl: string | null; iconUrl: string | null;
libraryHeroImageUrl: string; libraryHeroImageUrl: string | null;
libraryImageUrl: string; libraryImageUrl: string | null;
logoImageUrl: string; logoImageUrl: string | null;
logoPosition: string | null; logoPosition: string | null;
coverImageUrl: string | null; coverImageUrl: string | null;
downloadSources: string[]; downloadSources: string[];

View File

@@ -56,6 +56,8 @@ export interface Game {
launchOptions?: string | null; launchOptions?: string | null;
favorite?: boolean; favorite?: boolean;
isPinned?: boolean; isPinned?: boolean;
achievementCount?: number;
unlockedAchievementCount?: number;
pinnedDate?: Date | null; pinnedDate?: Date | null;
automaticCloudSync?: boolean; automaticCloudSync?: boolean;
hasManuallyUpdatedPlaytime?: boolean; hasManuallyUpdatedPlaytime?: boolean;