From 8c0281844e5d9cfa07854132b9bad04c03dca829 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 20 Oct 2025 01:45:00 +0300 Subject: [PATCH] ci: fix lint errors --- .../achievements/upload-achievement-image.ts | 70 ++++++--- .../events/user/get-unlocked-achievements.ts | 5 +- .../achievements/merge-achievements.ts | 62 +++++--- src/main/services/screenshot.ts | 102 +++++++------ src/main/services/user/get-user-data.ts | 9 +- .../fullscreen-image-modal.scss | 2 +- .../fullscreen-image-modal.tsx | 19 ++- .../fullscreen-image-modal/index.ts | 2 +- .../user-profile/user-profile.context.tsx | 16 +- .../pages/achievements/achievement-list.tsx | 27 +++- .../profile-content/profile-content.scss | 22 ++- .../profile-content/profile-content.tsx | 143 ++++++++++-------- 12 files changed, 296 insertions(+), 183 deletions(-) diff --git a/src/main/events/achievements/upload-achievement-image.ts b/src/main/events/achievements/upload-achievement-image.ts index 73f4bbf5..4f771aca 100644 --- a/src/main/events/achievements/upload-achievement-image.ts +++ b/src/main/events/achievements/upload-achievement-image.ts @@ -17,9 +17,9 @@ const uploadImageToCDN = async (imagePath: string): Promise => { const fileSizeInBytes = stat.size; // Get presigned URL for achievement image - const response = await HydraApi.post<{ - presignedUrl: string; - achievementImageUrl: string; + const response = await HydraApi.post<{ + presignedUrl: string; + achievementImageUrl: string; }>("/presigned-urls/achievement-image", { imageExt: path.extname(imagePath).slice(1), imageLength: fileSizeInBytes, @@ -42,11 +42,11 @@ const uploadImageToCDN = async (imagePath: string): Promise => { */ const storeImageLocally = async (imagePath: string): Promise => { const fileBuffer = fs.readFileSync(imagePath); - const base64Image = fileBuffer.toString('base64'); + const base64Image = fileBuffer.toString("base64"); const mimeType = await fileTypeFromFile(imagePath); - + // Create a data URL for local storage - return `data:${mimeType?.mime || 'image/jpeg'};base64,${base64Image}`; + return `data:${mimeType?.mime || "image/jpeg"};base64,${base64Image}`; }; /** @@ -77,31 +77,42 @@ export const uploadAchievementImage = async ( let imageUrl: string; // Check if user has active subscription - const hasSubscription = await db - .get(levelKeys.user, { valueEncoding: "json" }) - .then((user) => { - const expiresAt = new Date(user?.subscription?.expiresAt ?? 0); - return expiresAt > new Date(); - }) - .catch(() => false); + const hasSubscription = await db + .get(levelKeys.user, { valueEncoding: "json" }) + .then((user) => { + const expiresAt = new Date(user?.subscription?.expiresAt ?? 0); + return expiresAt > new Date(); + }) + .catch(() => false); if (hasSubscription) { // Upload to CDN and update via API imageUrl = await uploadImageToCDN(imagePath); if (shop) { - await updateAchievementWithImageUrl(shop, gameId, achievementName, imageUrl); + await updateAchievementWithImageUrl( + shop, + gameId, + achievementName, + imageUrl + ); } - logger.log(`Achievement image uploaded to CDN for ${gameId}:${achievementName}`); + logger.log( + `Achievement image uploaded to CDN for ${gameId}:${achievementName}` + ); } else { // Store locally imageUrl = await storeImageLocally(imagePath); - logger.log(`Achievement image stored locally for ${gameId}:${achievementName}`); + logger.log( + `Achievement image stored locally for ${gameId}:${achievementName}` + ); } return { success: true, imageUrl }; - } catch (error) { - logger.error(`Failed to upload achievement image for ${gameId}:${achievementName}:`, error); + logger.error( + `Failed to upload achievement image for ${gameId}:${achievementName}:`, + error + ); throw error; } }; @@ -121,12 +132,19 @@ const uploadAchievementImageEvent = async ( const { imagePath, gameId, achievementName, shop } = params; try { - const result = await uploadAchievementImage(gameId, achievementName, imagePath, shop); + const result = await uploadAchievementImage( + gameId, + achievementName, + imagePath, + shop + ); // Update local database with image URL const achievementKey = levelKeys.game(shop, gameId); - const existingData = await gameAchievementsSublevel.get(achievementKey).catch(() => null); - + const existingData = await gameAchievementsSublevel + .get(achievementKey) + .catch(() => null); + if (existingData) { await gameAchievementsSublevel.put(achievementKey, { ...existingData, @@ -142,17 +160,19 @@ const uploadAchievementImageEvent = async ( } return result; - } catch (error) { // Clean up the temporary screenshot file even on error try { fs.unlinkSync(imagePath); } catch (cleanupError) { - logger.error(`Failed to cleanup screenshot file ${imagePath}:`, cleanupError); + logger.error( + `Failed to cleanup screenshot file ${imagePath}:`, + cleanupError + ); } - + throw error; } }; -registerEvent("uploadAchievementImage", uploadAchievementImageEvent); \ No newline at end of file +registerEvent("uploadAchievementImage", uploadAchievementImageEvent); diff --git a/src/main/events/user/get-unlocked-achievements.ts b/src/main/events/user/get-unlocked-achievements.ts index be0ce61e..3569ac3a 100644 --- a/src/main/events/user/get-unlocked-achievements.ts +++ b/src/main/events/user/get-unlocked-achievements.ts @@ -39,7 +39,7 @@ export const getUnlockedAchievements = async ( const userDetails = await db.get(levelKeys.user, { valueEncoding: "json", }); - + if (userDetails?.id) { remoteUserAchievements = await HydraApi.get( `/users/${userDetails.id}/games/achievements`, @@ -87,7 +87,8 @@ export const getUnlockedAchievements = async ( ...achievementData, unlocked: true, unlockTime: unlockedAchievementData.unlockTime, - achievementImageUrl: remoteAchievementData?.achievementImageUrl || null, + achievementImageUrl: + remoteAchievementData?.achievementImageUrl || null, }; } diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 9162ddc4..97e44bde 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -104,8 +104,6 @@ export const mergeAchievements = async ( publishNotification && userPreferences.achievementNotificationsEnabled !== false ) { - - const filteredAchievements = newAchievements .toSorted((a, b) => { return a.unlockTime - b.unlockTime; @@ -192,37 +190,57 @@ export const mergeAchievements = async ( } // Capture and upload screenshot AFTER achievements are synced to server - if (newAchievements.length && userPreferences.enableAchievementScreenshots === true) { + if ( + newAchievements.length && + userPreferences.enableAchievementScreenshots === true + ) { try { // Import and trigger the upload process - const { uploadAchievementImage } = await import("@main/events/achievements/upload-achievement-image"); - + const { uploadAchievementImage } = await import( + "@main/events/achievements/upload-achievement-image" + ); + // Upload the screenshot for each new achievement for (const achievement of newAchievements) { try { // Find the achievement data to get the display name - const achievementData = achievementsData.find((steamAchievement) => { - return ( - achievement.name.toUpperCase() === - steamAchievement.name.toUpperCase() - ); - }); - - const achievementDisplayName = achievementData?.displayName || achievement.name; - - // Capture screenshot with game title and achievement name - const screenshotPath = await ScreenshotService.captureDesktopScreenshot( - game.title, - achievementDisplayName + const achievementData = achievementsData.find( + (steamAchievement) => { + return ( + achievement.name.toUpperCase() === + steamAchievement.name.toUpperCase() + ); + } + ); + + const achievementDisplayName = + achievementData?.displayName || achievement.name; + + // Capture screenshot with game title and achievement name + const screenshotPath = + await ScreenshotService.captureDesktopScreenshot( + game.title, + achievementDisplayName + ); + + await uploadAchievementImage( + game.objectId, + achievement.name, + screenshotPath, + game.shop ); - - await uploadAchievementImage(game.objectId, achievement.name, screenshotPath, game.shop); } catch (error) { - achievementsLogger.error("Failed to upload achievement image", error); + achievementsLogger.error( + "Failed to upload achievement image", + error + ); } } } catch (error) { - achievementsLogger.error("Failed to capture screenshot for achievement", error); + achievementsLogger.error( + "Failed to capture screenshot for achievement", + error + ); } } }) diff --git a/src/main/services/screenshot.ts b/src/main/services/screenshot.ts index 907d945a..76912ecf 100644 --- a/src/main/services/screenshot.ts +++ b/src/main/services/screenshot.ts @@ -10,20 +10,21 @@ export class ScreenshotService { private static readonly MAX_WIDTH = 1280; // Maximum width for compression private static readonly MAX_HEIGHT = 720; // Maximum height for compression - /** * Compresses an image by resizing and adjusting quality */ - private static compressImage(image: Electron.NativeImage): Electron.NativeImage { + private static compressImage( + image: Electron.NativeImage + ): Electron.NativeImage { const size = image.getSize(); - + // Calculate new dimensions while maintaining aspect ratio let newWidth = size.width; let newHeight = size.height; - + if (newWidth > this.MAX_WIDTH || newHeight > this.MAX_HEIGHT) { const aspectRatio = newWidth / newHeight; - + if (newWidth > newHeight) { newWidth = this.MAX_WIDTH; newHeight = Math.round(newWidth / aspectRatio); @@ -32,21 +33,24 @@ export class ScreenshotService { newWidth = Math.round(newHeight * aspectRatio); } } - + // Resize the image if dimensions changed if (newWidth !== size.width || newHeight !== size.height) { return image.resize({ width: newWidth, height: newHeight }); } - + return image; } - public static async captureDesktopScreenshot(gameTitle?: string, achievementName?: string): Promise { + public static async captureDesktopScreenshot( + gameTitle?: string, + achievementName?: string + ): Promise { try { // Get all available desktop sources const sources = await desktopCapturer.getSources({ types: ["screen"], - thumbnailSize: { width: 1920, height: 1080 } + thumbnailSize: { width: 1920, height: 1080 }, }); if (sources.length === 0) { @@ -55,74 +59,79 @@ export class ScreenshotService { // Use the primary screen (first source) const primaryScreen = sources[0]; - + // Convert the thumbnail to a higher quality image - const originalImage = nativeImage.createFromDataURL(primaryScreen.thumbnail.toDataURL()); - + const originalImage = nativeImage.createFromDataURL( + primaryScreen.thumbnail.toDataURL() + ); + // Compress the image to reduce file size const compressedImage = this.compressImage(originalImage); - + // Create screenshots directory structure const userDataPath = app.getPath("userData"); const screenshotsDir = path.join(userDataPath, "screenshots"); - + let finalDir = screenshotsDir; let filename: string; - + if (gameTitle && achievementName) { // Create game-specific directory - const sanitizedGameTitle = gameTitle.replace(/[<>:"/\\|?*]/g, '_'); + const sanitizedGameTitle = gameTitle.replace(/[<>:"/\\|?*]/g, "_"); const gameDir = path.join(screenshotsDir, sanitizedGameTitle); finalDir = gameDir; - + // Use achievement name as filename (sanitized) - const sanitizedAchievementName = achievementName.replace(/[<>:"/\\|?*]/g, '_'); + const sanitizedAchievementName = achievementName.replace( + /[<>:"/\\|?*]/g, + "_" + ); filename = `${sanitizedAchievementName}.${this.SCREENSHOT_FORMAT}`; } else { // Fallback to timestamp-based naming const timestamp = Date.now(); filename = `achievement_screenshot_${timestamp}.${this.SCREENSHOT_FORMAT}`; } - + // Ensure directory exists if (!fs.existsSync(finalDir)) { fs.mkdirSync(finalDir, { recursive: true }); } - + const screenshotPath = path.join(finalDir, filename); - + // Save the compressed screenshot as JPEG with specified quality const jpegBuffer = compressedImage.toJPEG(this.SCREENSHOT_QUALITY); fs.writeFileSync(screenshotPath, jpegBuffer); - + logger.log(`Compressed screenshot saved to: ${screenshotPath}`); return screenshotPath; - } catch (error) { logger.error("Failed to capture desktop screenshot:", error); throw error; } } - public static async cleanupOldScreenshots(): Promise { try { const userDataPath = app.getPath("userData"); const screenshotsDir = path.join(userDataPath, "screenshots"); - + if (!fs.existsSync(screenshotsDir)) { return; } - + // Get all files recursively from screenshots directory and subdirectories - const getAllFiles = (dir: string): Array<{name: string, path: string, mtime: Date}> => { - const files: Array<{name: string, path: string, mtime: Date}> = []; - + const getAllFiles = ( + dir: string + ): Array<{ name: string; path: string; mtime: Date }> => { + const files: Array<{ name: string; path: string; mtime: Date }> = []; + const items = fs.readdirSync(dir); for (const item of items) { const itemPath = path.join(dir, item); const stat = fs.statSync(itemPath); - + if (stat.isDirectory()) { // Recursively get files from subdirectories files.push(...getAllFiles(itemPath)); @@ -130,20 +139,21 @@ export class ScreenshotService { files.push({ name: item, path: itemPath, - mtime: stat.mtime + mtime: stat.mtime, }); } } - + return files; }; - - const allFiles = getAllFiles(screenshotsDir) - .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); - + + const allFiles = getAllFiles(screenshotsDir).sort( + (a, b) => b.mtime.getTime() - a.mtime.getTime() + ); + // Keep only the 50 most recent screenshots (increased from 10 to accommodate multiple games) const filesToDelete = allFiles.slice(50); - + for (const file of filesToDelete) { try { fs.unlinkSync(file.path); @@ -152,11 +162,11 @@ export class ScreenshotService { logger.error(`Failed to delete screenshot ${file.name}:`, error); } } - + // Clean up empty directories const cleanupEmptyDirs = (dir: string) => { if (dir === screenshotsDir) return; // Don't delete the main screenshots directory - + try { const items = fs.readdirSync(dir); if (items.length === 0) { @@ -167,24 +177,24 @@ export class ScreenshotService { // Directory might not be empty or might not exist, ignore } }; - + // Check for empty game directories and clean them up - const gameDirectories = fs.readdirSync(screenshotsDir) - .map(item => path.join(screenshotsDir, item)) - .filter(itemPath => { + const gameDirectories = fs + .readdirSync(screenshotsDir) + .map((item) => path.join(screenshotsDir, item)) + .filter((itemPath) => { try { return fs.statSync(itemPath).isDirectory(); } catch { return false; } }); - + for (const gameDir of gameDirectories) { cleanupEmptyDirs(gameDir); } - } catch (error) { logger.error("Failed to cleanup old screenshots:", error); } } -} \ No newline at end of file +} diff --git a/src/main/services/user/get-user-data.ts b/src/main/services/user/get-user-data.ts index 26e7a1a3..04675913 100644 --- a/src/main/services/user/get-user-data.ts +++ b/src/main/services/user/get-user-data.ts @@ -1,4 +1,9 @@ -import { User, type ProfileVisibility, type UserDetails, type UserPreferences } from "@types"; +import { + User, + type ProfileVisibility, + type UserDetails, + type UserPreferences, +} from "@types"; import { HydraApi } from "../hydra-api"; import { UserNotLoggedInError } from "@shared"; import { logger } from "../logger"; @@ -13,7 +18,7 @@ export const getUserData = async () => { levelKeys.userPreferences, { valueEncoding: "json" } ); - + if (userPreferences?.language) { // Map supported languages (pt, ru, es) or fallback to en const supportedLanguages = ["pt", "ru", "es"]; diff --git a/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss b/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss index 3cd2fb24..439a05f0 100644 --- a/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss +++ b/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.scss @@ -103,4 +103,4 @@ padding: 60px 20px 20px; } } -} \ No newline at end of file +} diff --git a/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.tsx b/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.tsx index 43bcb154..fa0ca621 100644 --- a/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.tsx +++ b/src/renderer/src/components/fullscreen-image-modal/fullscreen-image-modal.tsx @@ -41,8 +41,21 @@ export function FullscreenImageModal({ } }; + const handleBackdropKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + return ( -
+
- +
); -} \ No newline at end of file +} diff --git a/src/renderer/src/components/fullscreen-image-modal/index.ts b/src/renderer/src/components/fullscreen-image-modal/index.ts index 48f84547..4275a349 100644 --- a/src/renderer/src/components/fullscreen-image-modal/index.ts +++ b/src/renderer/src/components/fullscreen-image-modal/index.ts @@ -1 +1 @@ -export { FullscreenImageModal } from "./fullscreen-image-modal"; \ No newline at end of file +export { FullscreenImageModal } from "./fullscreen-image-modal"; diff --git a/src/renderer/src/context/user-profile/user-profile.context.tsx b/src/renderer/src/context/user-profile/user-profile.context.tsx index 215ce600..eb8f5c70 100644 --- a/src/renderer/src/context/user-profile/user-profile.context.tsx +++ b/src/renderer/src/context/user-profile/user-profile.context.tsx @@ -134,8 +134,10 @@ export function UserProfileContextProvider({ // Get current language for API call const currentLanguage = i18n.language.split("-")[0]; const supportedLanguages = ["pt", "ru", "es"]; - const language = supportedLanguages.includes(currentLanguage) ? currentLanguage : "en"; - + const language = supportedLanguages.includes(currentLanguage) + ? currentLanguage + : "en"; + const params = new URLSearchParams({ language }); return window.electron.hydraApi @@ -153,7 +155,15 @@ export function UserProfileContextProvider({ showErrorToast(t("user_not_found")); navigate(-1); }); - }, [navigate, getUserStats, getUserLibraryGames, showErrorToast, userId, t, i18n]); + }, [ + navigate, + getUserStats, + getUserLibraryGames, + showErrorToast, + userId, + t, + i18n, + ]); const getBadges = useCallback(async () => { const language = i18n.language.split("-")[0]; diff --git a/src/renderer/src/pages/achievements/achievement-list.tsx b/src/renderer/src/pages/achievements/achievement-list.tsx index def100b6..2cf79a2d 100644 --- a/src/renderer/src/pages/achievements/achievement-list.tsx +++ b/src/renderer/src/pages/achievements/achievement-list.tsx @@ -71,8 +71,25 @@ export function AchievementList({ src={achievement.achievementImageUrl} alt={`${achievement.displayName} screenshot`} loading="lazy" - onClick={() => handleImageClick(achievement.achievementImageUrl!, achievement.displayName)} - style={{ cursor: 'pointer' }} + onClick={() => + handleImageClick( + achievement.achievementImageUrl, + achievement.displayName + ) + } + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleImageClick( + achievement.achievementImageUrl, + achievement.displayName + ); + } + }} + tabIndex={0} + role="button" + aria-label={`View ${achievement.displayName} screenshot in fullscreen`} + style={{ cursor: "pointer" }} />
@@ -80,7 +97,7 @@ export function AchievementList({
)} - + {achievement.points != undefined ? (
???

)} - + {achievement.unlockTime != null && (
))} - + )} - {userProfile?.achievements && userProfile.achievements.length > 0 && ( -
-
-
-

{t("souvenirs")}

- - {userProfile.achievements.length} - + {userProfile?.achievements && + userProfile.achievements.length > 0 && ( +
+
+
+

{t("souvenirs")}

+ + {userProfile.achievements.length} + +
-
-
- {userProfile.achievements.map((achievement, index) => ( -
-
-
- {achievement.name} handleImageClick(achievement.achievementImageUrl, achievement.name)} - style={{ cursor: 'pointer' }} - /> -
- +
+ {userProfile.achievements.map((achievement, index) => ( +
+
+
+ {achievement.name} + handleImageClick( + achievement.achievementImageUrl, + achievement.name + ) + } + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleImageClick(achievement.achievementImageUrl, achievement.name); + } + }} + tabIndex={0} + role="button" + aria-label={`View ${achievement.name} screenshot in fullscreen`} + style={{ cursor: "pointer" }} + /> +
+ +
-
- -
-
- - - {achievement.name} - -
- -
-
+ +
+
- - {achievement.gameTitle} + + {achievement.name}
- - {achievement.unlockTime && ( -
- {formatDateTime(achievement.unlockTime)} + +
+
+ + + {achievement.gameTitle} +
- )} + + {achievement.unlockTime && ( +
+ + {formatDateTime(achievement.unlockTime)} + +
+ )} +
+ +
- -
-
- ))} + ))} +
-
- )} + )} {hasAnyGames && ( @@ -343,7 +364,7 @@ export function ProfileContent() { {content} - +