feat: enhance keyboard shortcuts and background image handling
- Added `GlobalKeyboardShortcuts` component to manage global keyboard shortcuts. - Introduced new keyboard shortcut (Shift + B) for changing the background. - Updated `BackgroundImageSelector` to preserve custom background URLs and allow restoration of previously set backgrounds. - Improved local storage handling for custom backgrounds to enhance user experience.
This commit is contained in:
@@ -8,6 +8,7 @@ import { UserPreferencesProvider } from "@/contexts/UserPreferencesContext";
|
|||||||
import { KeyboardShortcutsProvider } from "@/contexts/KeyboardShortcutsContext";
|
import { KeyboardShortcutsProvider } from "@/contexts/KeyboardShortcutsContext";
|
||||||
import { userPreferencesService } from "@/services/core/user-preferences";
|
import { userPreferencesService } from "@/services/core/user-preferences";
|
||||||
import { KeyboardShortcuts } from "@/components/KeyboardShortcuts";
|
import { KeyboardShortcuts } from "@/components/KeyboardShortcuts";
|
||||||
|
import { GlobalKeyboardShortcuts } from "@/components/GlobalKeyboardShortcuts";
|
||||||
import { AuthProvider } from "../components/AuthProvider";
|
import { AuthProvider } from "../components/AuthProvider";
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/lib/auth';
|
import { authOptions } from '@/lib/auth';
|
||||||
@@ -55,6 +56,7 @@ export default async function RootLayout({
|
|||||||
<KeyboardShortcuts />
|
<KeyboardShortcuts />
|
||||||
<JiraConfigProvider config={initialPreferences?.jiraConfig || { enabled: false }}>
|
<JiraConfigProvider config={initialPreferences?.jiraConfig || { enabled: false }}>
|
||||||
<UserPreferencesProvider initialPreferences={initialPreferences}>
|
<UserPreferencesProvider initialPreferences={initialPreferences}>
|
||||||
|
<GlobalKeyboardShortcuts />
|
||||||
<BackgroundProvider>
|
<BackgroundProvider>
|
||||||
{children}
|
{children}
|
||||||
</BackgroundProvider>
|
</BackgroundProvider>
|
||||||
|
|||||||
14
src/components/GlobalKeyboardShortcuts.tsx
Normal file
14
src/components/GlobalKeyboardShortcuts.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
|
||||||
|
import { useBackgroundCycle } from '@/hooks/useBackgroundCycle';
|
||||||
|
|
||||||
|
export function GlobalKeyboardShortcuts() {
|
||||||
|
const { cycleBackground } = useBackgroundCycle();
|
||||||
|
|
||||||
|
useGlobalKeyboardShortcuts({
|
||||||
|
onCycleBackground: cycleBackground
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -87,11 +87,20 @@ 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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
updateViewPreferences({ backgroundImage });
|
updateViewPreferences({ backgroundImage });
|
||||||
@@ -100,14 +109,24 @@ export function BackgroundImageSelector() {
|
|||||||
const handleCustomUrlSubmit = () => {
|
const handleCustomUrlSubmit = () => {
|
||||||
if (!customUrl.trim()) return;
|
if (!customUrl.trim()) return;
|
||||||
|
|
||||||
updateViewPreferences({ backgroundImage: customUrl.trim() });
|
const url = customUrl.trim();
|
||||||
|
updateViewPreferences({ backgroundImage: url });
|
||||||
|
setPreservedCustomUrl(url);
|
||||||
|
localStorage.setItem('preservedCustomBackground', url);
|
||||||
setCustomUrl('');
|
setCustomUrl('');
|
||||||
setShowCustomInput(false);
|
setShowCustomInput(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleRemoveCustom = () => {
|
const handleRemoveCustom = () => {
|
||||||
updateViewPreferences({ backgroundImage: undefined });
|
updateViewPreferences({ backgroundImage: undefined });
|
||||||
|
setPreservedCustomUrl('');
|
||||||
|
localStorage.removeItem('preservedCustomBackground');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRestoreCustom = () => {
|
||||||
|
if (preservedCustomUrl) {
|
||||||
|
updateViewPreferences({ backgroundImage: preservedCustomUrl });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlurChange = (blur: number) => {
|
const handleBlurChange = (blur: number) => {
|
||||||
@@ -212,6 +231,17 @@ 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"
|
||||||
@@ -220,6 +250,7 @@ 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">
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ const PAGE_SHORTCUTS: PageShortcuts = {
|
|||||||
description: 'Faire tourner les thèmes dark',
|
description: 'Faire tourner les thèmes dark',
|
||||||
category: 'Apparence'
|
category: 'Apparence'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
keys: ['Shift', 'B'],
|
||||||
|
description: 'Changer le background',
|
||||||
|
category: 'Apparence'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
keys: ['Esc'],
|
keys: ['Esc'],
|
||||||
description: 'Fermer les modales/annuler',
|
description: 'Fermer les modales/annuler',
|
||||||
|
|||||||
103
src/hooks/useBackgroundCycle.ts
Normal file
103
src/hooks/useBackgroundCycle.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
||||||
|
|
||||||
|
// Liste des backgrounds prédéfinis pour le cycle
|
||||||
|
const BACKGROUND_CYCLE = [
|
||||||
|
'none',
|
||||||
|
'theme-subtle',
|
||||||
|
'theme-primary',
|
||||||
|
'theme-accent',
|
||||||
|
'theme-success',
|
||||||
|
'theme-purple',
|
||||||
|
'theme-diagonal',
|
||||||
|
'theme-radial',
|
||||||
|
'theme-sunset',
|
||||||
|
'theme-ocean',
|
||||||
|
'theme-forest',
|
||||||
|
'theme-galaxy'
|
||||||
|
];
|
||||||
|
|
||||||
|
export function useBackgroundCycle() {
|
||||||
|
const { preferences, updateViewPreferences } = useUserPreferences();
|
||||||
|
|
||||||
|
const cycleBackground = () => {
|
||||||
|
const currentBackground = preferences?.viewPreferences?.backgroundImage;
|
||||||
|
|
||||||
|
// Construire la liste complète des backgrounds (prédéfinis + personnalisé)
|
||||||
|
const allBackgrounds = [...BACKGROUND_CYCLE];
|
||||||
|
|
||||||
|
// Ajouter l'image personnalisée préservée si elle existe
|
||||||
|
const preservedCustomUrl = localStorage.getItem('preservedCustomBackground');
|
||||||
|
console.log('Debug cycle:', {
|
||||||
|
currentBackground,
|
||||||
|
preservedCustomUrl,
|
||||||
|
localStorageKeys: Object.keys(localStorage)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (preservedCustomUrl && !BACKGROUND_CYCLE.includes(preservedCustomUrl)) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Si on ne trouve pas l'index, c'est qu'on est sur "none" (undefined)
|
||||||
|
// Dans ce cas, on commence au début de la liste
|
||||||
|
let actualCurrentIndex = currentIndex;
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
actualCurrentIndex = -1; // On est sur "none", on va commencer à l'index 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextIndex = (actualCurrentIndex + 1) % allBackgrounds.length;
|
||||||
|
const nextBackground = allBackgrounds[nextIndex];
|
||||||
|
|
||||||
|
// Si c'est 'none', on met undefined
|
||||||
|
const backgroundImage = nextBackground === 'none' ? undefined : nextBackground;
|
||||||
|
|
||||||
|
// Si on est sur "none" (undefined) et qu'on va vers "none", on va vers le suivant
|
||||||
|
if (currentBackground === undefined && nextBackground === 'none') {
|
||||||
|
const nextNextIndex = (nextIndex + 1) % allBackgrounds.length;
|
||||||
|
const nextNextBackground = allBackgrounds[nextNextIndex];
|
||||||
|
const finalBackgroundImage = nextNextBackground === 'none' ? undefined : nextNextBackground;
|
||||||
|
|
||||||
|
console.log('Cycle result:', {
|
||||||
|
allBackgrounds,
|
||||||
|
currentIndex,
|
||||||
|
actualCurrentIndex,
|
||||||
|
nextIndex,
|
||||||
|
nextBackground,
|
||||||
|
backgroundImage,
|
||||||
|
currentBackground,
|
||||||
|
nextNextIndex,
|
||||||
|
nextNextBackground,
|
||||||
|
finalBackgroundImage
|
||||||
|
});
|
||||||
|
|
||||||
|
updateViewPreferences({ backgroundImage: finalBackgroundImage });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Cycle result:', {
|
||||||
|
allBackgrounds,
|
||||||
|
currentIndex,
|
||||||
|
actualCurrentIndex,
|
||||||
|
nextIndex,
|
||||||
|
nextBackground,
|
||||||
|
backgroundImage,
|
||||||
|
currentBackground
|
||||||
|
});
|
||||||
|
|
||||||
|
updateViewPreferences({ backgroundImage });
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
cycleBackground
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ interface KeyboardShortcutsActions {
|
|||||||
onSave?: () => void;
|
onSave?: () => void;
|
||||||
onBackup?: () => void;
|
onBackup?: () => void;
|
||||||
onOpenSearch?: () => void;
|
onOpenSearch?: () => void;
|
||||||
|
onCycleBackground?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGlobalKeyboardShortcuts(actions: KeyboardShortcutsActions = {}) {
|
export function useGlobalKeyboardShortcuts(actions: KeyboardShortcutsActions = {}) {
|
||||||
@@ -56,6 +57,10 @@ export function useGlobalKeyboardShortcuts(actions: KeyboardShortcutsActions = {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
actions.onOpenSearch?.();
|
actions.onOpenSearch?.();
|
||||||
return;
|
return;
|
||||||
|
case 'B':
|
||||||
|
event.preventDefault();
|
||||||
|
actions.onCycleBackground?.();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user