feat: implement personalized background image feature
- Added functionality for users to select and customize background images in settings, including predefined options and URL uploads. - Updated `ViewPreferences` to store background image settings and modified `userPreferencesService` to handle updates. - Enhanced global styles for improved readability with background images, including blur and transparency effects. - Integrated `BackgroundImageSelector` component into settings for intuitive user experience. - Refactored `Card` components across the app to use a new 'glass' variant for better aesthetics.
This commit is contained in:
134
src/contexts/BackgroundContext.tsx
Normal file
134
src/contexts/BackgroundContext.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import { useUserPreferences } from './UserPreferencesContext';
|
||||
|
||||
interface BackgroundContextType {
|
||||
backgroundImage: string | undefined;
|
||||
setBackgroundImage: (image: string | undefined) => void;
|
||||
}
|
||||
|
||||
const BackgroundContext = createContext<BackgroundContextType | undefined>(undefined);
|
||||
|
||||
interface BackgroundProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
||||
const { preferences } = useUserPreferences();
|
||||
const [backgroundImage, setBackgroundImageState] = useState<string | undefined>(
|
||||
preferences?.viewPreferences?.backgroundImage
|
||||
);
|
||||
const [backgroundBlur, setBackgroundBlurState] = useState<number>(
|
||||
preferences?.viewPreferences?.backgroundBlur || 0
|
||||
);
|
||||
const [backgroundOpacity, setBackgroundOpacityState] = useState<number>(
|
||||
preferences?.viewPreferences?.backgroundOpacity || 100
|
||||
);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// Hydration safe initialization
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Sync with preferences
|
||||
useEffect(() => {
|
||||
if (preferences?.viewPreferences?.backgroundImage !== backgroundImage) {
|
||||
setBackgroundImageState(preferences?.viewPreferences?.backgroundImage);
|
||||
}
|
||||
if (preferences?.viewPreferences?.backgroundBlur !== backgroundBlur) {
|
||||
setBackgroundBlurState(preferences?.viewPreferences?.backgroundBlur || 0);
|
||||
}
|
||||
if (preferences?.viewPreferences?.backgroundOpacity !== backgroundOpacity) {
|
||||
setBackgroundOpacityState(preferences?.viewPreferences?.backgroundOpacity || 100);
|
||||
}
|
||||
}, [preferences?.viewPreferences?.backgroundImage, preferences?.viewPreferences?.backgroundBlur, preferences?.viewPreferences?.backgroundOpacity, backgroundImage, backgroundBlur, backgroundOpacity]);
|
||||
|
||||
// Apply background image to document body
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
const body = document.body;
|
||||
|
||||
// Supprimer l'ancien élément de fond s'il existe
|
||||
const existingBackground = document.getElementById('custom-background');
|
||||
if (existingBackground) {
|
||||
existingBackground.remove();
|
||||
}
|
||||
|
||||
if (backgroundImage) {
|
||||
// Créer un élément div pour l'image de fond avec les effets
|
||||
const backgroundElement = document.createElement('div');
|
||||
backgroundElement.id = 'custom-background';
|
||||
backgroundElement.style.position = 'fixed';
|
||||
backgroundElement.style.top = '0';
|
||||
backgroundElement.style.left = '0';
|
||||
backgroundElement.style.width = '100%';
|
||||
backgroundElement.style.height = '100%';
|
||||
backgroundElement.style.zIndex = '-1';
|
||||
backgroundElement.style.pointerEvents = 'none';
|
||||
|
||||
// Vérifier si c'est une URL d'image ou un preset
|
||||
const PRESET_BACKGROUNDS = [
|
||||
'theme-subtle',
|
||||
'theme-primary',
|
||||
'theme-accent',
|
||||
'theme-success',
|
||||
'theme-purple',
|
||||
'theme-diagonal',
|
||||
'theme-radial'
|
||||
];
|
||||
|
||||
if (PRESET_BACKGROUNDS.includes(backgroundImage)) {
|
||||
// Appliquer le preset basé sur le thème
|
||||
const presetStyles = {
|
||||
'theme-subtle': 'linear-gradient(135deg, var(--background) 0%, var(--card-column) 100%)',
|
||||
'theme-primary': 'linear-gradient(135deg, var(--background) 0%, color-mix(in srgb, var(--primary) 20%, var(--background)) 100%)',
|
||||
'theme-accent': 'linear-gradient(135deg, var(--background) 0%, color-mix(in srgb, var(--accent) 20%, var(--background)) 100%)',
|
||||
'theme-success': 'linear-gradient(135deg, var(--background) 0%, color-mix(in srgb, var(--success) 20%, var(--background)) 100%)',
|
||||
'theme-purple': 'linear-gradient(135deg, var(--background) 0%, color-mix(in srgb, var(--purple) 20%, var(--background)) 100%)',
|
||||
'theme-diagonal': 'linear-gradient(45deg, var(--background) 0%, color-mix(in srgb, var(--primary) 15%, var(--background)) 50%, color-mix(in srgb, var(--accent) 15%, var(--background)) 100%)',
|
||||
'theme-radial': 'radial-gradient(circle at center, var(--background) 0%, color-mix(in srgb, var(--primary) 25%, var(--background)) 100%)'
|
||||
};
|
||||
|
||||
backgroundElement.style.backgroundImage = presetStyles[backgroundImage as keyof typeof presetStyles];
|
||||
} else {
|
||||
// Appliquer l'URL d'image personnalisée
|
||||
backgroundElement.style.backgroundImage = `url(${backgroundImage})`;
|
||||
}
|
||||
|
||||
// Appliquer les propriétés communes
|
||||
backgroundElement.style.backgroundSize = 'cover';
|
||||
backgroundElement.style.backgroundPosition = 'center';
|
||||
backgroundElement.style.backgroundRepeat = 'no-repeat';
|
||||
backgroundElement.style.filter = `blur(${backgroundBlur}px)`;
|
||||
backgroundElement.style.opacity = `${backgroundOpacity / 100}`;
|
||||
|
||||
// Ajouter l'élément au body
|
||||
body.appendChild(backgroundElement);
|
||||
body.classList.add('has-background-image');
|
||||
} else {
|
||||
// Supprimer l'image de fond
|
||||
body.classList.remove('has-background-image');
|
||||
}
|
||||
}
|
||||
}, [backgroundImage, backgroundBlur, backgroundOpacity, mounted]);
|
||||
|
||||
const setBackgroundImage = (image: string | undefined) => {
|
||||
setBackgroundImageState(image);
|
||||
};
|
||||
|
||||
return (
|
||||
<BackgroundContext.Provider value={{ backgroundImage, setBackgroundImage }}>
|
||||
{children}
|
||||
</BackgroundContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useBackground() {
|
||||
const context = useContext(BackgroundContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useBackground must be used within a BackgroundProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -57,7 +57,10 @@ const defaultPreferences: UserPreferences = {
|
||||
showFilters: true,
|
||||
objectivesCollapsed: false,
|
||||
theme: 'light',
|
||||
fontSize: 'medium'
|
||||
fontSize: 'medium',
|
||||
backgroundImage: undefined,
|
||||
backgroundBlur: 0,
|
||||
backgroundOpacity: 100
|
||||
},
|
||||
columnVisibility: {
|
||||
hiddenStatuses: []
|
||||
|
||||
Reference in New Issue
Block a user