From 77b6f1b2ad6f6d98153d2c10fe8693c770180b54 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 13 Dec 2025 16:10:57 +0200 Subject: [PATCH 1/9] feat: add peak speed tracking and management in download slice --- src/renderer/src/features/download-slice.ts | 17 +++++++ .../src/pages/downloads/download-group.tsx | 47 +++++++++---------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/renderer/src/features/download-slice.ts b/src/renderer/src/features/download-slice.ts index 0330cca3..ae0c9c9d 100644 --- a/src/renderer/src/features/download-slice.ts +++ b/src/renderer/src/features/download-slice.ts @@ -12,6 +12,7 @@ export interface DownloadState { gameId: string | null; gamesWithDeletionInProgress: string[]; extraction: ExtractionInfo | null; + peakSpeeds: Record; } const initialState: DownloadState = { @@ -19,6 +20,7 @@ const initialState: DownloadState = { gameId: null, gamesWithDeletionInProgress: [], extraction: null, + peakSpeeds: {}, }; export const downloadSlice = createSlice({ @@ -62,6 +64,19 @@ export const downloadSlice = createSlice({ clearExtraction: (state) => { state.extraction = null; }, + updatePeakSpeed: ( + state, + action: PayloadAction<{ gameId: string; speed: number }> + ) => { + const { gameId, speed } = action.payload; + const currentPeak = state.peakSpeeds[gameId] || 0; + if (speed > currentPeak) { + state.peakSpeeds[gameId] = speed; + } + }, + clearPeakSpeed: (state, action: PayloadAction) => { + state.peakSpeeds[action.payload] = 0; + }, }, }); @@ -72,4 +87,6 @@ export const { removeGameFromDeleting, setExtractionProgress, clearExtraction, + updatePeakSpeed, + clearPeakSpeed, } = downloadSlice.actions; diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 52fbcdfd..56ab398b 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -11,10 +11,12 @@ import { addMilliseconds } from "date-fns"; import { DOWNLOADER_NAME } from "@renderer/constants"; import { useAppSelector, + useAppDispatch, useDownload, useLibrary, useDate, } from "@renderer/hooks"; +import { updatePeakSpeed, clearPeakSpeed } from "@renderer/features"; import "./download-group.scss"; import { useTranslation } from "react-i18next"; @@ -512,7 +514,8 @@ export function DownloadGroup({ const { formatDistance } = useDate(); - const [peakSpeeds, setPeakSpeeds] = useState>({}); + const dispatch = useAppDispatch(); + const peakSpeeds = useAppSelector((state) => state.download.peakSpeeds); const speedHistoryRef = useRef>({}); const [dominantColors, setDominantColors] = useState>( {} @@ -577,28 +580,23 @@ export function DownloadGroup({ }, [library, lastPacket?.gameId]); useEffect(() => { - if (lastPacket?.gameId && lastPacket.downloadSpeed !== undefined) { - const gameId = lastPacket.gameId; + if (!lastPacket?.gameId || lastPacket.downloadSpeed === undefined) return; - const currentPeak = peakSpeeds[gameId] || 0; - if (lastPacket.downloadSpeed > currentPeak) { - setPeakSpeeds((prev) => ({ - ...prev, - [gameId]: lastPacket.downloadSpeed, - })); - } + const gameId = lastPacket.gameId; + const downloadSpeed = lastPacket.downloadSpeed; - if (!speedHistoryRef.current[gameId]) { - speedHistoryRef.current[gameId] = []; - } + dispatch(updatePeakSpeed({ gameId, speed: downloadSpeed })); - speedHistoryRef.current[gameId].push(lastPacket.downloadSpeed); - - if (speedHistoryRef.current[gameId].length > 120) { - speedHistoryRef.current[gameId].shift(); - } + if (!speedHistoryRef.current[gameId]) { + speedHistoryRef.current[gameId] = []; } - }, [lastPacket?.gameId, lastPacket?.downloadSpeed, peakSpeeds]); + + speedHistoryRef.current[gameId].push(downloadSpeed); + + if (speedHistoryRef.current[gameId].length > 120) { + speedHistoryRef.current[gameId].shift(); + } + }, [lastPacket, dispatch]); useEffect(() => { for (const game of library) { @@ -610,11 +608,11 @@ export function DownloadGroup({ // Fresh download - clear any old data if (speedHistoryRef.current[game.id]?.length > 0) { speedHistoryRef.current[game.id] = []; - setPeakSpeeds((prev) => ({ ...prev, [game.id]: 0 })); + dispatch(clearPeakSpeed(game.id)); } } } - }, [library]); + }, [library, dispatch]); useEffect(() => { const timeouts: NodeJS.Timeout[] = []; @@ -624,9 +622,10 @@ export function DownloadGroup({ game.download?.progress === 1 && speedHistoryRef.current[game.id]?.length > 0 ) { + const gameId = game.id; const timeout = setTimeout(() => { - speedHistoryRef.current[game.id] = []; - setPeakSpeeds((prev) => ({ ...prev, [game.id]: 0 })); + speedHistoryRef.current[gameId] = []; + dispatch(clearPeakSpeed(gameId)); }, 10_000); timeouts.push(timeout); } @@ -637,7 +636,7 @@ export function DownloadGroup({ clearTimeout(timeout); } }; - }, [library]); + }, [library, dispatch]); useEffect(() => { if (library.length > 0 && title === t("download_in_progress")) { From 67f863e0f32a245346bfbb00d1f8964adb9856fc Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 13 Dec 2025 17:41:15 +0200 Subject: [PATCH 2/9] fix: remove unused peak speed update dispatch in DownloadGroup component --- src/renderer/src/features/download-slice.ts | 9 +++++++++ src/renderer/src/pages/downloads/download-group.tsx | 6 ++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/features/download-slice.ts b/src/renderer/src/features/download-slice.ts index ae0c9c9d..80d78b0d 100644 --- a/src/renderer/src/features/download-slice.ts +++ b/src/renderer/src/features/download-slice.ts @@ -30,6 +30,15 @@ export const downloadSlice = createSlice({ setLastPacket: (state, action: PayloadAction) => { state.lastPacket = action.payload; if (!state.gameId && action.payload) state.gameId = action.payload.gameId; + + // Track peak speed atomically when packet arrives + if (action.payload?.gameId && action.payload.downloadSpeed != null) { + const { gameId, downloadSpeed } = action.payload; + const currentPeak = state.peakSpeeds[gameId] || 0; + if (downloadSpeed > currentPeak) { + state.peakSpeeds[gameId] = downloadSpeed; + } + } }, clearDownload: (state) => { state.lastPacket = null; diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 56ab398b..7c3f0f77 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -16,7 +16,7 @@ import { useLibrary, useDate, } from "@renderer/hooks"; -import { updatePeakSpeed, clearPeakSpeed } from "@renderer/features"; +import { clearPeakSpeed } from "@renderer/features"; import "./download-group.scss"; import { useTranslation } from "react-i18next"; @@ -585,8 +585,6 @@ export function DownloadGroup({ const gameId = lastPacket.gameId; const downloadSpeed = lastPacket.downloadSpeed; - dispatch(updatePeakSpeed({ gameId, speed: downloadSpeed })); - if (!speedHistoryRef.current[gameId]) { speedHistoryRef.current[gameId] = []; } @@ -596,7 +594,7 @@ export function DownloadGroup({ if (speedHistoryRef.current[gameId].length > 120) { speedHistoryRef.current[gameId].shift(); } - }, [lastPacket, dispatch]); + }, [lastPacket]); useEffect(() => { for (const game of library) { From 67ea9e78a2d5ba50bd588312be9af2d340971b41 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 13 Dec 2025 18:14:52 +0200 Subject: [PATCH 3/9] feat: enhance download tracking by adding speed history management in download slice --- src/renderer/src/features/download-slice.ts | 17 ++++- .../src/pages/downloads/download-group.tsx | 74 +++---------------- 2 files changed, 28 insertions(+), 63 deletions(-) diff --git a/src/renderer/src/features/download-slice.ts b/src/renderer/src/features/download-slice.ts index 80d78b0d..f70421c0 100644 --- a/src/renderer/src/features/download-slice.ts +++ b/src/renderer/src/features/download-slice.ts @@ -13,6 +13,7 @@ export interface DownloadState { gamesWithDeletionInProgress: string[]; extraction: ExtractionInfo | null; peakSpeeds: Record; + speedHistory: Record; } const initialState: DownloadState = { @@ -21,6 +22,7 @@ const initialState: DownloadState = { gamesWithDeletionInProgress: [], extraction: null, peakSpeeds: {}, + speedHistory: {}, }; export const downloadSlice = createSlice({ @@ -31,13 +33,25 @@ export const downloadSlice = createSlice({ state.lastPacket = action.payload; if (!state.gameId && action.payload) state.gameId = action.payload.gameId; - // Track peak speed atomically when packet arrives + // Track peak speed and speed history atomically when packet arrives if (action.payload?.gameId && action.payload.downloadSpeed != null) { const { gameId, downloadSpeed } = action.payload; + + // Update peak speed if this is higher const currentPeak = state.peakSpeeds[gameId] || 0; if (downloadSpeed > currentPeak) { state.peakSpeeds[gameId] = downloadSpeed; } + + // Update speed history for chart + if (!state.speedHistory[gameId]) { + state.speedHistory[gameId] = []; + } + state.speedHistory[gameId].push(downloadSpeed); + // Keep only last 120 entries + if (state.speedHistory[gameId].length > 120) { + state.speedHistory[gameId].shift(); + } } }, clearDownload: (state) => { @@ -85,6 +99,7 @@ export const downloadSlice = createSlice({ }, clearPeakSpeed: (state, action: PayloadAction) => { state.peakSpeeds[action.payload] = 0; + state.speedHistory[action.payload] = []; }, }, }); diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 7c3f0f77..bf6584f2 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -11,12 +11,10 @@ import { addMilliseconds } from "date-fns"; import { DOWNLOADER_NAME } from "@renderer/constants"; import { useAppSelector, - useAppDispatch, useDownload, useLibrary, useDate, } from "@renderer/hooks"; -import { clearPeakSpeed } from "@renderer/features"; import "./download-group.scss"; import { useTranslation } from "react-i18next"; @@ -514,9 +512,9 @@ export function DownloadGroup({ const { formatDistance } = useDate(); - const dispatch = useAppDispatch(); + // Get speed history and peak speeds from Redux (centralized state) + const speedHistory = useAppSelector((state) => state.download.speedHistory); const peakSpeeds = useAppSelector((state) => state.download.peakSpeeds); - const speedHistoryRef = useRef>({}); const [dominantColors, setDominantColors] = useState>( {} ); @@ -579,62 +577,8 @@ export function DownloadGroup({ }); }, [library, lastPacket?.gameId]); - useEffect(() => { - if (!lastPacket?.gameId || lastPacket.downloadSpeed === undefined) return; - - const gameId = lastPacket.gameId; - const downloadSpeed = lastPacket.downloadSpeed; - - if (!speedHistoryRef.current[gameId]) { - speedHistoryRef.current[gameId] = []; - } - - speedHistoryRef.current[gameId].push(downloadSpeed); - - if (speedHistoryRef.current[gameId].length > 120) { - speedHistoryRef.current[gameId].shift(); - } - }, [lastPacket]); - - useEffect(() => { - for (const game of library) { - if ( - game.download && - game.download.progress < 0.01 && - game.download.status !== "paused" - ) { - // Fresh download - clear any old data - if (speedHistoryRef.current[game.id]?.length > 0) { - speedHistoryRef.current[game.id] = []; - dispatch(clearPeakSpeed(game.id)); - } - } - } - }, [library, dispatch]); - - useEffect(() => { - const timeouts: NodeJS.Timeout[] = []; - - for (const game of library) { - if ( - game.download?.progress === 1 && - speedHistoryRef.current[game.id]?.length > 0 - ) { - const gameId = game.id; - const timeout = setTimeout(() => { - speedHistoryRef.current[gameId] = []; - dispatch(clearPeakSpeed(gameId)); - }, 10_000); - timeouts.push(timeout); - } - } - - return () => { - for (const timeout of timeouts) { - clearTimeout(timeout); - } - }; - }, [library, dispatch]); + // Speed history and peak speeds are now tracked in Redux (in setLastPacket reducer) + // No local effect needed - data is updated atomically when packets arrive useEffect(() => { if (library.length > 0 && title === t("download_in_progress")) { @@ -839,7 +783,13 @@ export function DownloadGroup({ ? (lastPacket?.downloadSpeed ?? 0) : 0; const finalDownloadSize = getFinalDownloadSize(game); - const peakSpeed = peakSpeeds[game.id] || 0; + // Use lastPacket.gameId for lookup since that's the key used to store the data + // Fall back to game.id if lastPacket is not available + const dataKey = lastPacket?.gameId ?? game.id; + const gameSpeedHistory = speedHistory[dataKey] ?? []; + const storedPeak = peakSpeeds[dataKey]; + // Use stored peak if available and > 0, otherwise use current speed as initial value + const peakSpeed = storedPeak !== undefined && storedPeak > 0 ? storedPeak : downloadSpeed; let currentProgress = game.download?.progress || 0; if (isGameExtracting) { @@ -861,7 +811,7 @@ export function DownloadGroup({ currentProgress={currentProgress} dominantColor={dominantColor} lastPacket={lastPacket} - speedHistory={speedHistoryRef.current[game.id] || []} + speedHistory={gameSpeedHistory} formatSpeed={formatSpeed} calculateETA={calculateETA} pauseDownload={pauseDownload} From 78d2be85f2fc51b20267d8c39b1a1c1dc561e8ba Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 13 Dec 2025 18:15:21 +0200 Subject: [PATCH 4/9] style: format peak speed calculation for improved readability in DownloadGroup component --- src/renderer/src/pages/downloads/download-group.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index bf6584f2..31d74d2e 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -789,7 +789,8 @@ export function DownloadGroup({ const gameSpeedHistory = speedHistory[dataKey] ?? []; const storedPeak = peakSpeeds[dataKey]; // Use stored peak if available and > 0, otherwise use current speed as initial value - const peakSpeed = storedPeak !== undefined && storedPeak > 0 ? storedPeak : downloadSpeed; + const peakSpeed = + storedPeak !== undefined && storedPeak > 0 ? storedPeak : downloadSpeed; let currentProgress = game.download?.progress || 0; if (isGameExtracting) { From 95a7bc2236feeb83fb320189a0f411e1a7002f89 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 13 Dec 2025 18:32:16 +0200 Subject: [PATCH 5/9] feat: add new translation keys for network and peak in English and Portuguese locales --- src/locales/en/translation.json | 4 +++- src/locales/pt-BR/translation.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 9be4ff26..85a44236 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -420,7 +420,9 @@ "delete_archive_title": "Would you like to delete {{fileName}}?", "delete_archive_description": "The file has been successfully extracted and it's no longer needed.", "yes": "Yes", - "no": "No" + "no": "No", + "network": "NETWORK", + "peak": "PEAK" }, "settings": { "downloads_path": "Downloads path", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index ee0da176..719f72f7 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -408,7 +408,9 @@ "delete_archive_title": "Deseja deletar {{fileName}}?", "delete_archive_description": "O arquivo foi extraído com sucesso e não é mais necessário.", "yes": "Sim", - "no": "Não" + "no": "Não", + "network": "REDE", + "peak": "PICO" }, "settings": { "downloads_path": "Diretório dos downloads", From 142bd3156c3a045a547e9fa9045e1d36c5400d6d Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 14 Dec 2025 11:27:29 +0200 Subject: [PATCH 6/9] fix: ensure downloader value is properly checked and converted to number in DownloadGroup component --- src/renderer/src/pages/downloads/download-group.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 31d74d2e..6a22148a 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -412,10 +412,12 @@ function HeroDownloadView({ )} - {game.download?.downloader && ( + {game.download?.downloader !== undefined && (
- {DOWNLOADER_NAME[game.download.downloader]} + + {DOWNLOADER_NAME[Number(game.download.downloader)]} +
)} @@ -856,7 +858,9 @@ export function DownloadGroup({
- {DOWNLOADER_NAME[game.download!.downloader]} + + {DOWNLOADER_NAME[Number(game.download!.downloader)]} +
{extraction?.visibleId === game.id ? ( From affa7a2b2e3dd4708b0f9b0905af7178a2bf3205 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 16 Dec 2025 15:42:34 +0200 Subject: [PATCH 7/9] feat: add fullscreen media modal to profile hero for avatar display --- proto | 2 +- .../fullscreen-media-modal.scss | 61 +++++++++++++ .../fullscreen-media-modal.tsx | 88 +++++++++++++++++++ src/renderer/src/components/index.ts | 1 + .../profile/profile-hero/profile-hero.tsx | 21 ++++- 5 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.scss create mode 100644 src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx diff --git a/proto b/proto index 7a23620f..6f11c99c 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 7a23620f930f6fbb84c0abcaab5149a34ab4b4eb +Subproject commit 6f11c99c572420a282ba5149b6866e39b8a4569c diff --git a/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.scss b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.scss new file mode 100644 index 00000000..454653b8 --- /dev/null +++ b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.scss @@ -0,0 +1,61 @@ +@use "../../scss/globals.scss"; + +.fullscreen-media-modal { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + position: relative; + background-color: rgba(0, 0, 0, 0.5); + + &__close-button { + position: absolute; + top: calc(globals.$spacing-unit * 4); + right: calc(globals.$spacing-unit * 3); + cursor: pointer; + color: globals.$body-color; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 50%; + border: 1px solid globals.$border-color; + padding: globals.$spacing-unit; + display: flex; + align-items: center; + justify-content: center; + transition: all ease 0.2s; + z-index: 10; + + &:hover { + background-color: rgba(0, 0, 0, 0.8); + transform: scale(1.1); + } + } + + &__image-container { + max-width: 90%; + max-height: 90%; + display: flex; + justify-content: center; + align-items: center; + } + + &__image { + max-width: 100%; + max-height: 60vh; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + animation: image-appear 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; + } +} + +@keyframes image-appear { + 0% { + opacity: 0; + transform: scale(0.85); + } + 100% { + opacity: 1; + transform: scale(1); + } +} diff --git a/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx new file mode 100644 index 00000000..12052834 --- /dev/null +++ b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx @@ -0,0 +1,88 @@ +import { useCallback, useEffect, useRef } from "react"; +import { createPortal } from "react-dom"; +import { XIcon } from "@primer/octicons-react"; +import { useTranslation } from "react-i18next"; + +import { Backdrop } from "../backdrop/backdrop"; +import "./fullscreen-media-modal.scss"; + +export interface FullscreenMediaModalProps { + visible: boolean; + onClose: () => void; + src: string | null | undefined; + alt?: string; +} + +export function FullscreenMediaModal({ + visible, + onClose, + src, + alt, +}: FullscreenMediaModalProps) { + const containerRef = useRef(null); + + const { t } = useTranslation("modal"); + + useEffect(() => { + if (visible) { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + onClose(); + } + }; + + window.addEventListener("keydown", onKeyDown); + + return () => { + window.removeEventListener("keydown", onKeyDown); + }; + } + + return () => {}; + }, [onClose, visible]); + + useEffect(() => { + const onMouseDown = (e: MouseEvent) => { + if (containerRef.current) { + const clickedOnImage = containerRef.current.contains(e.target as Node); + + if (!clickedOnImage) { + onClose(); + } + } + }; + + if (visible) { + window.addEventListener("mousedown", onMouseDown); + } + + return () => { + window.removeEventListener("mousedown", onMouseDown); + }; + }, [onClose, visible]); + + if (!visible || !src) return null; + + return createPortal( + +
+ + +
+ {alt} +
+
+
, + document.body + ); +} diff --git a/src/renderer/src/components/index.ts b/src/renderer/src/components/index.ts index e8876fcb..8bb028bd 100644 --- a/src/renderer/src/components/index.ts +++ b/src/renderer/src/components/index.ts @@ -20,3 +20,4 @@ export * from "./game-context-menu/game-context-menu"; export * from "./game-context-menu/use-game-actions"; export * from "./star-rating/star-rating"; export * from "./search-dropdown/search-dropdown"; +export * from "./fullscreen-media-modal/fullscreen-media-modal"; diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx index fc354d01..f9109067 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx @@ -9,7 +9,12 @@ import { XCircleFillIcon, } from "@primer/octicons-react"; import { buildGameDetailsPath } from "@renderer/helpers"; -import { Avatar, Button, Link } from "@renderer/components"; +import { + Avatar, + Button, + FullscreenMediaModal, + Link, +} from "@renderer/components"; import { useTranslation } from "react-i18next"; import { useAppSelector, @@ -33,6 +38,7 @@ type FriendAction = export function ProfileHero() { const [showEditProfileModal, setShowEditProfileModal] = useState(false); + const [showFullscreenAvatar, setShowFullscreenAvatar] = useState(false); const [isPerformingAction, setIsPerformingAction] = useState(false); const { @@ -246,10 +252,12 @@ export function ProfileHero() { ]); const handleAvatarClick = useCallback(() => { - if (isMe) { + if (userProfile?.profileImageUrl) { + setShowFullscreenAvatar(true); + } else if (isMe) { setShowEditProfileModal(true); } - }, [isMe]); + }, [isMe, userProfile?.profileImageUrl]); const currentGame = useMemo(() => { if (isMe) { @@ -272,6 +280,13 @@ export function ProfileHero() { onClose={() => setShowEditProfileModal(false)} /> + setShowFullscreenAvatar(false)} + src={userProfile?.profileImageUrl} + alt={userProfile?.displayName} + /> +
Date: Tue, 16 Dec 2025 15:43:43 +0200 Subject: [PATCH 8/9] refactor(fullscreen-media-modal): remove unused useCallback import --- .../fullscreen-media-modal/fullscreen-media-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx index 12052834..2d2abb6d 100644 --- a/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx +++ b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; import { createPortal } from "react-dom"; import { XIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; From 1524e73ee6d901858b27ab938c7c75c6f64aaa01 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 16 Dec 2025 15:49:10 +0200 Subject: [PATCH 9/9] refactor(fullscreen-media-modal): replace div with dialog element for improved semantics --- .../fullscreen-media-modal/fullscreen-media-modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx index 2d2abb6d..700bddb1 100644 --- a/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx +++ b/src/renderer/src/components/fullscreen-media-modal/fullscreen-media-modal.tsx @@ -65,7 +65,7 @@ export function FullscreenMediaModal({ return createPortal( -
+
-
+ , document.body );