Compare commits

..

1 Commits

Author SHA1 Message Date
Moyasee
96b12cb27e feat: enhance notifications functionality with sync and visibility change handling 2025-12-27 04:15:15 +02:00
5 changed files with 51 additions and 66 deletions

View File

@@ -35,27 +35,18 @@ export class DownloadManager {
): string | undefined {
if (originalUrl?.includes("#")) {
const hashPart = originalUrl.split("#")[1];
if (hashPart && !hashPart.startsWith("http") && hashPart.includes(".")) {
return hashPart;
}
if (hashPart && !hashPart.startsWith("http")) return hashPart;
}
if (url.includes("#")) {
const hashPart = url.split("#")[1];
if (hashPart && !hashPart.startsWith("http") && hashPart.includes(".")) {
return hashPart;
}
if (hashPart && !hashPart.startsWith("http")) return hashPart;
}
try {
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const pathParts = pathname.split("/");
const filename = pathParts[pathParts.length - 1];
if (filename?.includes(".") && filename.length > 0) {
return decodeURIComponent(filename);
}
const filename = urlObj.pathname.split("/").pop();
if (filename?.length) return filename;
} catch {
// Invalid URL
}
@@ -64,7 +55,7 @@ export class DownloadManager {
}
private static sanitizeFilename(filename: string): string {
return filename.replaceAll(/[<>:"/\\|?*]/g, "_");
return filename.replace(/[<>:"/\\|?*]/g, "_");
}
private static createDownloadPayload(
@@ -73,19 +64,13 @@ export class DownloadManager {
downloadId: string,
savePath: string
) {
const filename =
this.extractFilename(originalUrl, directUrl) ||
this.extractFilename(directUrl);
const filename = this.extractFilename(directUrl, originalUrl);
const sanitizedFilename = filename
? this.sanitizeFilename(filename)
: undefined;
if (sanitizedFilename) {
logger.log(`[DownloadManager] Using filename: ${sanitizedFilename}`);
} else {
logger.log(
`[DownloadManager] No filename extracted, aria2 will use default`
);
}
return {
@@ -242,10 +227,10 @@ export class DownloadManager {
)
) {
gameFilesManager.extractDownloadedFile();
} else if (download.folderName) {
} else {
gameFilesManager
.extractFilesInDirectory(
path.join(download.downloadPath, download.folderName)
path.join(download.downloadPath, download.folderName!)
)
.then(() => gameFilesManager.setExtractionComplete());
}

View File

@@ -221,26 +221,6 @@
left: 0;
z-index: 0;
}
&__cover-placeholder {
position: relative;
width: 100%;
height: 100%;
min-width: 100%;
min-height: 100%;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.08) 0%,
rgba(255, 255, 255, 0.04) 50%,
rgba(255, 255, 255, 0.08) 100%
);
border-radius: 4px;
color: rgba(255, 255, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 0;
}
}
@keyframes pulse {

View File

@@ -1,12 +1,7 @@
import { LibraryGame } from "@types";
import { useGameCard } from "@renderer/hooks";
import { memo, useState } from "react";
import {
ClockIcon,
AlertFillIcon,
TrophyIcon,
ImageIcon,
} from "@primer/octicons-react";
import { memo } from "react";
import { ClockIcon, AlertFillIcon, TrophyIcon } from "@primer/octicons-react";
import "./library-game-card.scss";
interface LibraryGameCardProps {
@@ -30,9 +25,14 @@ export const LibraryGameCard = memo(function LibraryGameCard({
const { formatPlayTime, handleCardClick, handleContextMenuClick } =
useGameCard(game, onContextMenu);
const coverImage = game.coverImageUrl?.replaceAll("\\", "/") ?? "";
const [imageError, setImageError] = useState(false);
const coverImage = (
game.customIconUrl ??
game.coverImageUrl ??
game.libraryImageUrl ??
game.libraryHeroImageUrl ??
game.iconUrl ??
""
).replaceAll("\\", "/");
return (
<button
@@ -98,19 +98,12 @@ export const LibraryGameCard = memo(function LibraryGameCard({
)}
</div>
{imageError || !coverImage ? (
<div className="library-game-card__cover-placeholder">
<ImageIcon size={48} />
</div>
) : (
<img
src={coverImage}
alt={game.title}
className="library-game-card__game-image"
loading="lazy"
onError={() => setImageError(true)}
/>
)}
<img
src={coverImage ?? undefined}
alt={game.title}
className="library-game-card__game-image"
loading="lazy"
/>
</button>
);
});

View File

@@ -130,6 +130,32 @@ export default function Notifications() {
return () => unsubscribe();
}, []);
useEffect(() => {
const unsubscribe = window.electron.onSyncNotificationCount(() => {
if (userDetails) {
fetchApiNotifications(0, false);
}
fetchLocalNotifications();
});
return () => unsubscribe();
}, [userDetails, fetchApiNotifications, fetchLocalNotifications]);
useEffect(() => {
const handleVisibilityChange = () => {
if (!document.hidden && userDetails) {
fetchApiNotifications(0, false);
fetchLocalNotifications();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [userDetails, fetchApiNotifications, fetchLocalNotifications]);
const mergedNotifications = useMemo<MergedNotification[]>(() => {
const sortByDate = (a: MergedNotification, b: MergedNotification) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();

View File

@@ -116,5 +116,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
}