diff --git a/package.json b/package.json index cd480ee5..a785c25f 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "react-loading-skeleton": "^3.4.0", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", + "react-shadow": "^20.6.0", "react-tooltip": "^5.28.0", "sound-play": "^1.1.0", "steam-shortcut-editor": "https://github.com/hydralauncher/steam-shortcut-editor", diff --git a/src/main/events/themes/toggle-custom-theme.ts b/src/main/events/themes/toggle-custom-theme.ts index 50440551..59f66793 100644 --- a/src/main/events/themes/toggle-custom-theme.ts +++ b/src/main/events/themes/toggle-custom-theme.ts @@ -1,5 +1,6 @@ import { themesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; const toggleCustomTheme = async ( _event: Electron.IpcMainInvokeEvent, @@ -17,6 +18,8 @@ const toggleCustomTheme = async ( isActive, updatedAt: new Date(), }); + + WindowManager.notificationWindow?.webContents.send("on-custom-theme-updated"); }; registerEvent("toggleCustomTheme", toggleCustomTheme); diff --git a/src/main/events/themes/update-custom-theme.ts b/src/main/events/themes/update-custom-theme.ts index 92d85b8b..f71c50da 100644 --- a/src/main/events/themes/update-custom-theme.ts +++ b/src/main/events/themes/update-custom-theme.ts @@ -20,8 +20,10 @@ const updateCustomTheme = async ( }); if (theme.isActive) { - WindowManager.mainWindow?.webContents.send("css-injected", code); - WindowManager.notificationWindow?.webContents.send("css-injected", code); + WindowManager.mainWindow?.webContents.send("on-custom-theme-updated"); + WindowManager.notificationWindow?.webContents.send( + "on-custom-theme-updated" + ); } }; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 0f5a4d21..329e1457 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -29,7 +29,7 @@ export class HydraApi { private static instance: AxiosInstance; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes - private static readonly ADD_LOG_INTERCEPTOR = true; + private static readonly ADD_LOG_INTERCEPTOR = false; private static secondsToMilliseconds(seconds: number) { return seconds * 1000; diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 7c789bf9..a978f4eb 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -289,6 +289,21 @@ export class WindowManager { const display = screen.getPrimaryDisplay(); const { width, height } = display.workAreaSize; + console.log( + width, + height, + this.NOTIFICATION_WINDOW_HEIGHT, + this.NOTIFICATION_WINDOW_WIDTH, + position + ); + + if (position === "bottom-left") { + return { + x: 0, + y: height - this.NOTIFICATION_WINDOW_HEIGHT, + }; + } + if (position === "bottom-center") { return { x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, @@ -310,13 +325,6 @@ export class WindowManager { }; } - if (position === "bottom-left") { - return { - x: 0, - y: height - this.NOTIFICATION_WINDOW_HEIGHT, - }; - } - if (position === "top-right") { return { x: width - this.NOTIFICATION_WINDOW_WIDTH, @@ -372,8 +380,13 @@ export class WindowManager { // this.notificationWindow.setVisibleOnAllWorkspaces(true, { // visibleOnFullScreen: true, // }); + this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); this.loadNotificationWindowURL(); + + if (isStaging) { + this.notificationWindow.webContents.openDevTools(); + } } public static async showAchievementTestNotification() { diff --git a/src/preload/index.ts b/src/preload/index.ts index 482b9d19..42f8d538 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -462,11 +462,11 @@ contextBridge.exposeInMainWorld("electron", { /* Editor */ openEditorWindow: (themeId: string) => ipcRenderer.invoke("openEditorWindow", themeId), - onCssInjected: (cb: (cssString: string) => void) => { - const listener = (_event: Electron.IpcRendererEvent, cssString: string) => - cb(cssString); - ipcRenderer.on("css-injected", listener); - return () => ipcRenderer.removeListener("css-injected", listener); + onCustomThemeUpdated: (cb: () => void) => { + const listener = (_event: Electron.IpcRendererEvent) => cb(); + ipcRenderer.on("on-custom-theme-updated", listener); + return () => + ipcRenderer.removeListener("on-custom-theme-updated", listener); }, closeEditorWindow: (themeId?: string) => ipcRenderer.invoke("closeEditorWindow", themeId), diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index f22e0361..7cd6eaa4 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -28,7 +28,7 @@ import { downloadSourcesTable } from "./dexie"; import { useSubscription } from "./hooks/use-subscription"; import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-modal"; -import { injectCustomCss } from "./helpers"; +import { injectCustomCss, removeCustomCss } from "./helpers"; import "./app.scss"; export interface AppProps { @@ -246,17 +246,27 @@ export function App() { }; }, [updateRepacks]); - useEffect(() => { - const loadAndApplyTheme = async () => { - const activeTheme = await window.electron.getActiveCustomTheme(); - - if (activeTheme?.code) { - injectCustomCss(activeTheme.code); - } - }; - loadAndApplyTheme(); + const loadAndApplyTheme = useCallback(async () => { + const activeTheme = await window.electron.getActiveCustomTheme(); + if (activeTheme?.code) { + injectCustomCss(activeTheme.code); + } else { + removeCustomCss(); + } }, []); + useEffect(() => { + loadAndApplyTheme(); + }, [loadAndApplyTheme]); + + useEffect(() => { + const unsubscribe = window.electron.onCustomThemeUpdated(() => { + loadAndApplyTheme(); + }); + + return () => unsubscribe(); + }, [loadAndApplyTheme]); + const playAudio = useCallback(() => { const audio = new Audio(achievementSound); audio.volume = 0.2; @@ -273,14 +283,6 @@ export function App() { }; }, [playAudio]); - useEffect(() => { - const unsubscribe = window.electron.onCssInjected((cssString) => { - injectCustomCss(cssString); - }); - - return () => unsubscribe(); - }, []); - const handleToastClose = useCallback(() => { dispatch(closeToast()); }, [dispatch]); diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index 4dbcc7d4..0a41782e 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -141,7 +141,7 @@ $margin-bottom: 28px; .achievement-notification { width: 360px; - height: 192px; + height: 140px; display: flex; &--top-left { diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.tsx b/src/renderer/src/components/achievements/notification/achievement-notification.tsx index 1eefda8e..7bb681fe 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/components/achievements/notification/achievement-notification.tsx @@ -3,10 +3,10 @@ import { AchievementNotificationInfo, } from "@types"; import cn from "classnames"; -import "./achievement-notification.scss"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; import { EyeClosedIcon } from "@primer/octicons-react"; import Ellipses from "@renderer/assets/icons/ellipses.png"; +import "./achievement-notification.scss"; interface AchievementNotificationProps { position: AchievementCustomNotificationPosition; diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index cc7261e1..3290d798 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -352,9 +352,7 @@ declare global { /* Editor */ openEditorWindow: (themeId: string) => Promise; - onCssInjected: ( - cb: (cssString: string) => void - ) => () => Electron.IpcRenderer; + onCustomThemeUpdated: (cb: () => void) => () => Electron.IpcRenderer; closeEditorWindow: (themeId?: string) => Promise; } diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts index ce66b37e..eb7cebb0 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -55,35 +55,32 @@ export const buildGameAchievementPath = ( export const darkenColor = (color: string, amount: number, alpha: number = 1) => new Color(color).darken(amount).alpha(alpha).toString(); -export const injectCustomCss = (css: string) => { +export const injectCustomCss = ( + css: string, + target: HTMLElement = document.head +) => { try { - const currentCustomCss = document.getElementById("custom-css"); - if (currentCustomCss) { - currentCustomCss.remove(); - } + target.querySelector("#custom-css")?.remove(); if (css.startsWith(THEME_WEB_STORE_URL)) { const link = document.createElement("link"); link.id = "custom-css"; link.rel = "stylesheet"; link.href = css; - document.head.appendChild(link); + target.appendChild(link); } else { const style = document.createElement("style"); style.id = "custom-css"; style.textContent = ` ${css} `; - document.head.appendChild(style); + target.appendChild(style); } } catch (error) { console.error("failed to inject custom css:", error); } }; -export const removeCustomCss = () => { - const currentCustomCss = document.getElementById("custom-css"); - if (currentCustomCss) { - currentCustomCss.remove(); - } +export const removeCustomCss = (target: HTMLElement = document.head) => { + target.querySelector("#custom-css")?.remove(); }; diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 0d99e37c..328e4575 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -5,8 +5,11 @@ import { AchievementCustomNotificationPosition, AchievementNotificationInfo, } from "@types"; -import { injectCustomCss } from "@renderer/helpers"; +import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; import { AchievementNotificationItem } from "@renderer/components/achievements/notification/achievement-notification"; +import app from "../../../app.scss?inline"; +import styles from "../../../components/achievements/notification/achievement-notification.scss?inline"; +import root from "react-shadow"; const NOTIFICATION_TIMEOUT = 4000; @@ -28,6 +31,8 @@ export function AchievementNotification() { const closingAnimation = useRef(-1); const visibleAnimation = useRef(-1); + const [shadowRootRef, setShadowRootRef] = useState(null); + const playAudio = useCallback(() => { const audio = new Audio(achievementSound); audio.volume = 0.1; @@ -132,31 +137,45 @@ export function AchievementNotification() { } }, [achievements]); - useEffect(() => { - const loadAndApplyTheme = async () => { - const activeTheme = await window.electron.getActiveCustomTheme(); - if (activeTheme?.code) { - injectCustomCss(activeTheme.code); - } - }; - loadAndApplyTheme(); - }, []); + const loadAndApplyTheme = useCallback(async () => { + if (!shadowRootRef) return; + const activeTheme = await window.electron.getActiveCustomTheme(); + if (activeTheme?.code) { + console.log("injecting custom css"); + injectCustomCss(activeTheme.code, shadowRootRef); + } else { + console.log("removing custom css"); + removeCustomCss(shadowRootRef); + } + }, [shadowRootRef]); useEffect(() => { - const unsubscribe = window.electron.onCssInjected((cssString) => { - injectCustomCss(cssString); + loadAndApplyTheme(); + }, [loadAndApplyTheme]); + + useEffect(() => { + const unsubscribe = window.electron.onCustomThemeUpdated(() => { + console.log("onCustomThemeUpdated"); + loadAndApplyTheme(); }); return () => unsubscribe(); - }, []); - - if (!isVisible || !currentAchievement) return null; + }, [loadAndApplyTheme]); return ( - + + +
setShadowRootRef(ref)}> + {isVisible && currentAchievement && ( + + )} +
+
); } diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx index b8ab0b84..413e52e9 100644 --- a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx +++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx @@ -40,7 +40,7 @@ export function SettingsAppearance({ }, [loadThemes]); useEffect(() => { - const unsubscribe = window.electron.onCssInjected(() => { + const unsubscribe = window.electron.onCustomThemeUpdated(() => { loadThemes(); }); diff --git a/yarn.lock b/yarn.lock index 91084f52..44ac8dfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6069,6 +6069,11 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +humps@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" + integrity sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g== + husky@^9.1.7: version "9.1.7" resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" @@ -7863,6 +7868,13 @@ react-router@6.26.2: dependencies: "@remix-run/router" "1.19.2" +react-shadow@^20.6.0: + version "20.6.0" + resolved "https://registry.yarnpkg.com/react-shadow/-/react-shadow-20.6.0.tgz#13c11ec50787ba6ab637381814562066be368d49" + integrity sha512-kY+w4OMNZ8Nj9YI9eiTgvvJ/wYO7XyX1D/LYhvwQZv5vw69iCiDtGB0BX/2U8gLUuZAMN+x/7rHJKqHh8wXFHQ== + dependencies: + humps "^2.0.1" + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"