From 49bb42a29874d2b6da70e76fd56e14bb5538dee3 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Mon, 7 Jul 2025 20:11:32 -0700 Subject: [PATCH 1/2] Use lazy loading (react-intersection-observer) for image loading --- package.json | 1 + pnpm-lock.yaml | 18 ++++++++ src/shared/components/image/image.tsx | 61 +++++++++++++++------------ 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index b447bc2f..900e8f53 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "react-i18next": "^11.18.6", "react-icons": "^5.5.0", "react-image": "^4.1.0", + "react-intersection-observer": "^9.16.0", "react-loading-skeleton": "^3.5.0", "react-player": "^2.11.0", "react-router": "^6.16.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62369222..b01e31ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,6 +182,9 @@ importers: react-image: specifier: ^4.1.0 version: 4.1.0(@babel/runtime@7.27.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-intersection-observer: + specifier: ^9.16.0 + version: 9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-loading-skeleton: specifier: ^3.5.0 version: 3.5.0(react@19.1.0) @@ -3655,6 +3658,15 @@ packages: react: '>=16.8' react-dom: '>=16.8' + react-intersection-observer@9.16.0: + resolution: {integrity: sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react-dom: + optional: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -8458,6 +8470,12 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + react-intersection-observer@9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + react-is@16.13.1: {} react-loading-skeleton@3.5.0(react@19.1.0): diff --git a/src/shared/components/image/image.tsx b/src/shared/components/image/image.tsx index 8b704dc7..5a6725bd 100644 --- a/src/shared/components/image/image.tsx +++ b/src/shared/components/image/image.tsx @@ -2,6 +2,7 @@ import clsx from 'clsx'; import { motion, MotionConfigProps } from 'motion/react'; import { type ImgHTMLAttributes } from 'react'; import { Img } from 'react-image'; +import { InView } from 'react-intersection-observer'; import styles from './image.module.css'; @@ -44,33 +45,41 @@ export function Image({ }: ImageProps) { if (src) { return ( - ( - - {children} - + + {({ inView, ref }) => ( +
+ {inView && ( + ( + + {children} + + )} + loader={ + includeLoader ? ( + + + + ) : null + } + src={src} + unloader={ + includeUnloader ? ( + + + + ) : null + } + /> + )} +
)} - loader={ - includeLoader ? ( - - - - ) : null - } - src={src} - unloader={ - includeUnloader ? ( - - - - ) : null - } - /> +
); } From f5af1c314c2545a1973867a892ed8199f691ee88 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:56:51 -0700 Subject: [PATCH 2/2] add image loader/unloader and only toggle source --- .../player/hooks/use-handle-playqueue-add.ts | 6 +- .../add-to-playlist-context-modal.tsx | 4 +- src/shared/components/image/image.tsx | 57 ++++++++++--------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts index b9597b8d..8a8ccbdc 100644 --- a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts +++ b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts @@ -78,7 +78,7 @@ export const useHandlePlayQueueAdd = () => { // Allow this to be undefined for "play shuffled". If undefined, default to 0, // otherwise, choose the selected item in the queue let initialSongIndex: number | undefined; - let toastId: string | null = null; + let toastId: null | string = null; if (byItemType) { let songList: SongListResponse | undefined; @@ -148,9 +148,9 @@ export const useHandlePlayQueueAdd = () => { clearTimeout(timeoutIds.current[fetchId] as ReturnType); delete timeoutIds.current[fetchId]; - if(toastId){ + if (toastId) { toast.hide(toastId); - } + } } catch (err: any) { if (instanceOfCancellationError(err)) { return null; diff --git a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx index 0f24ca23..4732a758 100644 --- a/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx +++ b/src/renderer/features/playlists/components/add-to-playlist-context-modal.tsx @@ -230,19 +230,19 @@ export const AddToPlaylistContextModal = ({ clearable data={playlistSelect} disabled={playlistList.isLoading} + dropdownOpened={isDropdownOpened} label={t('form.addToPlaylist.input', { context: 'playlists', postProcess: 'titleCase', })} searchable size="md" - dropdownOpened={isDropdownOpened} {...form.getInputProps('playlistId')} - onClick={() => setIsDropdownOpened(true)} onChange={(e) => { setIsDropdownOpened(false); form.getInputProps('playlistId').onChange(e); }} + onClick={() => setIsDropdownOpened(true)} />