feat: add onChange handlers to ChipSelect and Text components; clear imageQueue on background type and category changes

This commit is contained in:
alexsparkes
2025-10-31 16:42:15 +00:00
parent ef22e91b07
commit b303d02492
6 changed files with 71 additions and 14 deletions

View File

@@ -8,7 +8,7 @@ import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
function ChipSelect({ label, options }) { function ChipSelect({ label, options, onChange }) {
let start = (localStorage.getItem('apiCategories') || '').split(','); let start = (localStorage.getItem('apiCategories') || '').split(',');
if (start[0] === '') { if (start[0] === '') {
start = []; start = [];
@@ -22,6 +22,11 @@ function ChipSelect({ label, options }) {
} = event; } = event;
setoptionsSelected(typeof value === 'string' ? value.split(',') : value); setoptionsSelected(typeof value === 'string' ? value.split(',') : value);
localStorage.setItem('apiCategories', value); localStorage.setItem('apiCategories', value);
// Call parent onChange if provided
if (onChange) {
onChange(value);
}
}; };
return ( return (

View File

@@ -20,6 +20,11 @@ const Text = memo((props) => {
localStorage.setItem(props.name, value); localStorage.setItem(props.name, value);
setValue(value); setValue(value);
// Call parent onChange if provided
if (props.onChange) {
props.onChange(value);
}
if (props.element) { if (props.element) {
if (!document.querySelector(props.element)) { if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex'; document.querySelector('.reminder-info').style.display = 'flex';
@@ -28,7 +33,7 @@ const Text = memo((props) => {
} }
EventBus.emit('refresh', props.category); EventBus.emit('refresh', props.category);
}, [props.name, props.upperCaseFirst, props.element, props.category]); }, [props.name, props.upperCaseFirst, props.element, props.category, props.onChange]);
const resetItem = useCallback(() => { const resetItem = useCallback(() => {
handleChange({ handleChange({

View File

@@ -1,11 +1,11 @@
import { memo } from 'react'; import { memo, useMemo } from 'react';
/** /**
* BackgroundVideo component for rendering video backgrounds * BackgroundVideo component for rendering video backgrounds
*/ */
function BackgroundVideo({ url, filterStyle }) { function BackgroundVideo({ url, filterStyle }) {
const isMuted = localStorage.getItem('backgroundVideoMute') === 'true'; const isMuted = useMemo(() => localStorage.getItem('backgroundVideoMute') === 'true', []);
const shouldLoop = localStorage.getItem('backgroundVideoLoop') === 'true'; const shouldLoop = useMemo(() => localStorage.getItem('backgroundVideoLoop') === 'true', []);
return ( return (
<video <video

View File

@@ -1,13 +1,17 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef, useMemo } from 'react';
import { createBlobUrl } from '../api/blobUrl'; import { createBlobUrl } from '../api/blobUrl';
import { generateBlurHashDataUrl } from '../api/blurHash'; import { generateBlurHashDataUrl } from '../api/blurHash';
import { getBackgroundFilterStyle, getBackgroundOverlayStyle } from '../api/backgroundFilters'; import { getBackgroundFilterStyle, getBackgroundOverlayStyle } from '../api/backgroundFilters';
// Constants
const TRANSITION_DURATION = 1200; // milliseconds
/** /**
* Hook for rendering backgrounds to the DOM * Hook for rendering backgrounds to the DOM
*/ */
export function useBackgroundRenderer(backgroundData) { export function useBackgroundRenderer(backgroundData) {
const blobRef = useRef(null); const blobRef = useRef(null);
const abortControllerRef = useRef(null);
useEffect(() => { useEffect(() => {
if (backgroundData.video) return; if (backgroundData.video) return;
@@ -15,11 +19,18 @@ export function useBackgroundRenderer(backgroundData) {
const element = document.getElementById('backgroundImage'); const element = document.getElementById('backgroundImage');
if (!element) return; if (!element) return;
// Abort any pending image loads
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
const applyBackground = async () => { const applyBackground = async () => {
if (backgroundData.url) { if (backgroundData.url) {
const hasTransition = localStorage.getItem('bgtransition') !== 'false'; const hasTransition = localStorage.getItem('bgtransition') !== 'false';
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!hasTransition) { if (!hasTransition || prefersReducedMotion) {
element.style.background = `url(${backgroundData.url})`; element.style.background = `url(${backgroundData.url})`;
document.querySelector('.photoInformation')?.style.setProperty('display', 'flex'); document.querySelector('.photoInformation')?.style.setProperty('display', 'flex');
return; return;
@@ -82,7 +93,7 @@ export function useBackgroundRenderer(backgroundData) {
void overlay.offsetHeight; void overlay.offsetHeight;
// Re-enable transition // Re-enable transition
overlay.style.transition = 'opacity 1.2s ease-in-out'; overlay.style.transition = `opacity ${TRANSITION_DURATION / 1000}s ease-in-out`;
// Force another reflow before changing opacity // Force another reflow before changing opacity
void overlay.offsetHeight; void overlay.offsetHeight;
@@ -95,7 +106,7 @@ export function useBackgroundRenderer(backgroundData) {
element.style.backgroundImage = `url(${finalUrl})`; element.style.backgroundImage = `url(${finalUrl})`;
overlay.style.opacity = '0'; overlay.style.opacity = '0';
overlay.style.backgroundImage = ''; overlay.style.backgroundImage = '';
}, 1300); }, TRANSITION_DURATION + 100);
} else if (backgroundData.style) { } else if (backgroundData.style) {
element.setAttribute('style', backgroundData.style); element.setAttribute('style', backgroundData.style);
} }
@@ -104,7 +115,21 @@ export function useBackgroundRenderer(backgroundData) {
applyBackground(); applyBackground();
return () => { return () => {
if (blobRef.current) URL.revokeObjectURL(blobRef.current); // Cleanup blob URL
if (blobRef.current) {
URL.revokeObjectURL(blobRef.current);
}
// Abort pending image loads
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// Remove overlay element to prevent memory leak
const overlay = document.getElementById('backgroundOverlay');
if (overlay) {
overlay.remove();
}
}; };
}, [backgroundData.url, backgroundData.style, backgroundData.video, backgroundData.photoInfo]); }, [backgroundData.url, backgroundData.style, backgroundData.video, backgroundData.photoInfo]);
} }
@@ -113,12 +138,12 @@ export function useBackgroundRenderer(backgroundData) {
* Hook to get computed filter styles (legacy - for video backgrounds) * Hook to get computed filter styles (legacy - for video backgrounds)
*/ */
export function useBackgroundFilters() { export function useBackgroundFilters() {
return { WebkitFilter: getBackgroundFilterStyle() }; return useMemo(() => ({ WebkitFilter: getBackgroundFilterStyle() }), []);
} }
/** /**
* Hook to get computed overlay filter styles * Hook to get computed overlay filter styles
*/ */
export function useBackgroundOverlayFilters() { export function useBackgroundOverlayFilters() {
return getBackgroundOverlayStyle(); return useMemo(() => getBackgroundOverlayStyle(), []);
} }

View File

@@ -57,6 +57,8 @@ const BackgroundOptions = memo(() => {
const updateAPI = useCallback((e) => { const updateAPI = useCallback((e) => {
localStorage.setItem('nextImage', null); localStorage.setItem('nextImage', null);
// Clear prefetch queue when API changes to prevent showing cached images from old API
localStorage.removeItem('imageQueue');
if (e === 'mue') { if (e === 'mue') {
setBackgroundCategories(backgroundCategoriesOG); setBackgroundCategories(backgroundCategoriesOG);
setBackgroundAPI('mue'); setBackgroundAPI('mue');
@@ -177,7 +179,11 @@ const BackgroundOptions = memo(() => {
<Dropdown <Dropdown
label={variables.getMessage('modals.main.settings.sections.background.type.title')} label={variables.getMessage('modals.main.settings.sections.background.type.title')}
name="backgroundType" name="backgroundType"
onChange={(value) => setBackgroundType(value)} onChange={(value) => {
// Clear prefetch queue when changing background type
localStorage.removeItem('imageQueue');
setBackgroundType(value);
}}
category="background" category="background"
items={getBackgroundOptionItems(marketplaceEnabled)} items={getBackgroundOptionItems(marketplaceEnabled)}
/> />
@@ -208,7 +214,11 @@ const BackgroundOptions = memo(() => {
<SourceSection <SourceSection
backgroundType={backgroundType} backgroundType={backgroundType}
marketplaceEnabled={marketplaceEnabled} marketplaceEnabled={marketplaceEnabled}
onTypeChange={(value) => setBackgroundType(value)} onTypeChange={(value) => {
// Clear prefetch queue when changing background type
localStorage.removeItem('imageQueue');
setBackgroundType(value);
}}
/> />
{getBackgroundSettings()} {getBackgroundSettings()}
</> </>

View File

@@ -34,6 +34,10 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
label={variables.getMessage('modals.main.settings.sections.background.categories')} label={variables.getMessage('modals.main.settings.sections.background.categories')}
options={backgroundCategories} options={backgroundCategories}
name="apiCategories" name="apiCategories"
onChange={() => {
// Clear prefetch queue when categories change
localStorage.removeItem('imageQueue');
}}
/> />
)} )}
<Dropdown <Dropdown
@@ -43,6 +47,10 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
name="apiQuality" name="apiQuality"
element=".other" element=".other"
items={APIQualityOptions} items={APIQualityOptions}
onChange={() => {
// Clear prefetch queue when quality changes
localStorage.removeItem('imageQueue');
}}
/> />
<Radio <Radio
title="API" title="API"
@@ -81,6 +89,10 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
name="unsplashCollections" name="unsplashCollections"
category="background" category="background"
element="#backgroundImage" element="#backgroundImage"
onChange={() => {
// Clear prefetch queue when Unsplash collections change
localStorage.removeItem('imageQueue');
}}
/> />
</Action> </Action>
</Row> </Row>