feat: enhance BackgroundImageSelector with custom image management

- Removed preserved custom URL handling and replaced it with a custom images array for better management of user-added backgrounds.
- Updated the component to allow adding, selecting, and removing custom images, improving user experience and flexibility.
- Adjusted background cycling logic to include custom images, ensuring a seamless integration with existing backgrounds.
This commit is contained in:
Julien Froidefond
2025-10-02 14:40:50 +02:00
parent fbb9311f9e
commit 99377ee38d
3 changed files with 93 additions and 64 deletions

View File

@@ -87,19 +87,11 @@ export function BackgroundImageSelector() {
const [customUrl, setCustomUrl] = useState(''); const [customUrl, setCustomUrl] = useState('');
const [showCustomInput, setShowCustomInput] = useState(false); const [showCustomInput, setShowCustomInput] = useState(false);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const [preservedCustomUrl, setPreservedCustomUrl] = useState<string>('');
const currentBackground = preferences?.viewPreferences?.backgroundImage; const currentBackground = preferences?.viewPreferences?.backgroundImage;
const backgroundBlur = preferences?.viewPreferences?.backgroundBlur || 0; const backgroundBlur = preferences?.viewPreferences?.backgroundBlur || 0;
const backgroundOpacity = preferences?.viewPreferences?.backgroundOpacity || 100; const backgroundOpacity = preferences?.viewPreferences?.backgroundOpacity || 100;
const customImages = preferences?.viewPreferences?.customImages || [];
// Préserver l'URL personnalisée si elle existe
useEffect(() => {
if (currentBackground && !PRESET_BACKGROUNDS.some(preset => preset.id === currentBackground)) {
setPreservedCustomUrl(currentBackground);
localStorage.setItem('preservedCustomBackground', currentBackground);
}
}, [currentBackground]);
const handlePresetSelect = (presetId: string) => { const handlePresetSelect = (presetId: string) => {
const backgroundImage = presetId === 'none' ? undefined : presetId; const backgroundImage = presetId === 'none' ? undefined : presetId;
@@ -110,23 +102,33 @@ export function BackgroundImageSelector() {
if (!customUrl.trim()) return; if (!customUrl.trim()) return;
const url = customUrl.trim(); const url = customUrl.trim();
// Ajouter l'image aux images personnalisées si elle n'existe pas déjà
if (!customImages.includes(url)) {
const newCustomImages = [...customImages, url];
updateViewPreferences({
backgroundImage: url,
customImages: newCustomImages
});
} else {
// Si elle existe déjà, juste la sélectionner
updateViewPreferences({ backgroundImage: url }); updateViewPreferences({ backgroundImage: url });
setPreservedCustomUrl(url); }
localStorage.setItem('preservedCustomBackground', url);
setCustomUrl(''); setCustomUrl('');
setShowCustomInput(false); setShowCustomInput(false);
}; };
const handleRemoveCustom = () => { const handleRemoveCustomImage = (urlToRemove: string) => {
updateViewPreferences({ backgroundImage: undefined }); const newCustomImages = customImages.filter(url => url !== urlToRemove);
setPreservedCustomUrl(''); updateViewPreferences({
localStorage.removeItem('preservedCustomBackground'); customImages: newCustomImages,
backgroundImage: currentBackground === urlToRemove ? undefined : currentBackground
});
}; };
const handleRestoreCustom = () => { const handleSelectCustomImage = (url: string) => {
if (preservedCustomUrl) { updateViewPreferences({ backgroundImage: url });
updateViewPreferences({ backgroundImage: preservedCustomUrl });
}
}; };
const handleBlurChange = (blur: number) => { const handleBlurChange = (blur: number) => {
@@ -219,6 +221,60 @@ export function BackgroundImageSelector() {
</CardContent> </CardContent>
</Card> </Card>
{/* Images personnalisées */}
{customImages.length > 0 && (
<Card variant="glass">
<CardContent className="p-4">
<div className="text-sm text-[var(--muted-foreground)] mb-4">Images personnalisées :</div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{customImages.map((url, index) => (
<div key={url} className="relative">
<Button
onClick={() => handleSelectCustomImage(url)}
variant={currentBackground === url ? 'selected' : 'secondary'}
className="p-3 h-auto text-left justify-start w-full"
>
<div className="flex items-start gap-3 w-full">
{/* Aperçu */}
<div
className="w-12 h-8 rounded border border-[var(--border)]/30 flex-shrink-0 bg-cover bg-center"
style={{ backgroundImage: `url("${url}")` }}
/>
<div className="flex-1 min-w-0 pr-8">
<div className="font-medium text-[var(--foreground)] mb-1">
Image {index + 1}
</div>
<div className="text-xs text-[var(--muted-foreground)] leading-relaxed truncate">
{url.length > 25 ? `${url.substring(0, 25)}...` : url}
</div>
{currentBackground === url && (
<div className="mt-1 text-xs text-[var(--primary)] font-medium">
Sélectionné
</div>
)}
</div>
</div>
</Button>
{/* Bouton supprimer - positionné absolument */}
<Button
onClick={(e) => {
e.stopPropagation();
handleRemoveCustomImage(url);
}}
variant="destructive"
size="sm"
className="absolute top-2 right-2 px-2 py-1 text-xs h-6 w-6 min-w-0"
>
×
</Button>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* URL personnalisée */} {/* URL personnalisée */}
<Card> <Card>
@@ -231,17 +287,6 @@ export function BackgroundImageSelector() {
Ajoutez votre propre image de fond Ajoutez votre propre image de fond
</p> </p>
</div> </div>
<div className="flex gap-2">
{preservedCustomUrl && (
<Button
onClick={handleRestoreCustom}
variant="secondary"
size="sm"
className="text-xs"
>
Restaurer
</Button>
)}
<Button <Button
onClick={() => setShowCustomInput(!showCustomInput)} onClick={() => setShowCustomInput(!showCustomInput)}
variant="secondary" variant="secondary"
@@ -250,7 +295,6 @@ export function BackgroundImageSelector() {
{showCustomInput ? 'Masquer' : 'Ajouter'} {showCustomInput ? 'Masquer' : 'Ajouter'}
</Button> </Button>
</div> </div>
</div>
{showCustomInput && ( {showCustomInput && (
<div className="space-y-3"> <div className="space-y-3">
@@ -282,17 +326,6 @@ export function BackgroundImageSelector() {
</div> </div>
</div> </div>
)} )}
{/* Bouton pour supprimer l'image personnalisée */}
{currentBackground && !PRESET_BACKGROUNDS.find(p => p.id === currentBackground) && (
<Button
onClick={handleRemoveCustom}
variant="destructive"
className="w-full"
>
Supprimer l&apos;image personnalisée
</Button>
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -23,23 +23,17 @@ export function useBackgroundCycle() {
const cycleBackground = () => { const cycleBackground = () => {
const currentBackground = preferences?.viewPreferences?.backgroundImage; const currentBackground = preferences?.viewPreferences?.backgroundImage;
const customImages = preferences?.viewPreferences?.customImages || [];
// Construire la liste complète des backgrounds (prédéfinis + personnalisé) // Construire la liste complète des backgrounds (prédéfinis + personnalisés)
const allBackgrounds = [...BACKGROUND_CYCLE]; const allBackgrounds = [...BACKGROUND_CYCLE];
// Ajouter l'image personnalisée préservée si elle existe // Ajouter toutes les images personnalisées
const preservedCustomUrl = localStorage.getItem('preservedCustomBackground'); customImages.forEach(url => {
if (!allBackgrounds.includes(url)) {
if (preservedCustomUrl && !BACKGROUND_CYCLE.includes(preservedCustomUrl)) { allBackgrounds.push(url);
allBackgrounds.push(preservedCustomUrl);
}
// Ajouter aussi l'image actuelle si elle est personnalisée ET différente de celle préservée
if (currentBackground &&
!BACKGROUND_CYCLE.includes(currentBackground) &&
currentBackground !== preservedCustomUrl) {
allBackgrounds.push(currentBackground);
} }
});
const currentIndex = allBackgrounds.findIndex(bg => bg === currentBackground); const currentIndex = allBackgrounds.findIndex(bg => bg === currentBackground);

View File

@@ -107,6 +107,7 @@ export interface ViewPreferences {
backgroundImage?: string; backgroundImage?: string;
backgroundBlur?: number; backgroundBlur?: number;
backgroundOpacity?: number; backgroundOpacity?: number;
customImages?: string[];
[key: string]: [key: string]:
| boolean | boolean
| 'tags' | 'tags'
@@ -117,6 +118,7 @@ export interface ViewPreferences {
| 'large' | 'large'
| string | string
| number | number
| string[]
| undefined; | undefined;
} }