From 96cfa8c015d0587a149143d5a768ab1e1741773f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 May 2025 16:37:49 -0300 Subject: [PATCH 01/22] feat: re adding achievement notification window --- src/main/index.ts | 1 + .../achievement-watcher-manager.ts | 10 ++ .../achievements/merge-achievements.ts | 9 +- src/main/services/window-manager.ts | 44 +++++- src/preload/index.ts | 27 +++- src/renderer/src/declaration.d.ts | 11 +- src/renderer/src/main.tsx | 5 + .../achievement-notification.scss | 43 ++++++ .../notification/achievement-notification.tsx | 146 ++++++++++++++++++ 9 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 src/renderer/src/pages/achievements/notification/achievement-notification.scss create mode 100644 src/renderer/src/pages/achievements/notification/achievement-notification.tsx diff --git a/src/main/index.ts b/src/main/index.ts index 3b223299..5151b956 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -73,6 +73,7 @@ app.whenReady().then(async () => { WindowManager.createMainWindow(); } + WindowManager.createNotificationWindow(); WindowManager.createSystemTray(language || "en"); }); diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 8b076d9e..97381743 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -12,6 +12,8 @@ import { achievementsLogger } from "../logger"; import { Cracker } from "@shared"; import { publishCombinedNewAchievementNotification } from "../notifications"; import { gamesSublevel } from "@main/level"; +import { WindowManager } from "../window-manager"; +import { sleep } from "@main/helpers"; const fileStats: Map = new Map(); const fltFiles: Map> = new Map(); @@ -231,6 +233,8 @@ export class AchievementWatcherManager { }; public static async preSearchAchievements() { + await sleep(5000); + try { const newAchievementsCount = process.platform === "win32" @@ -246,6 +250,12 @@ export class AchievementWatcherManager { ); if (totalNewAchievements > 0) { + WindowManager.notificationWindow?.webContents.send( + "on-combined-achievements-unlocked", + totalNewGamesWithAchievements, + totalNewAchievements + ); + publishCombinedNewAchievementNotification( totalNewAchievements, totalNewGamesWithAchievements diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 0385f2fc..8fff5033 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -66,7 +66,7 @@ export const mergeAchievements = async ( const newAchievements = [...newAchievementsMap.values()] .filter((achievement) => { - return !unlockedAchievements.some((localAchievement) => { + return !unlockedAchievements.slice(1).some((localAchievement) => { return ( localAchievement.name.toUpperCase() === achievement.name.toUpperCase() ); @@ -106,6 +106,13 @@ export const mergeAchievements = async ( }; }); + WindowManager.notificationWindow?.webContents.send( + "on-achievement-unlocked", + game.objectId, + game.shop, + achievementsInfo + ); + publishNewAchievementNotification({ achievements: achievementsInfo, unlockedAchievementCount: mergedLocalAchievements.length, diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 9841eb6e..ea7afa82 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -23,6 +23,7 @@ import { isStaging } from "@main/constants"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; + public static notificationWindow: Electron.BrowserWindow | null = null; private static readonly editorWindows: Map = new Map(); @@ -259,6 +260,47 @@ export class WindowManager { } } + private static loadNotificationWindowURL() { + if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { + this.notificationWindow?.loadURL( + `${process.env["ELECTRON_RENDERER_URL"]}#/achievement-notification` + ); + } else { + this.notificationWindow?.loadFile( + path.join(__dirname, "../renderer/index.html"), + { + hash: "achievement-notification", + } + ); + } + } + + public static createNotificationWindow() { + this.notificationWindow = new BrowserWindow({ + transparent: true, + maximizable: false, + autoHideMenuBar: true, + minimizable: false, + focusable: false, + skipTaskbar: true, + frame: false, + width: 350, + height: 104, + x: 0, + y: 0, + webPreferences: { + preload: path.join(__dirname, "../preload/index.mjs"), + sandbox: false, + }, + }); + this.notificationWindow.setIgnoreMouseEvents(true); + // this.notificationWindow.setVisibleOnAllWorkspaces(true, { + // visibleOnFullScreen: true, + // }); + this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); + this.loadNotificationWindowURL(); + } + public static openEditorWindow(themeId: string) { if (this.mainWindow) { const existingWindow = this.editorWindows.get(themeId); @@ -277,7 +319,7 @@ export class WindowManager { minHeight: 540, backgroundColor: "#1c1c1c", titleBarStyle: process.platform === "linux" ? "default" : "hidden", - ...(process.platform === "linux" ? { icon } : {}), + icon, trafficLightPosition: { x: 16, y: 16 }, titleBarOverlay: { symbolColor: "#DADBE1", diff --git a/src/preload/index.ts b/src/preload/index.ts index 981901d3..3edf6b0c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -207,12 +207,35 @@ contextBridge.exposeInMainWorld("electron", { return () => ipcRenderer.removeListener("on-library-batch-complete", listener); }, - onAchievementUnlocked: (cb: () => void) => { - const listener = (_event: Electron.IpcRendererEvent) => cb(); + onAchievementUnlocked: ( + cb: ( + objectId: string, + shop: GameShop, + achievements?: { displayName: string; iconUrl: string }[] + ) => void + ) => { + const listener = ( + _event: Electron.IpcRendererEvent, + objectId: string, + shop: GameShop, + achievements?: { displayName: string; iconUrl: string }[] + ) => cb(objectId, shop, achievements); ipcRenderer.on("on-achievement-unlocked", listener); return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); }, + onCombinedAchievementsUnlocked: ( + cb: (gameCount: number, achievementsCount: number) => void + ) => { + const listener = ( + _event: Electron.IpcRendererEvent, + gameCount: number, + achievementCount: number + ) => cb(gameCount, achievementCount); + ipcRenderer.on("on-combined-achievements-unlocked", listener); + return () => + ipcRenderer.removeListener("on-combined-achievements-unlocked", listener); + }, onExtractionComplete: (cb: (shop: GameShop, objectId: string) => void) => { const listener = ( _event: Electron.IpcRendererEvent, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 0dee5767..28032d9a 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -175,7 +175,16 @@ declare global { minimized: boolean; }) => Promise; extractGameDownload: (shop: GameShop, objectId: string) => Promise; - onAchievementUnlocked: (cb: () => void) => () => Electron.IpcRenderer; + onAchievementUnlocked: ( + cb: ( + objectId: string, + shop: GameShop, + achievements?: { displayName: string; iconUrl: string }[] + ) => void + ) => () => Electron.IpcRenderer; + onCombinedAchievementsUnlocked: ( + cb: (gameCount: number, achievementCount: number) => void + ) => () => Electron.IpcRenderer; onExtractionComplete: ( cb: (shop: GameShop, objectId: string) => void ) => () => Electron.IpcRenderer; diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index bb0a3e03..c4cc1fef 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -30,6 +30,7 @@ import Settings from "./pages/settings/settings"; import Profile from "./pages/profile/profile"; import Achievements from "./pages/achievements/achievements"; import ThemeEditor from "./pages/theme-editor/theme-editor"; +import { AchievementNotification } from "./pages/achievements/notification/achievement-notification"; Sentry.init({ dsn: import.meta.env.RENDERER_VITE_SENTRY_DSN, @@ -84,6 +85,10 @@ ReactDOM.createRoot(document.getElementById("root")!).render( } /> + } + /> diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.scss b/src/renderer/src/pages/achievements/notification/achievement-notification.scss new file mode 100644 index 00000000..51f90432 --- /dev/null +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.scss @@ -0,0 +1,43 @@ +@use "../../../scss/globals.scss"; + +@keyframes achievement-in { + 0% { + transform: translateY(-240px); + } + 100% { + transform: translateY(0); + } +} + +@keyframes achievement-out { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-240px); + } +} + +.achievement-notification { + margin-top: 24px; + margin-left: 24px; + animation-duration: 1s; + height: 60px; + display: flex; + animation-name: achievement-in; + transform: translateY(0); + + &.closing { + animation-name: achievement-out; + transform: translateY(-240px); + } + + &__content { + display: flex; + flex-direction: row; + gap: 8px; + align-items: center; + background: globals.$background-color; + padding-right: 8px; + } +} diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx new file mode 100644 index 00000000..9aac12ad --- /dev/null +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -0,0 +1,146 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import achievementSound from "@renderer/assets/audio/achievement.wav"; +import { useTranslation } from "react-i18next"; +import cn from "classnames"; +import "./achievement-notification.scss"; + +interface AchievementInfo { + displayName: string; + iconUrl: string; +} + +const NOTIFICATION_TIMEOUT = 4000; + +export function AchievementNotification() { + const { t } = useTranslation("achievement"); + + const [isClosing, setIsClosing] = useState(false); + const [isVisible, setIsVisible] = useState(false); + + const [achievements, setAchievements] = useState([]); + const [currentAchievement, setCurrentAchievement] = + useState(null); + + const achievementAnimation = useRef(-1); + const closingAnimation = useRef(-1); + const visibleAnimation = useRef(-1); + + const playAudio = useCallback(() => { + const audio = new Audio(achievementSound); + audio.volume = 0.1; + audio.play(); + }, []); + + useEffect(() => { + const unsubscribe = window.electron.onCombinedAchievementsUnlocked( + (gameCount, achievementCount) => { + if (gameCount === 0 || achievementCount === 0) return; + + setAchievements([ + { + displayName: t("new_achievements_unlocked", { + gameCount, + achievementCount, + }), + iconUrl: + "https://avatars.githubusercontent.com/u/164102380?s=400&u=01a13a7b4f0c642f7e547b8e1d70440ea06fa750&v=4", + }, + ]); + + playAudio(); + } + ); + + return () => { + unsubscribe(); + }; + }, [t, playAudio]); + + useEffect(() => { + const unsubscribe = window.electron.onAchievementUnlocked( + (_object, _shop, achievements) => { + if (!achievements?.length) return; + + setAchievements((ach) => ach.concat(achievements)); + + playAudio(); + } + ); + + return () => { + unsubscribe(); + }; + }, [playAudio]); + + const hasAchievementsPending = achievements.length > 0; + + const startAnimateClosing = useCallback(() => { + cancelAnimationFrame(closingAnimation.current); + cancelAnimationFrame(visibleAnimation.current); + cancelAnimationFrame(achievementAnimation.current); + + setIsClosing(true); + + const zero = performance.now(); + closingAnimation.current = requestAnimationFrame( + function animateClosing(time) { + if (time - zero <= 1000) { + closingAnimation.current = requestAnimationFrame(animateClosing); + } else { + setIsVisible(false); + } + } + ); + }, []); + + useEffect(() => { + if (hasAchievementsPending) { + setIsClosing(false); + setIsVisible(true); + + let zero = performance.now(); + cancelAnimationFrame(closingAnimation.current); + cancelAnimationFrame(visibleAnimation.current); + cancelAnimationFrame(achievementAnimation.current); + achievementAnimation.current = requestAnimationFrame( + function animateLock(time) { + if (time - zero > NOTIFICATION_TIMEOUT) { + zero = performance.now(); + setAchievements((ach) => ach.slice(1)); + } + achievementAnimation.current = requestAnimationFrame(animateLock); + } + ); + } else { + startAnimateClosing(); + } + }, [hasAchievementsPending, startAnimateClosing]); + + useEffect(() => { + if (achievements.length) { + setCurrentAchievement(achievements[0]); + } + }, [achievements]); + + if (!isVisible || !currentAchievement) return null; + + return ( +
+
+ {currentAchievement.displayName} +
+

{t("achievement_unlocked")}

+

{currentAchievement.displayName}

+
+
+
+ ); +} From 96385d90d853ba6a5241860a58042896b23b0749 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 May 2025 17:42:30 -0300 Subject: [PATCH 02/22] feat: custom achievement notification position --- src/main/events/index.ts | 1 + ...chievement-notification-window-position.ts | 19 +++++ .../achievements/merge-achievements.ts | 2 +- src/main/services/window-manager.ts | 84 +++++++++++++++++-- src/preload/index.ts | 66 ++++++++------- src/renderer/src/declaration.d.ts | 22 ++--- .../notification/achievement-notification.tsx | 20 +++++ .../src/pages/settings/settings-general.tsx | 72 ++++++++++++++-- src/types/level.types.ts | 10 +++ 9 files changed, 242 insertions(+), 54 deletions(-) create mode 100644 src/main/events/notifications/update-achievement-notification-window-position.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index acc589f9..ce41b369 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -86,6 +86,7 @@ import "./cloud-save/upload-save-game"; import "./cloud-save/delete-game-artifact"; import "./cloud-save/select-game-backup-path"; import "./notifications/publish-new-repacks-notification"; +import "./notifications/update-achievement-notification-window-position"; import "./themes/add-custom-theme"; import "./themes/delete-custom-theme"; import "./themes/get-all-custom-themes"; diff --git a/src/main/events/notifications/update-achievement-notification-window-position.ts b/src/main/events/notifications/update-achievement-notification-window-position.ts new file mode 100644 index 00000000..3ba6d660 --- /dev/null +++ b/src/main/events/notifications/update-achievement-notification-window-position.ts @@ -0,0 +1,19 @@ +import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; + +const updateAchievementCustomNotificationWindowPosition = async ( + _event: Electron.IpcMainInvokeEvent +) => { + const { x, y } = await WindowManager.getNotificationWindowPosition(); + + WindowManager.notificationWindow?.setPosition(x, y); + + WindowManager.notificationWindow?.webContents.send( + "on-test-achievement-notification" + ); +}; + +registerEvent( + "updateAchievementCustomNotificationWindowPosition", + updateAchievementCustomNotificationWindowPosition +); diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 8fff5033..a06d9652 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -66,7 +66,7 @@ export const mergeAchievements = async ( const newAchievements = [...newAchievementsMap.values()] .filter((achievement) => { - return !unlockedAchievements.slice(1).some((localAchievement) => { + return !unlockedAchievements.some((localAchievement) => { return ( localAchievement.name.toUpperCase() === achievement.name.toUpperCase() ); diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index ea7afa82..5c080c09 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -6,6 +6,7 @@ import { Tray, app, nativeImage, + screen, shell, } from "electron"; import { is } from "@electron-toolkit/utils"; @@ -275,7 +276,74 @@ export class WindowManager { } } - public static createNotificationWindow() { + private static readonly NOTIFICATION_WINDOW_WIDTH = 350; + private static readonly NOTIFICATION_WINDOW_HEIGHT = 104; + + public static async getNotificationWindowPosition() { + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + + const display = screen.getPrimaryDisplay(); + const { width, height } = display.workAreaSize; + + if ( + userPreferences?.achievementCustomNotificationPosition === "bottom_center" + ) { + return { + x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, + y: height - this.NOTIFICATION_WINDOW_HEIGHT, + }; + } + + if ( + userPreferences?.achievementCustomNotificationPosition === "bottom_right" + ) { + return { + x: width - this.NOTIFICATION_WINDOW_WIDTH, + y: height - this.NOTIFICATION_WINDOW_HEIGHT, + }; + } + + if ( + userPreferences?.achievementCustomNotificationPosition === "top_center" + ) { + return { + x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, + y: 0, + }; + } + + if ( + userPreferences?.achievementCustomNotificationPosition === "bottom_left" + ) { + return { + x: 0, + y: height - this.NOTIFICATION_WINDOW_HEIGHT, + }; + } + + if ( + userPreferences?.achievementCustomNotificationPosition === "top_right" + ) { + return { + x: width - this.NOTIFICATION_WINDOW_WIDTH, + y: 0, + }; + } + + return { + x: 0, + y: 0, + }; + } + + public static async createNotificationWindow() { + const { x, y } = await this.getNotificationWindowPosition(); + this.notificationWindow = new BrowserWindow({ transparent: true, maximizable: false, @@ -284,10 +352,10 @@ export class WindowManager { focusable: false, skipTaskbar: true, frame: false, - width: 350, - height: 104, - x: 0, - y: 0, + width: this.NOTIFICATION_WINDOW_WIDTH, + height: this.NOTIFICATION_WINDOW_HEIGHT, + x, + y, webPreferences: { preload: path.join(__dirname, "../preload/index.mjs"), sandbox: false, @@ -299,6 +367,12 @@ export class WindowManager { // }); this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); this.loadNotificationWindowURL(); + + this.notificationWindow.once("ready-to-show", () => { + if (isStaging) { + this.notificationWindow?.webContents.openDevTools(); + } + }); } public static openEditorWindow(themeId: string) { diff --git a/src/preload/index.ts b/src/preload/index.ts index 3edf6b0c..33384289 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -207,35 +207,6 @@ contextBridge.exposeInMainWorld("electron", { return () => ipcRenderer.removeListener("on-library-batch-complete", listener); }, - onAchievementUnlocked: ( - cb: ( - objectId: string, - shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] - ) => void - ) => { - const listener = ( - _event: Electron.IpcRendererEvent, - objectId: string, - shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] - ) => cb(objectId, shop, achievements); - ipcRenderer.on("on-achievement-unlocked", listener); - return () => - ipcRenderer.removeListener("on-achievement-unlocked", listener); - }, - onCombinedAchievementsUnlocked: ( - cb: (gameCount: number, achievementsCount: number) => void - ) => { - const listener = ( - _event: Electron.IpcRendererEvent, - gameCount: number, - achievementCount: number - ) => cb(gameCount, achievementCount); - ipcRenderer.on("on-combined-achievements-unlocked", listener); - return () => - ipcRenderer.removeListener("on-combined-achievements-unlocked", listener); - }, onExtractionComplete: (cb: (shop: GameShop, objectId: string) => void) => { const listener = ( _event: Electron.IpcRendererEvent, @@ -433,6 +404,43 @@ contextBridge.exposeInMainWorld("electron", { /* Notifications */ publishNewRepacksNotification: (newRepacksCount: number) => ipcRenderer.invoke("publishNewRepacksNotification", newRepacksCount), + onAchievementUnlocked: ( + cb: ( + objectId: string, + shop: GameShop, + achievements?: { displayName: string; iconUrl: string }[] + ) => void + ) => { + const listener = ( + _event: Electron.IpcRendererEvent, + objectId: string, + shop: GameShop, + achievements?: { displayName: string; iconUrl: string }[] + ) => cb(objectId, shop, achievements); + ipcRenderer.on("on-achievement-unlocked", listener); + return () => + ipcRenderer.removeListener("on-achievement-unlocked", listener); + }, + onCombinedAchievementsUnlocked: ( + cb: (gameCount: number, achievementsCount: number) => void + ) => { + const listener = ( + _event: Electron.IpcRendererEvent, + gameCount: number, + achievementCount: number + ) => cb(gameCount, achievementCount); + ipcRenderer.on("on-combined-achievements-unlocked", listener); + return () => + ipcRenderer.removeListener("on-combined-achievements-unlocked", listener); + }, + onTestAchievementNotification: (cb: () => void) => { + const listener = (_event: Electron.IpcRendererEvent) => cb(); + ipcRenderer.on("on-test-achievement-notification", listener); + return () => + ipcRenderer.removeListener("on-test-achievement-notification", listener); + }, + updateAchievementCustomNotificationWindowPosition: () => + ipcRenderer.invoke("updateAchievementCustomNotificationWindowPosition"), /* Themes */ addCustomTheme: (theme: Theme) => ipcRenderer.invoke("addCustomTheme", theme), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 28032d9a..eaa4cfcf 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -175,16 +175,6 @@ declare global { minimized: boolean; }) => Promise; extractGameDownload: (shop: GameShop, objectId: string) => Promise; - onAchievementUnlocked: ( - cb: ( - objectId: string, - shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] - ) => void - ) => () => Electron.IpcRenderer; - onCombinedAchievementsUnlocked: ( - cb: (gameCount: number, achievementCount: number) => void - ) => () => Electron.IpcRenderer; onExtractionComplete: ( cb: (shop: GameShop, objectId: string) => void ) => () => Electron.IpcRenderer; @@ -331,6 +321,18 @@ declare global { /* Notifications */ publishNewRepacksNotification: (newRepacksCount: number) => Promise; + onAchievementUnlocked: ( + cb: ( + objectId: string, + shop: GameShop, + achievements?: { displayName: string; iconUrl: string }[] + ) => void + ) => () => Electron.IpcRenderer; + onCombinedAchievementsUnlocked: ( + cb: (gameCount: number, achievementCount: number) => void + ) => () => Electron.IpcRenderer; + onTestAchievementNotification: (cb: () => void) => Electron.IpcRenderer; + updateAchievementCustomNotificationWindowPosition: () => Promise; /* Themes */ addCustomTheme: (theme: Theme) => Promise; diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 9aac12ad..49f06888 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -72,6 +72,26 @@ export function AchievementNotification() { }; }, [playAudio]); + useEffect(() => { + const unsubscribe = window.electron.onTestAchievementNotification(() => { + setAchievements((ach) => + ach.concat([ + { + displayName: "Test Achievement", + iconUrl: + "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fc.tenor.com%2FRwKr7hVnXREAAAAC%2Fnyan-cat.gif&f=1&nofb=1&ipt=706fd8b00cbfb5b2d2621603834d5f32c0f34cce7113de228d2fcc2247a80318", + }, + ]) + ); + + playAudio(); + }); + + return () => { + unsubscribe(); + }; + }, [playAudio]); + const hasAchievementsPending = achievements.length > 0; const startAnimateClosing = useCallback(() => { diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 14faf883..b04c57cc 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import { TextField, Button, @@ -14,6 +14,7 @@ import { settingsContext } from "@renderer/context"; import "./settings-general.scss"; import { DesktopDownloadIcon } from "@primer/octicons-react"; import { logger } from "@renderer/logger"; +import { AchievementCustomNotificationPosition } from "@types"; interface LanguageOption { option: string; @@ -36,10 +37,12 @@ export function SettingsGeneral() { downloadsPath: "", downloadNotificationsEnabled: false, repackUpdatesNotificationsEnabled: false, - achievementNotificationsEnabled: false, friendRequestNotificationsEnabled: false, + achievementNotificationsEnabled: false, + achievementCustomNotificationsEnabled: true, + achievementCustomNotificationPosition: + "top_left" as AchievementCustomNotificationPosition, language: "", - customStyles: window.localStorage.getItem("customStyles") || "", }); @@ -102,6 +105,10 @@ export function SettingsGeneral() { userPreferences.repackUpdatesNotificationsEnabled ?? false, achievementNotificationsEnabled: userPreferences.achievementNotificationsEnabled ?? false, + achievementCustomNotificationsEnabled: + userPreferences.achievementCustomNotificationsEnabled ?? true, + achievementCustomNotificationPosition: + userPreferences.achievementCustomNotificationPosition ?? "top_left", friendRequestNotificationsEnabled: userPreferences.friendRequestNotificationsEnabled ?? false, language: language ?? "en", @@ -109,6 +116,21 @@ export function SettingsGeneral() { } }, [userPreferences, defaultDownloadsPath]); + const achievementCustomNotificationPositionOptions = useMemo(() => { + return [ + "top_left", + "top_right", + "bottom_left", + "bottom_right", + "top_center", + "bottom_center", + ].map((position) => ({ + key: position, + value: position, + label: t(position), + })); + }, [t]); + const handleLanguageChange = ( event: React.ChangeEvent ) => { @@ -118,9 +140,19 @@ export function SettingsGeneral() { changeLanguage(value); }; - const handleChange = (values: Partial) => { + const handleChange = async (values: Partial) => { setForm((prev) => ({ ...prev, ...values })); - updateUserPreferences(values); + await updateUserPreferences(values); + }; + + const handleChangeAchievementCustomNotificationPosition = async ( + event: React.ChangeEvent + ) => { + const value = event.target.value as AchievementCustomNotificationPosition; + + await handleChange({ achievementCustomNotificationPosition: value }); + + window.electron.updateAchievementCustomNotificationWindowPosition(); }; const handleChooseDownloadsPath = async () => { @@ -205,6 +237,17 @@ export function SettingsGeneral() { } /> + + handleChange({ + friendRequestNotificationsEnabled: + !form.friendRequestNotificationsEnabled, + }) + } + /> + handleChange({ - friendRequestNotificationsEnabled: - !form.friendRequestNotificationsEnabled, + achievementCustomNotificationsEnabled: + !form.achievementCustomNotificationsEnabled, }) } /> + {form.achievementNotificationsEnabled && + form.achievementCustomNotificationsEnabled && ( + + )} +

{t("common_redist")}

diff --git a/src/types/level.types.ts b/src/types/level.types.ts index 764998af..bdbb72a7 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -70,6 +70,14 @@ export interface GameAchievement { cacheExpiresTimestamp: number | undefined; } +export type AchievementCustomNotificationPosition = + | "top_left" + | "top_center" + | "top_right" + | "bottom_left" + | "bottom_center" + | "bottom_right"; + export interface UserPreferences { downloadsPath?: string | null; language?: string; @@ -86,6 +94,8 @@ export interface UserPreferences { downloadNotificationsEnabled?: boolean; repackUpdatesNotificationsEnabled?: boolean; achievementNotificationsEnabled?: boolean; + achievementCustomNotificationsEnabled?: boolean; + achievementCustomNotificationPosition?: AchievementCustomNotificationPosition; friendRequestNotificationsEnabled?: boolean; showDownloadSpeedInMegabytes?: boolean; extractFilesByDefault?: boolean; From f960bb4f6f54a3ab1040ee96e7a6e096d60e02ca Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 May 2025 19:52:43 -0300 Subject: [PATCH 03/22] feat: set achievements cache only if game has achievements --- src/main/services/achievements/get-game-achievement-data.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 0f351dcb..f4d66b6a 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -38,7 +38,9 @@ export const getGameAchievementData = async ( await gameAchievementsSublevel.put(levelKeys.game(shop, objectId), { unlockedAchievements: cachedAchievements?.unlockedAchievements ?? [], achievements, - cacheExpiresTimestamp: Date.now() + 1000 * 60 * 30, // 30 minutes + cacheExpiresTimestamp: achievements.length + ? Date.now() + 1000 * 60 * 30 // 30 minutes + : undefined, }); return achievements; From 6f43da8d2804fd701c9f4b734d87624896be07ce Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 15 May 2025 19:42:23 -0300 Subject: [PATCH 04/22] feat: achievement notification custom position and animations --- src/main/events/index.ts | 2 +- ...chievement-notification-window-position.ts | 19 -- .../update-achievement-notification-window.ts | 29 ++ .../achievement-watcher-manager.ts | 80 ++++-- .../achievements/merge-achievements.ts | 31 +- src/main/services/window-manager.ts | 69 +++-- src/preload/index.ts | 45 ++- src/renderer/src/assets/icons/ellipses.png | Bin 0 -> 3675 bytes src/renderer/src/assets/icons/trophy.png | Bin 0 -> 417 bytes src/renderer/src/declaration.d.ts | 22 +- .../achievement-notification.scss | 264 ++++++++++++++++-- .../notification/achievement-notification.tsx | 69 +++-- .../src/pages/settings/settings-general.tsx | 24 +- 13 files changed, 502 insertions(+), 152 deletions(-) delete mode 100644 src/main/events/notifications/update-achievement-notification-window-position.ts create mode 100644 src/main/events/notifications/update-achievement-notification-window.ts create mode 100644 src/renderer/src/assets/icons/ellipses.png create mode 100644 src/renderer/src/assets/icons/trophy.png diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 660d7f48..d937e3c4 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -87,7 +87,7 @@ import "./cloud-save/upload-save-game"; import "./cloud-save/delete-game-artifact"; import "./cloud-save/select-game-backup-path"; import "./notifications/publish-new-repacks-notification"; -import "./notifications/update-achievement-notification-window-position"; +import "./notifications/update-achievement-notification-window"; import "./themes/add-custom-theme"; import "./themes/delete-custom-theme"; import "./themes/get-all-custom-themes"; diff --git a/src/main/events/notifications/update-achievement-notification-window-position.ts b/src/main/events/notifications/update-achievement-notification-window-position.ts deleted file mode 100644 index 3ba6d660..00000000 --- a/src/main/events/notifications/update-achievement-notification-window-position.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { registerEvent } from "../register-event"; -import { WindowManager } from "@main/services"; - -const updateAchievementCustomNotificationWindowPosition = async ( - _event: Electron.IpcMainInvokeEvent -) => { - const { x, y } = await WindowManager.getNotificationWindowPosition(); - - WindowManager.notificationWindow?.setPosition(x, y); - - WindowManager.notificationWindow?.webContents.send( - "on-test-achievement-notification" - ); -}; - -registerEvent( - "updateAchievementCustomNotificationWindowPosition", - updateAchievementCustomNotificationWindowPosition -); diff --git a/src/main/events/notifications/update-achievement-notification-window.ts b/src/main/events/notifications/update-achievement-notification-window.ts new file mode 100644 index 00000000..93a2feb7 --- /dev/null +++ b/src/main/events/notifications/update-achievement-notification-window.ts @@ -0,0 +1,29 @@ +import { db, levelKeys } from "@main/level"; +import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; +import { UserPreferences } from "@types"; + +const updateAchievementCustomNotificationWindow = async ( + _event: Electron.IpcMainInvokeEvent +) => { + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + + WindowManager.closeNotificationWindow(); + + if ( + userPreferences.achievementNotificationsEnabled && + userPreferences.achievementCustomNotificationsEnabled !== false + ) { + WindowManager.createNotificationWindow(true); + } +}; + +registerEvent( + "updateAchievementCustomNotificationWindow", + updateAchievementCustomNotificationWindow +); diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 97381743..97746ca0 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -7,11 +7,16 @@ import { findAllAchievementFiles, getAlternativeObjectIds, } from "./find-achivement-files"; -import type { AchievementFile, Game, UnlockedAchievement } from "@types"; +import type { + AchievementFile, + Game, + UnlockedAchievement, + UserPreferences, +} from "@types"; import { achievementsLogger } from "../logger"; import { Cracker } from "@shared"; import { publishCombinedNewAchievementNotification } from "../notifications"; -import { gamesSublevel } from "@main/level"; +import { db, gamesSublevel, levelKeys } from "@main/level"; import { WindowManager } from "../window-manager"; import { sleep } from "@main/helpers"; @@ -186,7 +191,7 @@ export class AchievementWatcherManager { return mergeAchievements(game, unlockedAchievements, false); } - private static preSearchAchievementsWindows = async () => { + private static async getGameAchievementFilesWindows() { const games = await gamesSublevel .values() .all() @@ -196,24 +201,24 @@ export class AchievementWatcherManager { return Promise.all( games.map((game) => { - const gameAchievementFiles: AchievementFile[] = []; + const achievementFiles: AchievementFile[] = []; for (const objectId of getAlternativeObjectIds(game.objectId)) { - gameAchievementFiles.push( + achievementFiles.push( ...(gameAchievementFilesMap.get(objectId) || []) ); - gameAchievementFiles.push( + achievementFiles.push( ...findAchievementFileInExecutableDirectory(game) ); } - return this.preProcessGameAchievementFiles(game, gameAchievementFiles); + return { game, achievementFiles }; }) ); - }; + } - private static preSearchAchievementsWithWine = async () => { + private static async getGameAchievementFilesLinux() { const games = await gamesSublevel .values() .all() @@ -221,45 +226,70 @@ export class AchievementWatcherManager { return Promise.all( games.map((game) => { - const gameAchievementFiles = findAchievementFiles(game); + const achievementFiles = findAchievementFiles(game); const achievementFileInsideDirectory = findAchievementFileInExecutableDirectory(game); - gameAchievementFiles.push(...achievementFileInsideDirectory); + achievementFiles.push(...achievementFileInsideDirectory); - return this.preProcessGameAchievementFiles(game, gameAchievementFiles); + return { game, achievementFiles }; }) ); - }; + } public static async preSearchAchievements() { - await sleep(5000); + await sleep(2000); try { - const newAchievementsCount = + const gameAchievementFiles = process.platform === "win32" - ? await this.preSearchAchievementsWindows() - : await this.preSearchAchievementsWithWine(); + ? await this.getGameAchievementFilesWindows() + : await this.getGameAchievementFilesLinux(); + + const newAchievementsCount: number[] = []; + + for (const { game, achievementFiles } of gameAchievementFiles) { + const result = await this.preProcessGameAchievementFiles( + game, + achievementFiles + ); + + newAchievementsCount.push(result); + } const totalNewGamesWithAchievements = newAchievementsCount.filter( (achievements) => achievements ).length; + const totalNewAchievements = newAchievementsCount.reduce( (acc, val) => acc + val, 0 ); if (totalNewAchievements > 0) { - WindowManager.notificationWindow?.webContents.send( - "on-combined-achievements-unlocked", - totalNewGamesWithAchievements, - totalNewAchievements + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } ); - publishCombinedNewAchievementNotification( - totalNewAchievements, - totalNewGamesWithAchievements - ); + if (userPreferences.achievementNotificationsEnabled) { + if (userPreferences.achievementCustomNotificationsEnabled !== false) { + WindowManager.notificationWindow?.webContents.send( + "on-combined-achievements-unlocked", + totalNewGamesWithAchievements, + totalNewAchievements, + userPreferences.achievementCustomNotificationPosition ?? + "top_left" + ); + } else { + publishCombinedNewAchievementNotification( + totalNewAchievements, + totalNewGamesWithAchievements + ); + } + } } } catch (err) { achievementsLogger.error("Error on preSearchAchievements", err); diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index a06d9652..761c60e6 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -106,20 +106,23 @@ export const mergeAchievements = async ( }; }); - WindowManager.notificationWindow?.webContents.send( - "on-achievement-unlocked", - game.objectId, - game.shop, - achievementsInfo - ); - - publishNewAchievementNotification({ - achievements: achievementsInfo, - unlockedAchievementCount: mergedLocalAchievements.length, - totalAchievementCount: achievementsData.length, - gameTitle: game.title, - gameIcon: game.iconUrl, - }); + if (userPreferences?.achievementCustomNotificationsEnabled !== false) { + WindowManager.notificationWindow?.webContents.send( + "on-achievement-unlocked", + game.objectId, + game.shop, + userPreferences.achievementCustomNotificationPosition ?? "top_left", + achievementsInfo + ); + } else { + publishNewAchievementNotification({ + achievements: achievementsInfo, + unlockedAchievementCount: mergedLocalAchievements.length, + totalAchievementCount: achievementsData.length, + gameTitle: game.title, + gameIcon: game.iconUrl, + }); + } } if (game.remoteId) { diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 5c080c09..807294c7 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -18,7 +18,11 @@ import { HydraApi } from "./hydra-api"; import UserAgent from "user-agents"; import { db, gamesSublevel, levelKeys } from "@main/level"; import { orderBy, slice } from "lodash-es"; -import type { ScreenState, UserPreferences } from "@types"; +import type { + AchievementCustomNotificationPosition, + ScreenState, + UserPreferences, +} from "@types"; import { AuthPage } from "@shared"; import { isStaging } from "@main/constants"; @@ -276,59 +280,44 @@ export class WindowManager { } } - private static readonly NOTIFICATION_WINDOW_WIDTH = 350; - private static readonly NOTIFICATION_WINDOW_HEIGHT = 104; - - public static async getNotificationWindowPosition() { - const userPreferences = await db.get( - levelKeys.userPreferences, - { - valueEncoding: "json", - } - ); + private static readonly NOTIFICATION_WINDOW_WIDTH = 360; + private static readonly NOTIFICATION_WINDOW_HEIGHT = 140; + private static async getNotificationWindowPosition( + position: AchievementCustomNotificationPosition | undefined + ) { const display = screen.getPrimaryDisplay(); const { width, height } = display.workAreaSize; - if ( - userPreferences?.achievementCustomNotificationPosition === "bottom_center" - ) { + if (position === "bottom_center") { return { x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "bottom_right" - ) { + if (position === "bottom_right") { return { x: width - this.NOTIFICATION_WINDOW_WIDTH, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "top_center" - ) { + if (position === "top_center") { return { x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, y: 0, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "bottom_left" - ) { + if (position === "bottom_left") { return { x: 0, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "top_right" - ) { + if (position === "top_right") { return { x: width - this.NOTIFICATION_WINDOW_WIDTH, y: 0, @@ -341,8 +330,16 @@ export class WindowManager { }; } - public static async createNotificationWindow() { - const { x, y } = await this.getNotificationWindowPosition(); + public static async createNotificationWindow(showTestNotification = false) { + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + const { x, y } = await this.getNotificationWindowPosition( + userPreferences.achievementCustomNotificationPosition + ); this.notificationWindow = new BrowserWindow({ transparent: true, @@ -372,9 +369,25 @@ export class WindowManager { if (isStaging) { this.notificationWindow?.webContents.openDevTools(); } + + if (showTestNotification) { + setTimeout(() => { + this.notificationWindow?.webContents.send( + "on-test-achievement-notification", + userPreferences.achievementCustomNotificationPosition ?? "top_left" + ); + }, 1000); + } }); } + public static async closeNotificationWindow() { + if (this.notificationWindow) { + this.notificationWindow.close(); + this.notificationWindow = null; + } + } + public static openEditorWindow(themeId: string) { if (this.mainWindow) { const existingWindow = this.editorWindows.get(themeId); diff --git a/src/preload/index.ts b/src/preload/index.ts index 966d7f02..48e79d36 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -18,6 +18,7 @@ import type { FriendRequestSync, ShortcutLocation, ShopAssets, + AchievementCustomNotificationPosition, } from "@types"; import type { AuthPage, CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; @@ -410,39 +411,63 @@ contextBridge.exposeInMainWorld("electron", { cb: ( objectId: string, shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] + position: AchievementCustomNotificationPosition, + achievements?: { + displayName: string; + iconUrl: string; + isHidden: boolean; + isRare: boolean; + isPlatinum: boolean; + }[] ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, objectId: string, shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] - ) => cb(objectId, shop, achievements); + position: AchievementCustomNotificationPosition, + achievements?: { + displayName: string; + iconUrl: string; + isHidden: boolean; + isRare: boolean; + isPlatinum: boolean; + }[] + ) => cb(objectId, shop, position, achievements); ipcRenderer.on("on-achievement-unlocked", listener); return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); }, onCombinedAchievementsUnlocked: ( - cb: (gameCount: number, achievementsCount: number) => void + cb: ( + gameCount: number, + achievementsCount: number, + position: AchievementCustomNotificationPosition + ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, gameCount: number, - achievementCount: number - ) => cb(gameCount, achievementCount); + achievementCount: number, + position: AchievementCustomNotificationPosition + ) => cb(gameCount, achievementCount, position); ipcRenderer.on("on-combined-achievements-unlocked", listener); return () => ipcRenderer.removeListener("on-combined-achievements-unlocked", listener); }, - onTestAchievementNotification: (cb: () => void) => { - const listener = (_event: Electron.IpcRendererEvent) => cb(); + onTestAchievementNotification: ( + cb: (position: AchievementCustomNotificationPosition) => void + ) => { + const listener = ( + _event: Electron.IpcRendererEvent, + position: AchievementCustomNotificationPosition + ) => cb(position); ipcRenderer.on("on-test-achievement-notification", listener); return () => ipcRenderer.removeListener("on-test-achievement-notification", listener); }, - updateAchievementCustomNotificationWindowPosition: () => - ipcRenderer.invoke("updateAchievementCustomNotificationWindowPosition"), + updateAchievementCustomNotificationWindow: () => + ipcRenderer.invoke("updateAchievementCustomNotificationWindow"), /* Themes */ addCustomTheme: (theme: Theme) => ipcRenderer.invoke("addCustomTheme", theme), diff --git a/src/renderer/src/assets/icons/ellipses.png b/src/renderer/src/assets/icons/ellipses.png new file mode 100644 index 0000000000000000000000000000000000000000..9c8eca1d338b333dec34f09879a34466c3882cb3 GIT binary patch literal 3675 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_R+I14-?iy0WiR6&^0Gf3qF z0|NtNage(c!@6@aFBupZSkfJR9T^xl_H+M9WMyFB{ov{17*fIbHa0d}#$9Z0vQo2% zOrz`u@uZGV3s|cUHg1*wlfYh9|6R|e@`AI>26>eeHXhtuE^Mb2X5LVc+*7*j{M?m# zUo&=9=Y36=&A6rO9ecO*{NEMP+gGa@In@08lzMvZyB&|S%X4-mH$V6Nck0?C`@b*x zw99Lr{S@vmj`3HU%q=OQcBwevM)}>1XN_C_O+CWc!D!UL@&3-ogA*Su{vxqgxk1=s z%TMWYn}XxDn%B}eo;F+mo8;46o6Dv*_ko@G9qI3f{_dZ&=jx4TJ6EhN-d6hPU8S0> zb3Vs|mg4hs<-F7?;twm{`93LA#pLrIj~#C38_FEnR29xi-&?=nZgBaZm-8O3{lYL; zt>JY+iP@Gv>?gVZ`t)zx+IQs0!j^yow;tSF7F#AfSB1g4G<;KE=!y&fUu3oKdfqmh zL9k5az1|d#xy6EWl^hx$B;NDC^Ko1Lzp2+=SD(ByjVX-L#bRRL0%4X2r60-VGDkl> z_iYSwyzjr(DZS%@7NbO|{X|BteH`y<_fNJF6flt6_VQAcB13!8hsnGDTwK5^e`v4$ zy}SLw%Pw8oZSKb~>$iOB#HrSk&dBh^eXx5WwSYlN`GD@8JgaM&Jq%Z>_I%*0Z(%u= zC_h7=Hxq6F=XVYQ;)stAP*Y#rUpHj1ihG2Y>J{E+p@{pIV`iw=Y?I=@VaGmzuJbcY4y z^X32z&r}SQ9TaMKKE|m&yseBAAA@aM0{o10CAqQ4~8UF~7X7G7}rQ2F_`bB7q? zyZH7T{+zor&a5qc-xOEYi)(hUT#xNcOq!6O%k-f!?$n{@@3(5!thZohZ^;XlUa_@d z#iz>aYKlxkYt|OL7J4u<#d+^FCOa-!R|yY`R;J^{d!I`l-=k-6MP^#{L8A=OjZuBR zC&Pbr=&Y*kyQSbPAUtPRdWEWK;B1DtSDOkS26HPEOgO(@>JrnW$le80O_eP7eZBR0 zb?t)I|JhY}_6m+~e>O+46!b23doQT}ebyzWe5sr31#|Xfb%e1O|F)Bq(*Kk&?*Xg+ z{VSQfjTj}q9DMK0(3|?hV^?e>huooK-1E1|l()Fb^$Hz4E#V|sw^B;vn&N@|s-L?* z)VB*svOZ0ktFs}-sl_er%N|SdEkBDzTUWR+tY7#-u>Rl@j$1Y_s`l103NG?{w!!zy zRqmC7{8Q8zOg~!c{}(oXp}j&rV{OVRy;<|_^FM0*yGd-52xHU7x)3-1m30eV{;buP z(|F63=V|%i*ya@fl!wmA0jfL?7uO}MUw?=_L$!Y5ng^m@UuL|QSZ%wd`CrqTI*rff z+MEu>Yxwu-PkeBz^3hi2_%KvJMASCK62=)i*PKt_t5%y2K&DF-}$N1=0XksB7*k@`s`P)Ijh09&1=ScHlYWx z^2d}KQ#LVOd}mk@-xVt6;QOhN=k0RMFRk`JPAPux+srA`>A|!^_GFUAgJUz2H5pg@ zmHIJrb_$y_hu+5xi?!q)?*6AXbDCX`h=iOsr*^?))(h=M=6~afSCD(D_uzvs@< zLy`CPPZ8BnVUS#W>wmk1l0<09r`bQ39VoxP;&0&06?>J!q8d}h6lQ(%o+dG~_jB@c zkKYWt!p{oZJioH}>fZ3E#%vY^?!TSl#uu(DDSbBj; zBiQHo-$bWO4o^AlfZ+PaPtUDd7~Na_`pTE)5C6M47W78D|FfECA{%@pUW|w9`Aw&d zM>Wq|KdgVk@n8mfTgHcUv6<}7r&4}RXZB(bFX$JxR!i8>nXJ4;Zy{^N1KSSOB(|x| zjAyrs-Fy?p*0QPNdC}pssVo+v$}G3?t(M3r#mFywpMFbAHT{d1)9-)#{MT4AW=MUI ze}2r)Dzm;cBmQkfB%|13jg8yBKHtY0a5*8YXzG^zay9dH8&Adwzhe8c`L7J)KF+$6 zOrKr(R2FJY7lc30LX_8=X^=T^qdT<^MKpn7VVD zT%d{$M?rMXgv|`Q-rt>iyk6 zgHl7(@3os=Glv{JvuKIp0$H1ZYfax4rZ)C!{l2Q}XA+UUCR6qGiNs$g z?R2?f4xCw(A?I-Sz_uF;3JjZCqBi_HrewZohv=Ool}3rb(x>|v%S1zFfGjC{m;27AxCRg)@?kzRdK0*!{rClbs8)VluYe5 zRCsjD%WBD-`!030uCDL#HkxMh) z&5GXX#-Jvfq!YG=Um!cZ$L_HC-)sBo?Pi9!tVs<$_^ex$nTO-=UE{OYj=a}ydiyyk z=WKOyK%_^5LT`2!>j|g3ny)|35O%34uV**C?Ek7+G4)}~CExq3E*qH|s%Hql|L1>P zLd3|Z{@Ce=+o#sc1nH?d_-G&a{dl2Y#sBo=UsHPC{|Zscn}7VQWjLQ^c&((!SuTcm z2k&{fG1eTz*?;QKSr@NEpW_eQQ+YJ){kpZ0HVWPR63Zsb3;ixcll(I;rrPRX+#g^ba>&o}jcyg$EZAJ@r;yyoVWIUjdK7sTJa{6050?6|u^ zP0H^$Pv14(3~N&w#TGSxSJz!9=wsxN!2iQ%TDNxg!ZSCQ99{dNsY9M?`*y$i>s=Y_ zCde<{?#Cn_XQ*;wLAB(=D(7{RA9u0OlRdQW$xr5R+f3QULo=US}$?=#LemSYAN{@hG|^sXY+ zc7B2%L&LvU?vXVymm-c8XnZiLVfe(eKKV#!Ajbj@wPJ^c`@sbj#w%>1wD>}1-C5bS zf`uW%zM;s7=U`Aw0W;I&e#MuGZ+7;*U}5-WxBos1(`5O*bq*If7)9>=VVtn0@r?Sk z3;I^e1^3n+a9_;A@Z-=7&yehfRaal$=m>c5`s2U+S$@Xmehdp1q;Oq(kinzO`u=X> zr_wONcOO>tGVBy((vXlo@aN?x)x8?OS2e3PwQt*>cj&sl^Jqa<~&<_eceO0 zgZ}Ee=d`q}92s001sRTTxYwRm(_1O^s^@{|T&0FZ3l0c1ggW#T+zMU&pmu7p|EKf! zEDvg1c`~pjv#2)Zi%t+fp5&gMTdsBTl|0v4L7TUmzP>VHtl4l+u9(~6_feUSIa38J z;v=pyiP<#g-Cs1-eTCqc*UamG$f(C&`KI)xjlph3sliqOmy7C83&abA#s7SdpCp(X z_A&U0lSYi{v}>&QzWwVh>#_?zyw@PoSUKVTGpj032Kf@St86 z_x*2;rQcaJnAAfPW;wD6JjssB|FGvxbFk<$b-C&H_UpIJSo_Dm;BAYNFxPcG1_lNO MPgg&ebxsLQ08@<^fc4 literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/icons/trophy.png b/src/renderer/src/assets/icons/trophy.png new file mode 100644 index 0000000000000000000000000000000000000000..73ae25e066c3d4a54941ee61590e4fdc69803bd4 GIT binary patch literal 417 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4mJh`hKCF@W-u@?a29w(7Bet#3xhBt!>lvsG#`=Bd{c;ur zXo$>XNsHUm8+xibZo)YcGmWJeRlYg6xfE4SUa@6WQ?HkaxZ}^`me*sutc>-%wEUy)32pa;p+ZH zr!Pb;ns)Jwp~9;0#dEi<>3bsn?WMfZ#&qfGtBF@tpDbFo?`EEw$<{Zi&)Rm*$~|LX zQsY;tC*9M?;cr?0tET!;M8}WxWsbP0l+XkKf9$3( literal 0 HcmV?d00001 diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 0f650114..ac3bc9d2 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -35,6 +35,7 @@ import type { CatalogueSearchResult, ShopAssets, ShopDetailsWithAssets, + AchievementCustomNotificationPosition, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type disk from "diskusage"; @@ -326,14 +327,27 @@ declare global { cb: ( objectId: string, shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] + position: AchievementCustomNotificationPosition, + achievements?: { + displayName: string; + iconUrl: string; + isHidden: boolean; + isRare: boolean; + isPlatinum: boolean; + }[] ) => void ) => () => Electron.IpcRenderer; onCombinedAchievementsUnlocked: ( - cb: (gameCount: number, achievementCount: number) => void + cb: ( + gameCount: number, + achievementCount: number, + position: AchievementCustomNotificationPosition + ) => void ) => () => Electron.IpcRenderer; - onTestAchievementNotification: (cb: () => void) => Electron.IpcRenderer; - updateAchievementCustomNotificationWindowPosition: () => Promise; + onTestAchievementNotification: ( + cb: (position: AchievementCustomNotificationPosition) => void + ) => Electron.IpcRenderer; + updateAchievementCustomNotificationWindow: () => Promise; /* Themes */ addCustomTheme: (theme: Theme) => Promise; diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.scss b/src/renderer/src/pages/achievements/notification/achievement-notification.scss index 51f90432..c919275f 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.scss @@ -1,35 +1,221 @@ @use "../../../scss/globals.scss"; -@keyframes achievement-in { +$margin-horizontal: 40px; +$margin-vertical: 52px; + +@keyframes content-in { 0% { - transform: translateY(-240px); + width: 80px; + opacity: 0; + transform: scale(0); } 100% { - transform: translateY(0); + width: 80px; + opacity: 1; + transform: scale(1); } } -@keyframes achievement-out { +@keyframes content-wait { 0% { - transform: translateY(0); + width: 80px; } 100% { - transform: translateY(-240px); + width: 80px; + } +} + +@keyframes trophy-out { + 0% { + opacity: 1; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +@keyframes ellipses-stand-by { + 0% { + opacity: 1; + } + 100% { + opacity: 1; + } +} +@keyframes ellipses-out { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + scale: 1.5; + } +} + +@keyframes content-expand { + 0% { + width: 80px; + } + 100% { + width: calc(360px - $margin-horizontal); + } +} + +@keyframes title-in { + 0% { + transform: translateY(10px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes description-in { + 0% { + transform: translateY(20px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes dark-overlay { + 0% { + opacity: 0.7; + } + 50% { + opacity: 0.7; + } + 100% { + opacity: 0; + } +} + +@keyframes content-out { + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(-20px); + opacity: 0; } } .achievement-notification { - margin-top: 24px; - margin-left: 24px; - animation-duration: 1s; - height: 60px; - display: flex; - animation-name: achievement-in; - transform: translateY(0); + position: relative; + display: grid; + width: calc(360px - $margin-horizontal); + height: 140px; + overflow: hidden; + animation: + content-in 450ms ease-in-out, + content-wait 450ms ease-in-out 450ms, + content-expand 450ms ease-in-out 900ms; + + &::before { + content: ""; + position: absolute; + top: 8px; + left: 8px; + width: 64px; + height: 64px; + opacity: 0; + z-index: 1; + background: url("/src/assets/icons/ellipses.png"); + background-size: contain; + animation: ellipses-out 900ms ease-in-out; + } + + &::after { + content: ""; + position: absolute; + top: 0px; + width: 80px; + height: 80px; + opacity: 0; + background: url("/src/assets/icons/trophy.png") no-repeat center; + animation: trophy-out 900ms ease-in-out; + } + + &.top_left { + margin: $margin-vertical 0 0 $margin-horizontal; + } + + &.top_center { + margin: $margin-vertical 0 0 $margin-horizontal; + } + + &.top_right { + margin: $margin-vertical $margin-horizontal 0 0; + align-self: end; + } + + &.bottom_left { + margin: 0 0 $margin-vertical $margin-horizontal; + } + + &.bottom_center { + margin: 0 0 $margin-vertical $margin-horizontal; + } + + &.bottom_right { + margin: 0 $margin-horizontal $margin-vertical 0; + align-self: end; + } &.closing { - animation-name: achievement-out; - transform: translateY(-240px); + transform: translateY(-20px); + opacity: 0; + animation: content-out 450ms ease-in-out; + } + + &__container { + width: calc(360px - $margin-horizontal); + max-width: 100%; + border: 1px solid #ffffff1a; + display: flex; + padding: 8px 16px 8px 8px; + background: globals.$background-color; + transform: translateY(0); + box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.25); + + &.top_left { + align-self: flex-start; + justify-self: flex-start; + } + + &.top_center { + align-self: flex-start; + justify-self: center; + } + + &.top_right { + align-self: flex-start; + justify-self: flex-end; + } + + &.bottom_left { + align-self: flex-end; + justify-self: flex-start; + } + + &.bottom_center { + align-self: flex-end; + justify-self: center; + } + + &.bottom_right { + align-self: flex-end; + justify-self: flex-end; + } } &__content { @@ -37,7 +223,51 @@ flex-direction: row; gap: 8px; align-items: center; - background: globals.$background-color; - padding-right: 8px; + width: 100%; + overflow: hidden; + position: relative; + + &::after { + content: ""; + position: absolute; + width: 64px; + height: 64px; + background: #000; + opacity: 0; + animation: dark-overlay 900ms ease-in-out; + } + } + + &__icon { + min-width: 64px; + min-height: 64px; + border-radius: 2px; + flex: 1; + position: relative; + } + + &__text-container { + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; + } + + &__title { + font-size: 14px; + font-weight: 700; + white-space: nowrap; + overflow: hidden; + color: globals.$muted-color; + animation: title-in 450ms ease-in-out 900ms; + } + + &__description { + font-size: 14px; + font-weight: 400; + white-space: nowrap; + overflow: hidden; + color: globals.$body-color; + animation: description-in 450ms ease-in-out 900ms; } } diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 49f06888..271c42ea 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -3,6 +3,7 @@ import achievementSound from "@renderer/assets/audio/achievement.wav"; import { useTranslation } from "react-i18next"; import cn from "classnames"; import "./achievement-notification.scss"; +import { AchievementCustomNotificationPosition } from "@types"; interface AchievementInfo { displayName: string; @@ -16,6 +17,8 @@ export function AchievementNotification() { const [isClosing, setIsClosing] = useState(false); const [isVisible, setIsVisible] = useState(false); + const [position, setPosition] = + useState("top_left"); const [achievements, setAchievements] = useState([]); const [currentAchievement, setCurrentAchievement] = @@ -33,9 +36,11 @@ export function AchievementNotification() { useEffect(() => { const unsubscribe = window.electron.onCombinedAchievementsUnlocked( - (gameCount, achievementCount) => { + (gameCount, achievementCount, position) => { if (gameCount === 0 || achievementCount === 0) return; + setPosition(position); + setAchievements([ { displayName: t("new_achievements_unlocked", { @@ -58,9 +63,10 @@ export function AchievementNotification() { useEffect(() => { const unsubscribe = window.electron.onAchievementUnlocked( - (_object, _shop, achievements) => { + (_object, _shop, position, achievements) => { if (!achievements?.length) return; + setPosition(position); setAchievements((ach) => ach.concat(achievements)); playAudio(); @@ -73,19 +79,22 @@ export function AchievementNotification() { }, [playAudio]); useEffect(() => { - const unsubscribe = window.electron.onTestAchievementNotification(() => { - setAchievements((ach) => - ach.concat([ - { - displayName: "Test Achievement", - iconUrl: - "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fc.tenor.com%2FRwKr7hVnXREAAAAC%2Fnyan-cat.gif&f=1&nofb=1&ipt=706fd8b00cbfb5b2d2621603834d5f32c0f34cce7113de228d2fcc2247a80318", - }, - ]) - ); + const unsubscribe = window.electron.onTestAchievementNotification( + (position) => { + setPosition(position); + setAchievements((ach) => + ach.concat([ + { + displayName: "Test Achievement", + iconUrl: + "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fc.tenor.com%2FRwKr7hVnXREAAAAC%2Fnyan-cat.gif&f=1&nofb=1&ipt=706fd8b00cbfb5b2d2621603834d5f32c0f34cce7113de228d2fcc2247a80318", + }, + ]) + ); - playAudio(); - }); + playAudio(); + } + ); return () => { unsubscribe(); @@ -104,7 +113,7 @@ export function AchievementNotification() { const zero = performance.now(); closingAnimation.current = requestAnimationFrame( function animateClosing(time) { - if (time - zero <= 1000) { + if (time - zero <= 450) { closingAnimation.current = requestAnimationFrame(animateClosing); } else { setIsVisible(false); @@ -147,18 +156,30 @@ export function AchievementNotification() { return (

-
- {currentAchievement.displayName} -
-

{t("achievement_unlocked")}

-

{currentAchievement.displayName}

+
+
+ {currentAchievement.displayName} +
+

+ {t("achievement_unlocked")} +

+

+ {currentAchievement.displayName} +

+
diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index b04c57cc..8d9de622 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -119,10 +119,10 @@ export function SettingsGeneral() { const achievementCustomNotificationPositionOptions = useMemo(() => { return [ "top_left", + "top_center", "top_right", "bottom_left", "bottom_right", - "top_center", "bottom_center", ].map((position) => ({ key: position, @@ -152,7 +152,7 @@ export function SettingsGeneral() { await handleChange({ achievementCustomNotificationPosition: value }); - window.electron.updateAchievementCustomNotificationWindowPosition(); + window.electron.updateAchievementCustomNotificationWindow(); }; const handleChooseDownloadsPath = async () => { @@ -251,24 +251,28 @@ export function SettingsGeneral() { - handleChange({ + onChange={async () => { + await handleChange({ achievementNotificationsEnabled: !form.achievementNotificationsEnabled, - }) - } + }); + + window.electron.updateAchievementCustomNotificationWindow(); + }} /> - handleChange({ + onChange={async () => { + await handleChange({ achievementCustomNotificationsEnabled: !form.achievementCustomNotificationsEnabled, - }) - } + }); + + window.electron.updateAchievementCustomNotificationWindow(); + }} /> {form.achievementNotificationsEnabled && From 0a4bdf160c06e9f451158b8abba022b9a933cf14 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 16 May 2025 05:19:33 -0300 Subject: [PATCH 05/22] feat: i18n and refactor --- src/locales/en/translation.json | 13 +++- src/locales/pt-BR/translation.json | 13 +++- .../achievements/merge-achievements.ts | 12 ++-- src/main/services/notifications/index.ts | 6 +- src/main/services/window-manager.ts | 23 +++++++- src/preload/index.ts | 38 ++---------- src/renderer/src/declaration.d.ts | 16 +---- .../notification/achievement-notification.tsx | 59 +++++++------------ .../src/pages/settings/settings-general.tsx | 2 +- src/types/index.ts | 9 +++ 10 files changed, 94 insertions(+), 97 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 2902218e..d342cc46 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -363,7 +363,14 @@ "install_common_redist": "Install", "installing_common_redist": "Installing…", "show_download_speed_in_megabytes": "Show download speed in megabytes per second", - "extract_files_by_default": "Extract files by default after download" + "extract_files_by_default": "Extract files by default after download", + "achievement_custom_notification_position": "Enable achievements custom notification", + "top_left": "Top left", + "top_center": "Top center", + "top_right": "Top right", + "bottom_left": "Bottom left", + "bottom_center": "Bottom center", + "bottom_right": "Bottom right" }, "notifications": { "download_complete": "Download complete", @@ -379,7 +386,9 @@ "new_friend_request_title": "New friend request", "extraction_complete": "Extraction complete", "game_extracted": "{{title}} extracted successfully", - "friend_started_playing_game": "{{displayName}} started playing a game" + "friend_started_playing_game": "{{displayName}} started playing a game", + "test_achievement_notification_title": "This is a test notification", + "test_achievement_notification_description": "Pretty cool, huh?" }, "system_tray": { "open": "Open Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 5509e07b..81490142 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -349,7 +349,14 @@ "install_common_redist": "Instalar", "installing_common_redist": "Instalando…", "show_download_speed_in_megabytes": "Exibir taxas de download em megabytes por segundo", - "extract_files_by_default": "Extrair arquivos automaticamente após o download" + "extract_files_by_default": "Extrair arquivos automaticamente após o download", + "enable_achievement_custom_notifications": "Habilitar notificações de conquistas customizadas", + "top_left": "Superior esquerdo", + "top_center": "Superior central", + "top_right": "Superior direito", + "bottom_left": "Inferior esquerdo", + "bottom_right": "Inferior direito", + "bottom_center": "Inferior central" }, "notifications": { "download_complete": "Download concluído", @@ -363,7 +370,9 @@ "new_friend_request_description": "{{displayName}} te enviou um pedido de amizade", "extraction_complete": "Extração concluída", "game_extracted": "{{title}} extraído com sucesso", - "friend_started_playing_game": "{{displayName}} começou a jogar" + "friend_started_playing_game": "{{displayName}} começou a jogar", + "test_achievement_notification_title": "Esta é uma notificação de teste", + "test_achievement_notification_description": "Bem legal, né?" }, "system_tray": { "open": "Abrir Hydra", diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 761c60e6..6e070c7d 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -1,4 +1,5 @@ import type { + AchievementNotificationInfo, Game, GameShop, UnlockedAchievement, @@ -86,7 +87,7 @@ export const mergeAchievements = async ( publishNotification && userPreferences?.achievementNotificationsEnabled ) { - const achievementsInfo = newAchievements + const achievementsInfo: AchievementNotificationInfo[] = newAchievements .toSorted((a, b) => { return a.unlockTime - b.unlockTime; }) @@ -101,7 +102,12 @@ export const mergeAchievements = async ( .filter((achievement) => Boolean(achievement)) .map((achievement) => { return { - displayName: achievement!.displayName, + title: achievement!.displayName, + description: achievement!.description, + points: achievement!.points, + isHidden: achievement!.hidden, + isRare: false, + isPlatinum: false, iconUrl: achievement!.icon, }; }); @@ -109,8 +115,6 @@ export const mergeAchievements = async ( if (userPreferences?.achievementCustomNotificationsEnabled !== false) { WindowManager.notificationWindow?.webContents.send( "on-achievement-unlocked", - game.objectId, - game.shop, userPreferences.achievementCustomNotificationPosition ?? "top_left", achievementsInfo ); diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index 77866a47..fa9ac593 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -162,7 +162,7 @@ export const publishExtractionCompleteNotification = async (game: Game) => { }; export const publishNewAchievementNotification = async (info: { - achievements: { displayName: string; iconUrl: string }[]; + achievements: { title: string; iconUrl: string }[]; unlockedAchievementCount: number; totalAchievementCount: number; gameTitle: string; @@ -176,12 +176,12 @@ export const publishNewAchievementNotification = async (info: { gameTitle: info.gameTitle, achievementCount: info.achievements.length, }), - body: info.achievements.map((a) => a.displayName).join(", "), + body: info.achievements.map((a) => a.title).join(", "), icon: (await downloadImage(info.gameIcon)) ?? icon, } : { title: t("achievement_unlocked", { ns: "achievement" }), - body: info.achievements[0].displayName, + body: info.achievements[0].title, icon: (await downloadImage(info.achievements[0].iconUrl)) ?? icon, }; diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 807294c7..dc07e673 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -20,6 +20,7 @@ import { db, gamesSublevel, levelKeys } from "@main/level"; import { orderBy, slice } from "lodash-es"; import type { AchievementCustomNotificationPosition, + AchievementNotificationInfo, ScreenState, UserPreferences, } from "@types"; @@ -371,10 +372,28 @@ export class WindowManager { } if (showTestNotification) { + const language = userPreferences.language ?? "en"; setTimeout(() => { this.notificationWindow?.webContents.send( - "on-test-achievement-notification", - userPreferences.achievementCustomNotificationPosition ?? "top_left" + "on-achievement-unlocked", + userPreferences.achievementCustomNotificationPosition ?? "top_left", + [ + { + title: t("test_achievement_notification_title", { + ns: "notifications", + lng: language, + }), + description: t("test_achievement_notification_description", { + ns: "notifications", + lng: language, + }), + iconUrl: "https://cdn.losbroxas.org/favicon.svg", + points: 100, + isHidden: false, + isRare: false, + isPlatinum: false, + }, + ] as AchievementNotificationInfo[] ); }, 1000); } diff --git a/src/preload/index.ts b/src/preload/index.ts index 48e79d36..c80f60ba 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -19,6 +19,7 @@ import type { ShortcutLocation, ShopAssets, AchievementCustomNotificationPosition, + AchievementNotificationInfo, } from "@types"; import type { AuthPage, CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; @@ -409,31 +410,15 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("publishNewRepacksNotification", newRepacksCount), onAchievementUnlocked: ( cb: ( - objectId: string, - shop: GameShop, - position: AchievementCustomNotificationPosition, - achievements?: { - displayName: string; - iconUrl: string; - isHidden: boolean; - isRare: boolean; - isPlatinum: boolean; - }[] + position?: AchievementCustomNotificationPosition, + achievements?: AchievementNotificationInfo[] ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, - objectId: string, - shop: GameShop, - position: AchievementCustomNotificationPosition, - achievements?: { - displayName: string; - iconUrl: string; - isHidden: boolean; - isRare: boolean; - isPlatinum: boolean; - }[] - ) => cb(objectId, shop, position, achievements); + position?: AchievementCustomNotificationPosition, + achievements?: AchievementNotificationInfo[] + ) => cb(position, achievements); ipcRenderer.on("on-achievement-unlocked", listener); return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); @@ -455,17 +440,6 @@ contextBridge.exposeInMainWorld("electron", { return () => ipcRenderer.removeListener("on-combined-achievements-unlocked", listener); }, - onTestAchievementNotification: ( - cb: (position: AchievementCustomNotificationPosition) => void - ) => { - const listener = ( - _event: Electron.IpcRendererEvent, - position: AchievementCustomNotificationPosition - ) => cb(position); - ipcRenderer.on("on-test-achievement-notification", listener); - return () => - ipcRenderer.removeListener("on-test-achievement-notification", listener); - }, updateAchievementCustomNotificationWindow: () => ipcRenderer.invoke("updateAchievementCustomNotificationWindow"), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index ac3bc9d2..5cdd8c04 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -36,6 +36,7 @@ import type { ShopAssets, ShopDetailsWithAssets, AchievementCustomNotificationPosition, + AchievementNotificationInfo, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type disk from "diskusage"; @@ -325,16 +326,8 @@ declare global { publishNewRepacksNotification: (newRepacksCount: number) => Promise; onAchievementUnlocked: ( cb: ( - objectId: string, - shop: GameShop, - position: AchievementCustomNotificationPosition, - achievements?: { - displayName: string; - iconUrl: string; - isHidden: boolean; - isRare: boolean; - isPlatinum: boolean; - }[] + position?: AchievementCustomNotificationPosition, + achievements?: AchievementNotificationInfo[] ) => void ) => () => Electron.IpcRenderer; onCombinedAchievementsUnlocked: ( @@ -344,9 +337,6 @@ declare global { position: AchievementCustomNotificationPosition ) => void ) => () => Electron.IpcRenderer; - onTestAchievementNotification: ( - cb: (position: AchievementCustomNotificationPosition) => void - ) => Electron.IpcRenderer; updateAchievementCustomNotificationWindow: () => Promise; /* Themes */ diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 271c42ea..4689e0d9 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -3,14 +3,12 @@ import achievementSound from "@renderer/assets/audio/achievement.wav"; import { useTranslation } from "react-i18next"; import cn from "classnames"; import "./achievement-notification.scss"; -import { AchievementCustomNotificationPosition } from "@types"; +import { + AchievementCustomNotificationPosition, + AchievementNotificationInfo, +} from "@types"; -interface AchievementInfo { - displayName: string; - iconUrl: string; -} - -const NOTIFICATION_TIMEOUT = 4000; +const NOTIFICATION_TIMEOUT = 6000; export function AchievementNotification() { const { t } = useTranslation("achievement"); @@ -20,9 +18,11 @@ export function AchievementNotification() { const [position, setPosition] = useState("top_left"); - const [achievements, setAchievements] = useState([]); + const [achievements, setAchievements] = useState< + AchievementNotificationInfo[] + >([]); const [currentAchievement, setCurrentAchievement] = - useState(null); + useState(null); const achievementAnimation = useRef(-1); const closingAnimation = useRef(-1); @@ -43,10 +43,14 @@ export function AchievementNotification() { setAchievements([ { - displayName: t("new_achievements_unlocked", { + title: t("new_achievements_unlocked", { gameCount, achievementCount, }), + isHidden: false, + isRare: false, + isPlatinum: false, + points: 0, iconUrl: "https://avatars.githubusercontent.com/u/164102380?s=400&u=01a13a7b4f0c642f7e547b8e1d70440ea06fa750&v=4", }, @@ -63,10 +67,12 @@ export function AchievementNotification() { useEffect(() => { const unsubscribe = window.electron.onAchievementUnlocked( - (_object, _shop, position, achievements) => { + (position, achievements) => { if (!achievements?.length) return; + if (position) { + setPosition(position); + } - setPosition(position); setAchievements((ach) => ach.concat(achievements)); playAudio(); @@ -78,29 +84,6 @@ export function AchievementNotification() { }; }, [playAudio]); - useEffect(() => { - const unsubscribe = window.electron.onTestAchievementNotification( - (position) => { - setPosition(position); - setAchievements((ach) => - ach.concat([ - { - displayName: "Test Achievement", - iconUrl: - "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fc.tenor.com%2FRwKr7hVnXREAAAAC%2Fnyan-cat.gif&f=1&nofb=1&ipt=706fd8b00cbfb5b2d2621603834d5f32c0f34cce7113de228d2fcc2247a80318", - }, - ]) - ); - - playAudio(); - } - ); - - return () => { - unsubscribe(); - }; - }, [playAudio]); - const hasAchievementsPending = achievements.length > 0; const startAnimateClosing = useCallback(() => { @@ -169,15 +152,15 @@ export function AchievementNotification() {
{currentAchievement.displayName}

- {t("achievement_unlocked")} + {currentAchievement.title}

- {currentAchievement.displayName} + {currentAchievement.description}

diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 8d9de622..792c4603 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -122,8 +122,8 @@ export function SettingsGeneral() { "top_center", "top_right", "bottom_left", - "bottom_right", "bottom_center", + "bottom_right", ].map((position) => ({ key: position, value: position, diff --git a/src/types/index.ts b/src/types/index.ts index aa71b5e5..c842d999 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -263,6 +263,15 @@ export type GameAchievementFiles = { [id: string]: AchievementFile[]; }; +export interface AchievementNotificationInfo { + title: string; + description?: string; + iconUrl: string; + isHidden: boolean; + isRare: boolean; + isPlatinum: boolean; + points?: number; +} export interface GameArtifact { id: string; artifactLengthInBytes: number; From c5beeb861e889e9fcef48dac92079c852153cf39 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 16 May 2025 05:45:06 -0300 Subject: [PATCH 06/22] feat: i18n update --- src/locales/en/translation.json | 5 +++-- src/locales/pt-BR/translation.json | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index d342cc46..64a83287 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -364,13 +364,14 @@ "installing_common_redist": "Installing…", "show_download_speed_in_megabytes": "Show download speed in megabytes per second", "extract_files_by_default": "Extract files by default after download", - "achievement_custom_notification_position": "Enable achievements custom notification", + "achievement_custom_notification_position": "Achievement custom notification position", "top_left": "Top left", "top_center": "Top center", "top_right": "Top right", "bottom_left": "Bottom left", "bottom_center": "Bottom center", - "bottom_right": "Bottom right" + "bottom_right": "Bottom right", + "enable_achievement_custom_notifications": "Enable achievement custom notifications" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 81490142..d9aa7376 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -350,13 +350,14 @@ "installing_common_redist": "Instalando…", "show_download_speed_in_megabytes": "Exibir taxas de download em megabytes por segundo", "extract_files_by_default": "Extrair arquivos automaticamente após o download", - "enable_achievement_custom_notifications": "Habilitar notificações de conquistas customizadas", + "enable_achievement_custom_notifications": "Habilitar notificações customizadas de conquistas", "top_left": "Superior esquerdo", "top_center": "Superior central", "top_right": "Superior direito", "bottom_left": "Inferior esquerdo", "bottom_right": "Inferior direito", - "bottom_center": "Inferior central" + "bottom_center": "Inferior central", + "achievement_custom_notification_position": "Posição das notificações customizadas de conquista" }, "notifications": { "download_complete": "Download concluído", From 39c073634ca41b2a4bf396959553f925223e6820 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 16 May 2025 06:56:21 -0300 Subject: [PATCH 07/22] feat: animation update --- .../notification/achievement-notification.scss | 3 +++ .../notification/achievement-notification.tsx | 11 +++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.scss b/src/renderer/src/pages/achievements/notification/achievement-notification.scss index c919275f..e62f4100 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.scss @@ -251,6 +251,7 @@ $margin-vertical: 52px; flex-direction: column; gap: 4px; width: 100%; + overflow: hidden; } &__title { @@ -258,6 +259,7 @@ $margin-vertical: 52px; font-weight: 700; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; color: globals.$muted-color; animation: title-in 450ms ease-in-out 900ms; } @@ -267,6 +269,7 @@ $margin-vertical: 52px; font-weight: 400; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; color: globals.$body-color; animation: description-in 450ms ease-in-out 900ms; } diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 4689e0d9..d86d83cf 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -8,7 +8,7 @@ import { AchievementNotificationInfo, } from "@types"; -const NOTIFICATION_TIMEOUT = 6000; +const NOTIFICATION_TIMEOUT = 4000; export function AchievementNotification() { const { t } = useTranslation("achievement"); @@ -100,6 +100,7 @@ export function AchievementNotification() { closingAnimation.current = requestAnimationFrame(animateClosing); } else { setIsVisible(false); + setAchievements((ach) => ach.slice(1)); } } ); @@ -118,15 +119,13 @@ export function AchievementNotification() { function animateLock(time) { if (time - zero > NOTIFICATION_TIMEOUT) { zero = performance.now(); - setAchievements((ach) => ach.slice(1)); + startAnimateClosing(); } achievementAnimation.current = requestAnimationFrame(animateLock); } ); - } else { - startAnimateClosing(); } - }, [hasAchievementsPending, startAnimateClosing]); + }, [hasAchievementsPending, startAnimateClosing, currentAchievement]); useEffect(() => { if (achievements.length) { @@ -145,8 +144,8 @@ export function AchievementNotification() { >
From bc06ae5c032a46fa954d30dfae9469f0ddac06b1 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 16 May 2025 16:18:19 -0300 Subject: [PATCH 08/22] feat: notification preview on theme editor --- src/main/events/themes/update-custom-theme.ts | 1 + src/main/services/window-manager.ts | 23 +--- .../achievement-notification.scss | 0 .../notification/achievement-notification.tsx | 50 ++++++++ .../collapsed-menu/collapsed-menu.scss | 40 ++++++ .../collapsed-menu/collapsed-menu.tsx | 52 ++++++++ .../components/select-field/select-field.tsx | 5 +- .../notification/achievement-notification.tsx | 62 +++++----- .../aparence/modals/add-theme-modal.tsx | 50 ++++---- .../src/pages/theme-editor/theme-editor.scss | 23 +++- .../src/pages/theme-editor/theme-editor.tsx | 114 +++++++++++++++--- src/shared/constants.ts | 5 + src/shared/index.ts | 22 ++++ 13 files changed, 347 insertions(+), 100 deletions(-) rename src/renderer/src/{pages => components}/achievements/notification/achievement-notification.scss (100%) create mode 100644 src/renderer/src/components/achievements/notification/achievement-notification.tsx create mode 100644 src/renderer/src/components/collapsed-menu/collapsed-menu.scss create mode 100644 src/renderer/src/components/collapsed-menu/collapsed-menu.tsx diff --git a/src/main/events/themes/update-custom-theme.ts b/src/main/events/themes/update-custom-theme.ts index b9a8e048..92d85b8b 100644 --- a/src/main/events/themes/update-custom-theme.ts +++ b/src/main/events/themes/update-custom-theme.ts @@ -21,6 +21,7 @@ const updateCustomTheme = async ( if (theme.isActive) { WindowManager.mainWindow?.webContents.send("css-injected", code); + WindowManager.notificationWindow?.webContents.send("css-injected", code); } }; diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index dc07e673..1ea5ea1d 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -20,11 +20,10 @@ import { db, gamesSublevel, levelKeys } from "@main/level"; import { orderBy, slice } from "lodash-es"; import type { AchievementCustomNotificationPosition, - AchievementNotificationInfo, ScreenState, UserPreferences, } from "@types"; -import { AuthPage } from "@shared"; +import { AuthPage, generateAchievementCustomNotificationTest } from "@shared"; import { isStaging } from "@main/constants"; export class WindowManager { @@ -377,23 +376,7 @@ export class WindowManager { this.notificationWindow?.webContents.send( "on-achievement-unlocked", userPreferences.achievementCustomNotificationPosition ?? "top_left", - [ - { - title: t("test_achievement_notification_title", { - ns: "notifications", - lng: language, - }), - description: t("test_achievement_notification_description", { - ns: "notifications", - lng: language, - }), - iconUrl: "https://cdn.losbroxas.org/favicon.svg", - points: 100, - isHidden: false, - isRare: false, - isPlatinum: false, - }, - ] as AchievementNotificationInfo[] + [generateAchievementCustomNotificationTest(t, language)] ); }, 1000); } @@ -419,7 +402,7 @@ export class WindowManager { } const editorWindow = new BrowserWindow({ - width: 600, + width: 720, height: 720, minWidth: 600, minHeight: 540, diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss similarity index 100% rename from src/renderer/src/pages/achievements/notification/achievement-notification.scss rename to src/renderer/src/components/achievements/notification/achievement-notification.scss diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx new file mode 100644 index 00000000..dddbe436 --- /dev/null +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -0,0 +1,50 @@ +import { + AchievementCustomNotificationPosition, + AchievementNotificationInfo, +} from "@types"; +import cn from "classnames"; +import "./achievement-notification.scss"; + +interface AchievementNotificationProps { + position: AchievementCustomNotificationPosition; + currentAchievement: AchievementNotificationInfo; + isClosing: boolean; +} + +export function AchievementNotificationItem({ + position, + currentAchievement, + isClosing, +}: Readonly) { + return ( +
+
+
+ {currentAchievement.title} +
+

+ {currentAchievement.title} +

+

+ {currentAchievement.description} +

+
+
+
+
+ ); +} diff --git a/src/renderer/src/components/collapsed-menu/collapsed-menu.scss b/src/renderer/src/components/collapsed-menu/collapsed-menu.scss new file mode 100644 index 00000000..c209940e --- /dev/null +++ b/src/renderer/src/components/collapsed-menu/collapsed-menu.scss @@ -0,0 +1,40 @@ +@use "../../scss/globals.scss"; + +.collapsed-menu { + &__button { + height: 72px; + padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 2); + display: flex; + align-items: center; + background-color: globals.$background-color; + color: globals.$muted-color; + width: 100%; + cursor: pointer; + transition: all ease 0.2s; + gap: globals.$spacing-unit; + font-size: globals.$body-font-size; + font-weight: bold; + + &:hover { + background-color: rgba(255, 255, 255, 0.05); + } + + &:active { + opacity: globals.$active-opacity; + } + } + + &__chevron { + transition: transform ease 0.2s; + + &--open { + transform: rotate(180deg); + } + } + + &__content { + overflow: hidden; + transition: max-height 0.4s cubic-bezier(0, 1, 0, 1); + position: relative; + } +} diff --git a/src/renderer/src/components/collapsed-menu/collapsed-menu.tsx b/src/renderer/src/components/collapsed-menu/collapsed-menu.tsx new file mode 100644 index 00000000..67058598 --- /dev/null +++ b/src/renderer/src/components/collapsed-menu/collapsed-menu.tsx @@ -0,0 +1,52 @@ +import { useEffect, useRef, useState } from "react"; +import { ChevronDownIcon } from "@primer/octicons-react"; +import "./collapsed-menu.scss"; + +export interface CollapsedMenuProps { + title: string; + children: React.ReactNode; +} + +export function CollapsedMenu({ + title, + children, +}: Readonly) { + const content = useRef(null); + const [isOpen, setIsOpen] = useState(true); + const [height, setHeight] = useState(0); + + useEffect(() => { + if (content.current && content.current.scrollHeight !== height) { + setHeight(isOpen ? content.current.scrollHeight : 0); + } else if (!isOpen) { + setHeight(0); + } + }, [isOpen, children, height]); + + return ( +
+ + +
+ {children} +
+
+ ); +} diff --git a/src/renderer/src/components/select-field/select-field.tsx b/src/renderer/src/components/select-field/select-field.tsx index 69e3d74b..08334b9f 100644 --- a/src/renderer/src/components/select-field/select-field.tsx +++ b/src/renderer/src/components/select-field/select-field.tsx @@ -18,12 +18,13 @@ export function SelectField({ options = [{ key: "-", value: value?.toString() || "-", label: "-" }], theme = "primary", onChange, -}: SelectProps) { + className, +}: Readonly) { const [isFocused, setIsFocused] = useState(false); const id = useId(); return ( -
+
{label && (
- +
+
+ +
+
+ +
+ { + return { + key: variation, + value: variation, + label: variation, + }; + } + )} + onChange={(value) => + setNotificationVariation( + value.target.value as keyof typeof notificationVariations + ) + } + /> + + +
+
+
From baddd4a99b912ce61c8719187aecbc4fe1717514 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 14:29:01 -0300 Subject: [PATCH 11/22] feat: animation and borders --- .../achievement-notification.scss | 60 ++++++++++++++++--- .../notification/achievement-notification.tsx | 7 ++- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index af8832da..7e374412 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -129,12 +129,23 @@ $margin-vertical: 52px; } } +@keyframes shine { + from { + transform: translateX(0px) rotate(36deg); + } + to { + transform: translateX(420px) rotate(36deg); + } +} + .achievement-notification { width: 360px; height: 192px; - &.closing * { - animation: none; + &.closing *, + &.closing *::before, + &.closing *::after { + animation: none !important; } .achievement-notification__outer-container { @@ -219,7 +230,23 @@ $margin-vertical: 52px; } &.isPlatinum &__container { - background: linear-gradient(94deg, #1c1c1c 0.38%, #044838 99.62%), #1c1c1c; + background: linear-gradient(94deg, #1c1c1c -25%, #044838 100%); + } + + &.isRare &__container { + &::before { + content: ""; + position: absolute; + top: -50%; + left: -60px; + width: 29px; + height: 134px; + transform: translateX(0px) rotate(36deg); + opacity: 0.2; + background: #d9d9d9; + filter: blur(8px); + animation: shine 450ms ease-in-out 1350ms; + } } &__content { @@ -228,17 +255,29 @@ $margin-vertical: 52px; gap: 8px; align-items: center; width: 100%; + z-index: 1; } &__icon { + box-sizing: border-box; min-width: 64px; min-height: 64px; - max-width: 64px; - max-height: 64px; + width: 64px; + height: 64px; border-radius: 2px; flex: 1; } + &.isRare &__icon { + outline: 1px solid #f4a510; + box-shadow: 0px 0px 12px 0px rgba(244, 165, 16, 0.25); + } + + &.isPlatinum &__icon { + outline: 1px solid #0cf1ca; + box-shadow: 0px 0px 12px 0px rgba(12, 241, 202, 0.25); + } + &__text-container { display: flex; flex-direction: column; @@ -257,12 +296,19 @@ $margin-vertical: 52px; animation: title-in 450ms ease-in-out 900ms; } + &__hidden-icon { + margin-right: 4px; + opacity: 0.5; + } + &__description { font-size: 14px; font-weight: 400; - white-space: nowrap; overflow: hidden; - text-overflow: ellipsis; + -webkit-line-clamp: 2; /* number of lines to show */ + line-clamp: 2; + display: -webkit-box; + -webkit-box-orient: vertical; color: globals.$body-color; animation: description-in 450ms ease-in-out 900ms; } diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx index 1cb48435..9ce551b9 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -5,6 +5,7 @@ import { import cn from "classnames"; import "./achievement-notification.scss"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; +import { EyeClosedIcon } from "@primer/octicons-react"; interface AchievementNotificationProps { position: AchievementCustomNotificationPosition; @@ -56,7 +57,6 @@ export function AchievementNotificationItem({ })} >
-
{achievement.title}

+ {achievement.isHidden && ( + + + + )} {achievement.title}

From 6a59036e2160ce321ea30bc62af51c7df8385ce9 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 16:56:09 -0300 Subject: [PATCH 12/22] feat: alignments --- .../achievement-notification.scss | 203 ++++++++++-------- .../notification/achievement-notification.tsx | 15 +- .../src/pages/theme-editor/theme-editor.tsx | 44 +++- 3 files changed, 157 insertions(+), 105 deletions(-) diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index 7e374412..1e68714b 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -1,7 +1,8 @@ @use "../../../scss/globals.scss"; $margin-horizontal: 40px; -$margin-vertical: 52px; +$margin-top: 52px; +$margin-bottom: 28px; @keyframes content-in { 0% { @@ -141,13 +142,43 @@ $margin-vertical: 52px; .achievement-notification { width: 360px; height: 192px; + display: flex; + + &.closing * { + animation: none; + } - &.closing *, &.closing *::before, &.closing *::after { animation: none !important; } + &.top_left { + align-items: start; + } + + &.top_center { + align-items: start; + } + + &.top_right { + justify-content: end; + align-items: start; + } + + &.bottom_left { + align-items: end; + } + + &.bottom_center { + align-items: end; + } + + &.bottom_right { + justify-content: end; + align-items: end; + } + .achievement-notification__outer-container { position: relative; display: grid; @@ -161,35 +192,33 @@ $margin-vertical: 52px; box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.25); &.top_left { - margin: $margin-vertical 0 0 $margin-horizontal; + margin: $margin-top 0 0 $margin-horizontal; } &.top_center { - margin: $margin-vertical 0 0 $margin-horizontal; + margin: $margin-top 0 0 $margin-horizontal; } &.top_right { - margin: $margin-vertical $margin-horizontal 0 0; - align-self: end; + margin: $margin-top $margin-horizontal 0 0; } &.bottom_left { - margin: 0 0 $margin-vertical $margin-horizontal; + margin: 0 0 $margin-bottom $margin-horizontal; } &.bottom_center { - margin: 0 0 $margin-vertical $margin-horizontal; + margin: 0 0 $margin-bottom $margin-horizontal; } &.bottom_right { - margin: 0 $margin-horizontal $margin-vertical 0; - align-self: end; + margin: 0 $margin-horizontal $margin-bottom 0; } + } - &.closing { - animation: content-out 450ms ease-in-out; - animation-fill-mode: forwards; - } + &.closing .achievement-notification__outer-container { + animation: content-out 450ms ease-in-out; + animation-fill-mode: forwards; } &__container { @@ -278,79 +307,6 @@ $margin-vertical: 52px; box-shadow: 0px 0px 12px 0px rgba(12, 241, 202, 0.25); } - &__text-container { - display: flex; - flex-direction: column; - gap: 4px; - width: 100%; - overflow: hidden; - } - - &__title { - font-size: 14px; - font-weight: 700; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: globals.$muted-color; - animation: title-in 450ms ease-in-out 900ms; - } - - &__hidden-icon { - margin-right: 4px; - opacity: 0.5; - } - - &__description { - font-size: 14px; - font-weight: 400; - overflow: hidden; - -webkit-line-clamp: 2; /* number of lines to show */ - line-clamp: 2; - display: -webkit-box; - -webkit-box-orient: vertical; - color: globals.$body-color; - animation: description-in 450ms ease-in-out 900ms; - } - - &__chip-container { - position: relative; - z-index: 2; - - animation: - chip-stand-by 900ms, - chip-in 300ms ease-in-out 900ms; - - &.closing { - animation: content-out 450ms ease-in-out; - animation-fill-mode: forwards; - } - - &.top_left { - margin: $margin-vertical 0 0 $margin-horizontal; - } - - &.top_center { - margin: $margin-vertical 0 0 $margin-horizontal; - } - - &.top_right { - margin: $margin-vertical $margin-horizontal 0 0; - } - - &.bottom_left { - margin: 0 0 $margin-vertical $margin-horizontal; - } - - &.bottom_center { - margin: 0 0 $margin-vertical $margin-horizontal; - } - - &.bottom_right { - margin: 0 $margin-horizontal $margin-vertical 0; - } - } - &__additional-overlay { position: absolute; top: 0; @@ -391,9 +347,49 @@ $margin-vertical: 52px; } } + &__text-container { + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; + overflow: hidden; + } + + &__title { + font-size: 14px; + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: globals.$muted-color; + animation: title-in 450ms ease-in-out 900ms; + } + + &__hidden-icon { + margin-right: 4px; + opacity: 0.5; + } + + &__description { + font-size: 14px; + font-weight: 400; + overflow: hidden; + -webkit-line-clamp: 2; /* number of lines to show */ + line-clamp: 2; + display: -webkit-box; + -webkit-box-orient: vertical; + color: globals.$body-color; + animation: description-in 450ms ease-in-out 900ms; + } + + &.closing &__chip { + animation: content-out 450ms ease-in-out; + animation-fill-mode: forwards; + } + &__chip { position: absolute; - top: -12px; + right: 8px; display: flex; gap: 4px; @@ -405,6 +401,37 @@ $margin-vertical: 52px; animation: chip-stand-by 900ms ease-in-out, chip-in 450ms ease-in-out 900ms; + z-index: 2; + + &.top_left { + top: -12px; + margin: $margin-top 0 0 $margin-horizontal; + } + + &.top_center { + top: -12px; + margin: $margin-top 0 0 $margin-horizontal; + } + + &.top_right { + top: -12px; + margin: $margin-top $margin-horizontal 0 0; + } + + &.bottom_left { + bottom: 70px; + margin: 0 0 $margin-bottom $margin-horizontal; + } + + &.bottom_center { + bottom: 70px; + margin: 0 0 $margin-bottom $margin-horizontal; + } + + &.bottom_right { + bottom: 70px; + margin: 0 $margin-horizontal $margin-bottom 0; + } &__icon { width: 16px; diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx index 9ce551b9..18f361cf 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -30,30 +30,25 @@ export function AchievementNotificationItem({ > {achievement.points && (

-
- - - +{achievement.points} - -
+ + + +{achievement.points} +
)}
diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index c3db23b8..4b7075dd 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -33,6 +33,8 @@ export default function ThemeEditor() { const [notificationVariation, setNotificationVariation] = useState("default"); + const [notificationAlignment, setNotificationAlignment] = + useState("top_left"); const achievementPreview = useMemo(() => { return { @@ -42,9 +44,9 @@ export default function ThemeEditor() { isHidden: notificationVariation === "hidden", isPlatinum: notificationVariation === "platinum", }, - position: "top_center" as AchievementCustomNotificationPosition, + position: notificationAlignment, }; - }, [t, i18n.language, notificationVariation]); + }, [t, i18n.language, notificationVariation, notificationAlignment]); useEffect(() => { window.document.title = "Hydra - Theme Editor"; @@ -95,6 +97,21 @@ export default function ThemeEditor() { } }; + const achievementCustomNotificationPositionOptions = useMemo(() => { + return [ + "top_left", + "top_center", + "top_right", + "bottom_left", + "bottom_center", + "bottom_right", + ].map((position) => ({ + key: position, + value: position, + label: t(position), + })); + }, [t]); + return (
{ return { @@ -156,11 +173,24 @@ export default function ThemeEditor() { } /> - + setNotificationAlignment( + e.target.value as AchievementCustomNotificationPosition + ) + } + options={achievementCustomNotificationPositionOptions} /> + +
+ +
From 0d747d03ab81597f29989857856f5a42f6d835da Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 17:14:09 -0300 Subject: [PATCH 13/22] feat: refactor css --- .../achievement-notification.scss | 162 +++++++----------- .../notification/achievement-notification.tsx | 28 +-- 2 files changed, 75 insertions(+), 115 deletions(-) diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index 1e68714b..d9497d45 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -144,12 +144,12 @@ $margin-bottom: 28px; height: 192px; display: flex; - &.closing * { + &--closing * { animation: none; } - &.closing *::before, - &.closing *::after { + &--closing *::before, + &--closing *::after { animation: none !important; } @@ -179,7 +179,7 @@ $margin-bottom: 28px; align-items: end; } - .achievement-notification__outer-container { + &__outer-container { position: relative; display: grid; width: calc(360px - $margin-horizontal); @@ -190,33 +190,33 @@ $margin-bottom: 28px; content-wait 450ms ease-in-out 450ms, content-expand 450ms ease-in-out 900ms; box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.25); - - &.top_left { - margin: $margin-top 0 0 $margin-horizontal; - } - - &.top_center { - margin: $margin-top 0 0 $margin-horizontal; - } - - &.top_right { - margin: $margin-top $margin-horizontal 0 0; - } - - &.bottom_left { - margin: 0 0 $margin-bottom $margin-horizontal; - } - - &.bottom_center { - margin: 0 0 $margin-bottom $margin-horizontal; - } - - &.bottom_right { - margin: 0 $margin-horizontal $margin-bottom 0; - } } - &.closing .achievement-notification__outer-container { + &.top_left &__outer-container { + margin: $margin-top 0 0 $margin-horizontal; + } + + &.top_center &__outer-container { + margin: $margin-top 0 0 $margin-horizontal; + } + + &.top_right &__outer-container { + margin: $margin-top $margin-horizontal 0 0; + } + + &.bottom_left &__outer-container { + margin: 0 0 $margin-bottom $margin-horizontal; + } + + &.bottom_center &__outer-container { + margin: 0 0 $margin-bottom $margin-horizontal; + } + + &.bottom_right &__outer-container { + margin: 0 $margin-horizontal $margin-bottom 0; + } + + &--closing .achievement-notification__outer-container { animation: content-out 450ms ease-in-out; animation-fill-mode: forwards; } @@ -226,43 +226,13 @@ $margin-bottom: 28px; display: flex; padding: 8px 16px 8px 8px; background: globals.$background-color; - - &.top_left { - align-self: flex-start; - justify-self: flex-start; - } - - &.top_center { - align-self: flex-start; - justify-self: center; - } - - &.top_right { - align-self: flex-start; - justify-self: flex-end; - } - - &.bottom_left { - align-self: flex-end; - justify-self: flex-start; - } - - &.bottom_center { - align-self: flex-end; - justify-self: center; - } - - &.bottom_right { - align-self: flex-end; - justify-self: flex-end; - } } - &.isPlatinum &__container { + &--platinum &__container { background: linear-gradient(94deg, #1c1c1c -25%, #044838 100%); } - &.isRare &__container { + &--rare &__container { &::before { content: ""; position: absolute; @@ -297,12 +267,12 @@ $margin-bottom: 28px; flex: 1; } - &.isRare &__icon { + &--rare &__icon { outline: 1px solid #f4a510; box-shadow: 0px 0px 12px 0px rgba(244, 165, 16, 0.25); } - &.isPlatinum &__icon { + &--platinum &__icon { outline: 1px solid #0cf1ca; box-shadow: 0px 0px 12px 0px rgba(12, 241, 202, 0.25); } @@ -382,7 +352,7 @@ $margin-bottom: 28px; animation: description-in 450ms ease-in-out 900ms; } - &.closing &__chip { + &--closing &__chip { animation: content-out 450ms ease-in-out; animation-fill-mode: forwards; } @@ -403,36 +373,6 @@ $margin-bottom: 28px; chip-in 450ms ease-in-out 900ms; z-index: 2; - &.top_left { - top: -12px; - margin: $margin-top 0 0 $margin-horizontal; - } - - &.top_center { - top: -12px; - margin: $margin-top 0 0 $margin-horizontal; - } - - &.top_right { - top: -12px; - margin: $margin-top $margin-horizontal 0 0; - } - - &.bottom_left { - bottom: 70px; - margin: 0 0 $margin-bottom $margin-horizontal; - } - - &.bottom_center { - bottom: 70px; - margin: 0 0 $margin-bottom $margin-horizontal; - } - - &.bottom_right { - bottom: 70px; - margin: 0 $margin-horizontal $margin-bottom 0; - } - &__icon { width: 16px; height: 16px; @@ -447,7 +387,37 @@ $margin-bottom: 28px; } } - &.isRare &__chip { + &.top_left &__chip { + top: -12px; + margin: $margin-top 0 0 $margin-horizontal; + } + + &.top_center &__chip { + top: -12px; + margin: $margin-top 0 0 $margin-horizontal; + } + + &.top_right &__chip { + top: -12px; + margin: $margin-top $margin-horizontal 0 0; + } + + &.bottom_left &__chip { + bottom: 70px; + margin: 0 0 $margin-bottom $margin-horizontal; + } + + &.bottom_center &__chip { + bottom: 70px; + margin: 0 0 $margin-bottom $margin-horizontal; + } + + &.bottom_right &__chip { + bottom: 70px; + margin: 0 $margin-horizontal $margin-bottom 0; + } + + &--rare &__chip { &__icon { path { fill: #fff; @@ -475,7 +445,7 @@ $margin-bottom: 28px; ); } - &.isPlatinum &__chip { + &--platinum &__chip { &__icon { path { fill: #fff; diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx index 18f361cf..76c2b0f8 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -18,22 +18,20 @@ export function AchievementNotificationItem({ achievement, isClosing, }: Readonly) { + const baseClassName = "achievement-notification"; + return (
{achievement.points && ( -
+
+{achievement.points} @@ -41,16 +39,8 @@ export function AchievementNotificationItem({
)} -
-
+
+
Date: Sat, 17 May 2025 17:18:49 -0300 Subject: [PATCH 14/22] fix: trophy and ellipses --- .../achievement-notification.scss | 20 ++++++++++--------- .../notification/achievement-notification.tsx | 6 ++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index d9497d45..53d4a8e1 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -144,15 +144,6 @@ $margin-bottom: 28px; height: 192px; display: flex; - &--closing * { - animation: none; - } - - &--closing *::before, - &--closing *::after { - animation: none !important; - } - &.top_left { align-items: start; } @@ -292,6 +283,7 @@ $margin-bottom: 28px; height: 64px; background: #000; opacity: 0; + z-index: 1; animation: dark-overlay 900ms ease-in-out; } @@ -302,6 +294,7 @@ $margin-bottom: 28px; width: 36px; height: 36px; opacity: 0; + z-index: 1; animation: trophy-out 900ms ease-in-out; } @@ -472,4 +465,13 @@ $margin-bottom: 28px; #083e31 85.77% ); } + + &--closing * { + animation: none; + } + + &--closing *::before, + &--closing *::after { + animation: none !important; + } } diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx index 76c2b0f8..4b7b98ec 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -6,6 +6,8 @@ import cn from "classnames"; import "./achievement-notification.scss"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; import { EyeClosedIcon } from "@primer/octicons-react"; +import Trophy from "@renderer/assets/icons/trophy.png"; +import Ellipses from "@renderer/assets/icons/ellipses.png"; interface AchievementNotificationProps { position: AchievementCustomNotificationPosition; @@ -66,12 +68,12 @@ export function AchievementNotificationItem({
Ellipses effect Trophy effect
From 3cc4ee3ee4899a396da5e70104a4e207acd8b26f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 17:42:46 -0300 Subject: [PATCH 15/22] feat: trophy gradient for variations --- src/renderer/src/assets/icons/trophy.png | Bin 417 -> 0 bytes src/renderer/src/assets/icons/trophy.svg | 5 + .../achievement-notification.scss | 105 ++++++++++++------ .../notification/achievement-notification.tsx | 11 +- 4 files changed, 82 insertions(+), 39 deletions(-) delete mode 100644 src/renderer/src/assets/icons/trophy.png create mode 100644 src/renderer/src/assets/icons/trophy.svg diff --git a/src/renderer/src/assets/icons/trophy.png b/src/renderer/src/assets/icons/trophy.png deleted file mode 100644 index 73ae25e066c3d4a54941ee61590e4fdc69803bd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 417 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4mJh`hKCF@W-u@?a29w(7Bet#3xhBt!>lvsG#`=Bd{c;ur zXo$>XNsHUm8+xibZo)YcGmWJeRlYg6xfE4SUa@6WQ?HkaxZ}^`me*sutc>-%wEUy)32pa;p+ZH zr!Pb;ns)Jwp~9;0#dEi<>3bsn?WMfZ#&qfGtBF@tpDbFo?`EEw$<{Zi&)Rm*$~|LX zQsY;tC*9M?;cr?0tET!;M8}WxWsbP0l+XkKf9$3( diff --git a/src/renderer/src/assets/icons/trophy.svg b/src/renderer/src/assets/icons/trophy.svg new file mode 100644 index 00000000..7be58814 --- /dev/null +++ b/src/renderer/src/assets/icons/trophy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index 53d4a8e1..e1df81d7 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -274,40 +274,83 @@ $margin-bottom: 28px; left: 0; width: 80px; height: 80px; + } - &__dark { - position: absolute; - top: 8px; - left: 8px; - width: 64px; - height: 64px; - background: #000; - opacity: 0; - z-index: 1; - animation: dark-overlay 900ms ease-in-out; - } + &__dark-overlay { + position: absolute; + top: 8px; + left: 8px; + width: 64px; + height: 64px; + background: #000; + opacity: 0; + z-index: 1; + animation: dark-overlay 900ms ease-in-out; + } - &__trophy { - position: absolute; - top: 22px; - left: 22px; - width: 36px; - height: 36px; - opacity: 0; - z-index: 1; - animation: trophy-out 900ms ease-in-out; - } + &__trophy-overlay { + position: absolute; + mask-image: url("/src/assets/icons/trophy.svg"); + top: 22px; + left: 22px; + width: 36px; + height: 36px; + opacity: 0; + z-index: 1; + animation: trophy-out 900ms ease-in-out; + background: #fff; + } - &__ellipses { - position: absolute; - top: 8px; - left: 8px; - width: 64px; - height: 64px; - z-index: 2; - opacity: 0; - animation: ellipses-out 900ms ease-in-out; - } + &--rare &__trophy-overlay { + background: linear-gradient( + 118deg, + #e8ad15 18.96%, + #d5900f 26.41%, + #e8ad15 29.99%, + #e4aa15 38.89%, + #ca890e 42.43%, + #ca880e 46.59%, + #ecbe1a 50.08%, + #ecbd1a 53.48%, + #b3790d 57.39%, + #66470a 75.64%, + #a37a13 78.2%, + #987112 79.28%, + #503808 83.6%, + #3e2d08 85.77% + ), + #fff; + } + + &--platinum &__trophy-overlay { + background: linear-gradient( + 118deg, + #15e8d6 18.96%, + #0fd5a7 26.41%, + #15e8b7 29.99%, + #15e4b4 38.89%, + #0eca7f 42.43%, + #0eca9e 46.59%, + #1aecbb 50.08%, + #1aecb0 53.48%, + #0db392 57.39%, + #0a6648 75.64%, + #13a38b 78.2%, + #129862 79.28%, + #085042 83.6%, + #083e31 85.77% + ); + } + + &__ellipses-overlay { + position: absolute; + top: 8px; + left: 8px; + width: 64px; + height: 64px; + z-index: 2; + opacity: 0; + animation: ellipses-out 900ms ease-in-out; } &__text-container { diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx index 4b7b98ec..edd11d6a 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -6,7 +6,6 @@ import cn from "classnames"; import "./achievement-notification.scss"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; import { EyeClosedIcon } from "@primer/octicons-react"; -import Trophy from "@renderer/assets/icons/trophy.png"; import Ellipses from "@renderer/assets/icons/ellipses.png"; interface AchievementNotificationProps { @@ -65,17 +64,13 @@ export function AchievementNotificationItem({
-
+
Ellipses effect - Trophy effect +
From 1247a105a07f2446addd7d08cd6de6e5073551e4 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 18:06:16 -0300 Subject: [PATCH 16/22] feat: info for rare and platinum achievements --- .../achievements/merge-achievements.ts | 33 +++++++++++++------ .../notification/achievement-notification.tsx | 2 +- .../aparence/modals/add-theme-modal.tsx | 10 ------ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 6e070c7d..801b53c7 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -14,6 +14,12 @@ import { SubscriptionRequiredError } from "@shared"; import { achievementsLogger } from "../logger"; import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; +const isRareAchievement = (points: number) => { + const rawPercentage = (50 - Math.sqrt(points)) * 2; + + return rawPercentage < 10; +}; + const saveAchievementsOnLocal = async ( objectId: string, shop: GameShop, @@ -87,7 +93,7 @@ export const mergeAchievements = async ( publishNotification && userPreferences?.achievementNotificationsEnabled ) { - const achievementsInfo: AchievementNotificationInfo[] = newAchievements + const filteredAchievements = newAchievements .toSorted((a, b) => { return a.unlockTime - b.unlockTime; }) @@ -99,16 +105,23 @@ export const mergeAchievements = async ( ); }); }) - .filter((achievement) => Boolean(achievement)) - .map((achievement) => { + .filter((achievement) => !!achievement); + + const achievementsInfo: AchievementNotificationInfo[] = + filteredAchievements.map((achievement, index) => { return { - title: achievement!.displayName, - description: achievement!.description, - points: achievement!.points, - isHidden: achievement!.hidden, - isRare: false, - isPlatinum: false, - iconUrl: achievement!.icon, + title: achievement.displayName, + description: achievement.description, + points: achievement.points, + isHidden: achievement.hidden, + isRare: achievement.points + ? isRareAchievement(achievement.points) + : false, + isPlatinum: + index === filteredAchievements.length - 1 && + newAchievements.length + unlockedAchievements.length === + achievementsData.length, + iconUrl: achievement.icon, }; }); diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 8b431b32..fc71723e 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -8,7 +8,7 @@ import { import { injectCustomCss } from "@renderer/helpers"; import { AchievementNotificationItem } from "@renderer/components/achievements/notification/achievement-notification"; -const NOTIFICATION_TIMEOUT = 40000; +const NOTIFICATION_TIMEOUT = 4000; export function AchievementNotification() { const { t } = useTranslation("achievement"); diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index 4a5babc6..58bcf58d 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -50,16 +50,6 @@ const DEFAULT_THEME_CODE = `/* /* Button */ .button {} - -/* Custom Achievement Notification */ -.achievement-notification {} -.achievement-notification__container {} -.achievement-notification__content {} -.achievement-notification__icon {} -.achievement-notification__text-container {} -.achievement-notification__title {} -.achievement-notification__description {} - `; export function AddThemeModal({ From c85f46844eaaa062299e11533ce996b2bfd9067a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 18:39:15 -0300 Subject: [PATCH 17/22] feat: adjust gradient angle --- .../achievement-notification.scss | 24 +++++++++---------- src/shared/index.ts | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index e1df81d7..c8fcbe33 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -454,16 +454,8 @@ $margin-bottom: 28px; } &--rare &__chip { - &__icon { - path { - fill: #fff; - } - } - &__label { - color: #fff; - } background: linear-gradient( - 118deg, + 160deg, #e8ad15 18.96%, #d5900f 26.41%, #e8ad15 29.99%, @@ -479,9 +471,6 @@ $margin-bottom: 28px; #503808 83.6%, #3e2d08 85.77% ); - } - - &--platinum &__chip { &__icon { path { fill: #fff; @@ -490,6 +479,9 @@ $margin-bottom: 28px; &__label { color: #fff; } + } + + &--platinum &__chip { background: linear-gradient( 118deg, #15e8d6 18.96%, @@ -507,6 +499,14 @@ $margin-bottom: 28px; #085042 83.6%, #083e31 85.77% ); + &__icon { + path { + fill: #fff; + } + } + &__label { + color: #fff; + } } &--closing * { diff --git a/src/shared/index.ts b/src/shared/index.ts index d2c968e7..98b20f10 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -191,7 +191,7 @@ export const generateAchievementCustomNotificationTest = ( lng: language ?? "en", }), iconUrl: "https://cdn.losbroxas.org/favicon.svg", - points: 100, + points: 2440, isHidden: false, isRare: false, isPlatinum: false, From 5475708b36b718e7a6eda2d713506934b6b5f309 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 18:53:39 -0300 Subject: [PATCH 18/22] feat: i18n --- src/locales/en/translation.json | 8 +++++- src/locales/pt-BR/translation.json | 8 +++++- src/main/services/window-manager.ts | 28 ++++++++++++------- .../src/pages/settings/settings-general.scss | 4 +++ .../src/pages/settings/settings-general.tsx | 24 ++++++++++++---- .../src/pages/theme-editor/theme-editor.scss | 4 +++ .../src/pages/theme-editor/theme-editor.tsx | 6 ++-- 7 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 64a83287..0ee177c2 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -371,7 +371,13 @@ "bottom_left": "Bottom left", "bottom_center": "Bottom center", "bottom_right": "Bottom right", - "enable_achievement_custom_notifications": "Enable achievement custom notifications" + "enable_achievement_custom_notifications": "Enable achievement custom notifications", + "alignment": "Alignment", + "variation": "Variation", + "default": "Default", + "rare": "Rare", + "platinum": "Platinum", + "hidden": "Hidden" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index d9aa7376..a678a472 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -357,7 +357,13 @@ "bottom_left": "Inferior esquerdo", "bottom_right": "Inferior direito", "bottom_center": "Inferior central", - "achievement_custom_notification_position": "Posição das notificações customizadas de conquista" + "achievement_custom_notification_position": "Posição das notificações customizadas de conquista", + "alignment": "Alinhamento", + "variation": "Variação", + "default": "Padrão", + "rare": "Rara", + "platinum": "Platina", + "hidden": "Oculta" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 1ea5ea1d..ed540c03 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -366,23 +366,31 @@ export class WindowManager { this.loadNotificationWindowURL(); this.notificationWindow.once("ready-to-show", () => { - if (isStaging) { - this.notificationWindow?.webContents.openDevTools(); - } - if (showTestNotification) { - const language = userPreferences.language ?? "en"; setTimeout(() => { - this.notificationWindow?.webContents.send( - "on-achievement-unlocked", - userPreferences.achievementCustomNotificationPosition ?? "top_left", - [generateAchievementCustomNotificationTest(t, language)] - ); + this.showTestNotification(); }, 1000); } }); } + public static async showTestNotification() { + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + + const language = userPreferences.language ?? "en"; + + this.notificationWindow?.webContents.send( + "on-achievement-unlocked", + userPreferences.achievementCustomNotificationPosition ?? "top_left", + [generateAchievementCustomNotificationTest(t, language)] + ); + } + public static async closeNotificationWindow() { if (this.notificationWindow) { this.notificationWindow.close(); diff --git a/src/renderer/src/pages/settings/settings-general.scss b/src/renderer/src/pages/settings/settings-general.scss index 03798462..302effa3 100644 --- a/src/renderer/src/pages/settings/settings-general.scss +++ b/src/renderer/src/pages/settings/settings-general.scss @@ -13,4 +13,8 @@ &__common-redist-button { align-self: flex-start; } + + &__test-achievement-notification-button { + align-self: flex-start; + } } diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 792c4603..5de83e40 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -277,12 +277,24 @@ export function SettingsGeneral() { {form.achievementNotificationsEnabled && form.achievementCustomNotificationsEnabled && ( - + <> + + + + )}

{t("common_redist")}

diff --git a/src/renderer/src/pages/theme-editor/theme-editor.scss b/src/renderer/src/pages/theme-editor/theme-editor.scss index 346e8230..288ce1bf 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.scss +++ b/src/renderer/src/pages/theme-editor/theme-editor.scss @@ -44,8 +44,11 @@ } &__footer { + display: flex; + flex-direction: column; background-color: globals.$dark-background-color; padding: globals.$spacing-unit globals.$spacing-unit * 2; + gap: 24px; &-actions { display: flex; @@ -82,6 +85,7 @@ } &__notification-preview { + padding-top: 12px; display: flex; flex-direction: row; align-items: center; diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index 4b7075dd..24c175b1 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -156,13 +156,13 @@ export default function ThemeEditor() {
{ return { key: variation, value: variation, - label: variation, + label: t(variation), }; } )} @@ -174,7 +174,7 @@ export default function ThemeEditor() { /> setNotificationAlignment( From 5ae67a3dc725322bb4a38b60e22bdfe299101c9d Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 17 May 2025 22:59:38 +0100 Subject: [PATCH 19/22] feat: renaming class names to BEM --- .../achievement-watcher-manager.ts | 2 +- .../achievements/merge-achievements.ts | 2 +- src/main/services/window-manager.ts | 12 +++---- .../achievement-notification.scss | 36 +++++++++---------- .../notification/achievement-notification.tsx | 2 +- .../notification/achievement-notification.tsx | 2 +- .../src/pages/settings/settings-general.tsx | 16 ++++----- .../src/pages/theme-editor/theme-editor.tsx | 14 ++++---- src/types/level.types.ts | 12 +++---- 9 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 97746ca0..c010f984 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -281,7 +281,7 @@ export class AchievementWatcherManager { totalNewGamesWithAchievements, totalNewAchievements, userPreferences.achievementCustomNotificationPosition ?? - "top_left" + "top-left" ); } else { publishCombinedNewAchievementNotification( diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 801b53c7..6b40ad72 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -128,7 +128,7 @@ export const mergeAchievements = async ( if (userPreferences?.achievementCustomNotificationsEnabled !== false) { WindowManager.notificationWindow?.webContents.send( "on-achievement-unlocked", - userPreferences.achievementCustomNotificationPosition ?? "top_left", + userPreferences.achievementCustomNotificationPosition ?? "top-left", achievementsInfo ); } else { diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 1ea5ea1d..91232f6e 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -289,35 +289,35 @@ export class WindowManager { const display = screen.getPrimaryDisplay(); const { width, height } = display.workAreaSize; - if (position === "bottom_center") { + if (position === "bottom-center") { return { x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if (position === "bottom_right") { + if (position === "bottom-right") { return { x: width - this.NOTIFICATION_WINDOW_WIDTH, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if (position === "top_center") { + if (position === "top-center") { return { x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, y: 0, }; } - if (position === "bottom_left") { + if (position === "bottom-left") { return { x: 0, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if (position === "top_right") { + if (position === "top-right") { return { x: width - this.NOTIFICATION_WINDOW_WIDTH, y: 0, @@ -375,7 +375,7 @@ export class WindowManager { setTimeout(() => { this.notificationWindow?.webContents.send( "on-achievement-unlocked", - userPreferences.achievementCustomNotificationPosition ?? "top_left", + userPreferences.achievementCustomNotificationPosition ?? "top-left", [generateAchievementCustomNotificationTest(t, language)] ); }, 1000); diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index e1df81d7..c1c9eb45 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -144,28 +144,28 @@ $margin-bottom: 28px; height: 192px; display: flex; - &.top_left { + &--top-left { align-items: start; } - &.top_center { + &--top-center { align-items: start; } - &.top_right { + &--top-right { justify-content: end; align-items: start; } - &.bottom_left { + &--bottom-left { align-items: end; } - &.bottom_center { + &--bottom-center { align-items: end; } - &.bottom_right { + &--bottom-right { justify-content: end; align-items: end; } @@ -183,27 +183,27 @@ $margin-bottom: 28px; box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.25); } - &.top_left &__outer-container { + &--top-left &__outer-container { margin: $margin-top 0 0 $margin-horizontal; } - &.top_center &__outer-container { + &--top-center &__outer-container { margin: $margin-top 0 0 $margin-horizontal; } - &.top_right &__outer-container { + &--top-right &__outer-container { margin: $margin-top $margin-horizontal 0 0; } - &.bottom_left &__outer-container { + &--bottom-left &__outer-container { margin: 0 0 $margin-bottom $margin-horizontal; } - &.bottom_center &__outer-container { + &--bottom-center &__outer-container { margin: 0 0 $margin-bottom $margin-horizontal; } - &.bottom_right &__outer-container { + &--bottom-right &__outer-container { margin: 0 $margin-horizontal $margin-bottom 0; } @@ -423,32 +423,32 @@ $margin-bottom: 28px; } } - &.top_left &__chip { + &--top-left &__chip { top: -12px; margin: $margin-top 0 0 $margin-horizontal; } - &.top_center &__chip { + &--top-center &__chip { top: -12px; margin: $margin-top 0 0 $margin-horizontal; } - &.top_right &__chip { + &--top-right &__chip { top: -12px; margin: $margin-top $margin-horizontal 0 0; } - &.bottom_left &__chip { + &--bottom-left &__chip { bottom: 70px; margin: 0 0 $margin-bottom $margin-horizontal; } - &.bottom_center &__chip { + &--bottom-center &__chip { bottom: 70px; margin: 0 0 $margin-bottom $margin-horizontal; } - &.bottom_right &__chip { + &--bottom-right &__chip { bottom: 70px; margin: 0 $margin-horizontal $margin-bottom 0; } diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx index edd11d6a..1eefda8e 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -24,7 +24,7 @@ export function AchievementNotificationItem({ return (
("top_left"); + useState("top-left"); const [achievements, setAchievements] = useState< AchievementNotificationInfo[] diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 792c4603..a0562374 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -41,7 +41,7 @@ export function SettingsGeneral() { achievementNotificationsEnabled: false, achievementCustomNotificationsEnabled: true, achievementCustomNotificationPosition: - "top_left" as AchievementCustomNotificationPosition, + "top-left" as AchievementCustomNotificationPosition, language: "", customStyles: window.localStorage.getItem("customStyles") || "", }); @@ -108,7 +108,7 @@ export function SettingsGeneral() { achievementCustomNotificationsEnabled: userPreferences.achievementCustomNotificationsEnabled ?? true, achievementCustomNotificationPosition: - userPreferences.achievementCustomNotificationPosition ?? "top_left", + userPreferences.achievementCustomNotificationPosition ?? "top-left", friendRequestNotificationsEnabled: userPreferences.friendRequestNotificationsEnabled ?? false, language: language ?? "en", @@ -118,12 +118,12 @@ export function SettingsGeneral() { const achievementCustomNotificationPositionOptions = useMemo(() => { return [ - "top_left", - "top_center", - "top_right", - "bottom_left", - "bottom_center", - "bottom_right", + "top-left", + "top-center", + "top-right", + "bottom-left", + "bottom-center", + "bottom-right", ].map((position) => ({ key: position, value: position, diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index 4b7075dd..9d289c41 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -34,7 +34,7 @@ export default function ThemeEditor() { const [notificationVariation, setNotificationVariation] = useState("default"); const [notificationAlignment, setNotificationAlignment] = - useState("top_left"); + useState("top-left"); const achievementPreview = useMemo(() => { return { @@ -99,12 +99,12 @@ export default function ThemeEditor() { const achievementCustomNotificationPositionOptions = useMemo(() => { return [ - "top_left", - "top_center", - "top_right", - "bottom_left", - "bottom_center", - "bottom_right", + "top-left", + "top-center", + "top-right", + "bottom-left", + "bottom-center", + "bottom-right", ].map((position) => ({ key: position, value: position, diff --git a/src/types/level.types.ts b/src/types/level.types.ts index bdbb72a7..bca933c1 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -71,12 +71,12 @@ export interface GameAchievement { } export type AchievementCustomNotificationPosition = - | "top_left" - | "top_center" - | "top_right" - | "bottom_left" - | "bottom_center" - | "bottom_right"; + | "top-left" + | "top-center" + | "top-right" + | "bottom-left" + | "bottom-center" + | "bottom-right"; export interface UserPreferences { downloadsPath?: string | null; From ee916b998a8a5976a18c6954da168c0c57e8d395 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 19:06:01 -0300 Subject: [PATCH 20/22] feat: refactor test notiication event --- src/locales/en/translation.json | 12 ++++++------ src/locales/pt-BR/translation.json | 12 ++++++------ src/main/events/index.ts | 1 + .../show-achievement-test-notification.ts | 15 +++++++++++++++ .../update-achievement-notification-window.ts | 2 +- src/main/services/window-manager.ts | 16 +++++----------- src/preload/index.ts | 2 ++ src/renderer/src/declaration.d.ts | 1 + .../src/pages/settings/settings-general.tsx | 4 +--- 9 files changed, 38 insertions(+), 27 deletions(-) create mode 100644 src/main/events/notifications/show-achievement-test-notification.ts diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 0ee177c2..db9e1a72 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -365,12 +365,12 @@ "show_download_speed_in_megabytes": "Show download speed in megabytes per second", "extract_files_by_default": "Extract files by default after download", "achievement_custom_notification_position": "Achievement custom notification position", - "top_left": "Top left", - "top_center": "Top center", - "top_right": "Top right", - "bottom_left": "Bottom left", - "bottom_center": "Bottom center", - "bottom_right": "Bottom right", + "top-left": "Top left", + "top-center": "Top center", + "top-right": "Top right", + "bottom-left": "Bottom left", + "bottom-center": "Bottom center", + "bottom-right": "Bottom right", "enable_achievement_custom_notifications": "Enable achievement custom notifications", "alignment": "Alignment", "variation": "Variation", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index a678a472..7d95ee0e 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -351,12 +351,12 @@ "show_download_speed_in_megabytes": "Exibir taxas de download em megabytes por segundo", "extract_files_by_default": "Extrair arquivos automaticamente após o download", "enable_achievement_custom_notifications": "Habilitar notificações customizadas de conquistas", - "top_left": "Superior esquerdo", - "top_center": "Superior central", - "top_right": "Superior direito", - "bottom_left": "Inferior esquerdo", - "bottom_right": "Inferior direito", - "bottom_center": "Inferior central", + "top-left": "Superior esquerdo", + "top-center": "Superior central", + "top-right": "Superior direito", + "bottom-left": "Inferior esquerdo", + "bottom-right": "Inferior direito", + "bottom-center": "Inferior central", "achievement_custom_notification_position": "Posição das notificações customizadas de conquista", "alignment": "Alinhamento", "variation": "Variação", diff --git a/src/main/events/index.ts b/src/main/events/index.ts index d937e3c4..d0f900d9 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -88,6 +88,7 @@ import "./cloud-save/delete-game-artifact"; import "./cloud-save/select-game-backup-path"; import "./notifications/publish-new-repacks-notification"; import "./notifications/update-achievement-notification-window"; +import "./notifications/show-achievement-test-notification"; import "./themes/add-custom-theme"; import "./themes/delete-custom-theme"; import "./themes/get-all-custom-themes"; diff --git a/src/main/events/notifications/show-achievement-test-notification.ts b/src/main/events/notifications/show-achievement-test-notification.ts new file mode 100644 index 00000000..b6f425f3 --- /dev/null +++ b/src/main/events/notifications/show-achievement-test-notification.ts @@ -0,0 +1,15 @@ +import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; + +const showAchievementTestNotification = async ( + _event: Electron.IpcMainInvokeEvent +) => { + setTimeout(() => { + WindowManager.showAchievementTestNotification(); + }, 1000); +}; + +registerEvent( + "showAchievementTestNotification", + showAchievementTestNotification +); diff --git a/src/main/events/notifications/update-achievement-notification-window.ts b/src/main/events/notifications/update-achievement-notification-window.ts index 93a2feb7..48fba272 100644 --- a/src/main/events/notifications/update-achievement-notification-window.ts +++ b/src/main/events/notifications/update-achievement-notification-window.ts @@ -19,7 +19,7 @@ const updateAchievementCustomNotificationWindow = async ( userPreferences.achievementNotificationsEnabled && userPreferences.achievementCustomNotificationsEnabled !== false ) { - WindowManager.createNotificationWindow(true); + WindowManager.createNotificationWindow(); } }; diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 647db95b..0c58c867 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -330,7 +330,9 @@ export class WindowManager { }; } - public static async createNotificationWindow(showTestNotification = false) { + public static async createNotificationWindow() { + if (this.notificationWindow) return; + const userPreferences = await db.get( levelKeys.userPreferences, { @@ -364,17 +366,9 @@ export class WindowManager { // }); this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); this.loadNotificationWindowURL(); - - this.notificationWindow.once("ready-to-show", () => { - if (showTestNotification) { - setTimeout(() => { - this.showTestNotification(); - }, 1000); - } - }); } - public static async showTestNotification() { + public static async showAchievementTestNotification() { const userPreferences = await db.get( levelKeys.userPreferences, { @@ -386,7 +380,7 @@ export class WindowManager { this.notificationWindow?.webContents.send( "on-achievement-unlocked", - userPreferences.achievementCustomNotificationPosition ?? "top_left", + userPreferences.achievementCustomNotificationPosition ?? "top-left", [generateAchievementCustomNotificationTest(t, language)] ); } diff --git a/src/preload/index.ts b/src/preload/index.ts index c80f60ba..482b9d19 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -442,6 +442,8 @@ contextBridge.exposeInMainWorld("electron", { }, updateAchievementCustomNotificationWindow: () => ipcRenderer.invoke("updateAchievementCustomNotificationWindow"), + showAchievementTestNotification: () => + ipcRenderer.invoke("showAchievementTestNotification"), /* Themes */ addCustomTheme: (theme: Theme) => ipcRenderer.invoke("addCustomTheme", theme), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 5cdd8c04..cc7261e1 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -338,6 +338,7 @@ declare global { ) => void ) => () => Electron.IpcRenderer; updateAchievementCustomNotificationWindow: () => Promise; + showAchievementTestNotification: () => Promise; /* Themes */ addCustomTheme: (theme: Theme) => Promise; diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 11bbf476..96a69fd0 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -288,9 +288,7 @@ export function SettingsGeneral() { From 15ddc7144575993c60b888433b70e8ef174f71ee Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 19:24:02 -0300 Subject: [PATCH 21/22] feat: i18n --- src/locales/en/translation.json | 3 ++- src/locales/pt-BR/translation.json | 3 ++- src/renderer/src/pages/settings/settings-general.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index db9e1a72..5e04710e 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -377,7 +377,8 @@ "default": "Default", "rare": "Rare", "platinum": "Platinum", - "hidden": "Hidden" + "hidden": "Hidden", + "test_notification": "Test notification" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 7d95ee0e..d06270ed 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -363,7 +363,8 @@ "default": "Padrão", "rare": "Rara", "platinum": "Platina", - "hidden": "Oculta" + "hidden": "Oculta", + "test_notification": "Testar notificação" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 96a69fd0..aa46a519 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -290,7 +290,7 @@ export function SettingsGeneral() { className="settings-general__test-achievement-notification-button" onClick={() => window.electron.showAchievementTestNotification()} > - Test Notification + {t("test_notification")} )} From 87a994f0f087517770889124a0f63428657d7b96 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 17 May 2025 19:32:32 -0300 Subject: [PATCH 22/22] feat: i18n and preview fix --- src/locales/en/translation.json | 3 ++- src/locales/pt-BR/translation.json | 3 ++- .../achievements/notification/achievement-notification.scss | 1 - src/renderer/src/pages/theme-editor/theme-editor.scss | 6 ++++++ src/renderer/src/pages/theme-editor/theme-editor.tsx | 4 ++-- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 5e04710e..93e18f17 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -378,7 +378,8 @@ "rare": "Rare", "platinum": "Platinum", "hidden": "Hidden", - "test_notification": "Test notification" + "test_notification": "Test notification", + "notification_preview": "Achievement Notification Preview" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index d06270ed..c99fab6f 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -364,7 +364,8 @@ "rare": "Rara", "platinum": "Platina", "hidden": "Oculta", - "test_notification": "Testar notificação" + "test_notification": "Testar notificação", + "notification_preview": "Prévia da Notificação de Conquistas" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index 9b26e8c4..4dbcc7d4 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -395,7 +395,6 @@ $margin-bottom: 28px; &__chip { position: absolute; - right: 8px; display: flex; gap: 4px; diff --git a/src/renderer/src/pages/theme-editor/theme-editor.scss b/src/renderer/src/pages/theme-editor/theme-editor.scss index 288ce1bf..38061c88 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.scss +++ b/src/renderer/src/pages/theme-editor/theme-editor.scss @@ -43,6 +43,12 @@ flex: 1; } + &__notification-preview-wrapper { + position: relative; + border: 1px solid globals.$muted-color; + border-radius: 2px; + } + &__footer { display: flex; flex-direction: column; diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index f52f0197..94ad2e79 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -152,7 +152,7 @@ export default function ThemeEditor() {
- +
-
+