diff --git a/src/lib/i18n/locales/de.json b/src/lib/i18n/locales/de.json index e27bb1026..525d937b8 100644 --- a/src/lib/i18n/locales/de.json +++ b/src/lib/i18n/locales/de.json @@ -1402,7 +1402,8 @@ "playback": "Wiedergabe", "navigation": "Navigation", "ui": "Oberfläche", - "focus": "Immersiv" + "focus": "Immersiv", + "mini": "Mini-Player" }, "actions": { "playbackToggle": "Wiedergabe / Pause", @@ -1417,10 +1418,16 @@ "uiEscape": "Schließen / Verwerfen", "uiShowShortcuts": "Tastenkürzel anzeigen", "uiOpenLink": "Musik-Link öffnen", + "uiMiniPlayer": "Mini-Player umschalten", "focusSeekForward": "Vorspulen (5s)", "focusSeekBack": "Zurückspulen (5s)", "focusSeekForwardLong": "Vorspulen (10s)", - "focusSeekBackLong": "Zurückspulen (10s)" + "focusSeekBackLong": "Zurückspulen (10s)", + "miniMicro": "Mikro-Ansicht", + "miniCompact": "Kompaktansicht", + "miniArtwork": "Cover-Ansicht", + "miniQueue": "Warteschlange-Ansicht", + "miniLyrics": "Text-Ansicht" }, "descriptions": { "playbackToggle": "Wiedergabe umschalten", @@ -1435,10 +1442,16 @@ "uiEscape": "Aktives Panel oder Modal schließen", "uiShowShortcuts": "Tastenkürzel-Modal anzeigen", "uiOpenLink": "Einen Qobuz-Link in der App öffnen", + "uiMiniPlayer": "Mini-Player-Modus umschalten", "focusSeekForward": "5 Sekunden vorspulen", "focusSeekBack": "5 Sekunden zurückspulen", "focusSeekForwardLong": "10 Sekunden vorspulen", - "focusSeekBackLong": "10 Sekunden zurückspulen" + "focusSeekBackLong": "10 Sekunden zurückspulen", + "miniMicro": "Zur Mikro-Ansicht wechseln", + "miniCompact": "Zur Kompaktansicht wechseln", + "miniArtwork": "Zur Cover-Ansicht wechseln", + "miniQueue": "Zur Warteschlange-Ansicht wechseln", + "miniLyrics": "Zur Text-Ansicht wechseln" } }, "dacWizard": { diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json index 24d788316..3cb62eea6 100644 --- a/src/lib/i18n/locales/en.json +++ b/src/lib/i18n/locales/en.json @@ -1464,7 +1464,8 @@ "playback": "Playback", "navigation": "Navigation", "ui": "Interface", - "focus": "Immersive" + "focus": "Immersive", + "mini": "Mini Player" }, "actions": { "playbackToggle": "Play / Pause", @@ -1479,10 +1480,16 @@ "uiEscape": "Close / Dismiss", "uiShowShortcuts": "Show Shortcuts", "uiOpenLink": "Open Music Link", + "uiMiniPlayer": "Toggle Mini Player", "focusSeekForward": "Seek Forward (5s)", "focusSeekBack": "Seek Back (5s)", "focusSeekForwardLong": "Seek Forward (10s)", - "focusSeekBackLong": "Seek Back (10s)" + "focusSeekBackLong": "Seek Back (10s)", + "miniMicro": "Micro View", + "miniCompact": "Compact View", + "miniArtwork": "Artwork View", + "miniQueue": "Queue View", + "miniLyrics": "Lyrics View" }, "descriptions": { "playbackToggle": "Toggle playback", @@ -1497,10 +1504,16 @@ "uiEscape": "Close active panel or modal", "uiShowShortcuts": "Show keyboard shortcuts modal", "uiOpenLink": "Open a Qobuz link in the app", + "uiMiniPlayer": "Toggle mini player mode", "focusSeekForward": "Seek forward 5 seconds", "focusSeekBack": "Seek back 5 seconds", "focusSeekForwardLong": "Seek forward 10 seconds", - "focusSeekBackLong": "Seek back 10 seconds" + "focusSeekBackLong": "Seek back 10 seconds", + "miniMicro": "Switch to micro view", + "miniCompact": "Switch to compact view", + "miniArtwork": "Switch to artwork view", + "miniQueue": "Switch to queue view", + "miniLyrics": "Switch to lyrics view" } }, "dacWizard": { diff --git a/src/lib/i18n/locales/es.json b/src/lib/i18n/locales/es.json index 8511528f2..a2feaf034 100644 --- a/src/lib/i18n/locales/es.json +++ b/src/lib/i18n/locales/es.json @@ -1464,7 +1464,8 @@ "playback": "Reproducción", "navigation": "Navegación", "ui": "Interfaz", - "focus": "Inmersivo" + "focus": "Inmersivo", + "mini": "Mini Reproductor" }, "actions": { "playbackToggle": "Reproducir / Pausar", @@ -1479,10 +1480,16 @@ "uiEscape": "Cerrar / Descartar", "uiShowShortcuts": "Mostrar Atajos", "uiOpenLink": "Abrir Enlace Musical", + "uiMiniPlayer": "Alternar Mini Reproductor", "focusSeekForward": "Avanzar (5s)", "focusSeekBack": "Retroceder (5s)", "focusSeekForwardLong": "Avanzar (10s)", - "focusSeekBackLong": "Retroceder (10s)" + "focusSeekBackLong": "Retroceder (10s)", + "miniMicro": "Vista Micro", + "miniCompact": "Vista Compacta", + "miniArtwork": "Vista Carátula", + "miniQueue": "Vista Cola", + "miniLyrics": "Vista Letras" }, "descriptions": { "playbackToggle": "Alternar reproducción", @@ -1497,10 +1504,16 @@ "uiEscape": "Cerrar panel activo o modal", "uiShowShortcuts": "Mostrar modal de atajos de teclado", "uiOpenLink": "Abrir un enlace de Qobuz en la app", + "uiMiniPlayer": "Alternar modo mini reproductor", "focusSeekForward": "Avanzar 5 segundos", "focusSeekBack": "Retroceder 5 segundos", "focusSeekForwardLong": "Avanzar 10 segundos", - "focusSeekBackLong": "Retroceder 10 segundos" + "focusSeekBackLong": "Retroceder 10 segundos", + "miniMicro": "Cambiar a vista micro", + "miniCompact": "Cambiar a vista compacta", + "miniArtwork": "Cambiar a vista carátula", + "miniQueue": "Cambiar a vista cola", + "miniLyrics": "Cambiar a vista letras" } }, "dacWizard": { diff --git a/src/lib/i18n/locales/fr.json b/src/lib/i18n/locales/fr.json index 55da63202..088ec5426 100644 --- a/src/lib/i18n/locales/fr.json +++ b/src/lib/i18n/locales/fr.json @@ -1402,7 +1402,8 @@ "playback": "Lecture", "navigation": "Navigation", "ui": "Interface", - "focus": "Immersif" + "focus": "Immersif", + "mini": "Mini Lecteur" }, "actions": { "playbackToggle": "Lecture / Pause", @@ -1417,10 +1418,16 @@ "uiEscape": "Fermer / Annuler", "uiShowShortcuts": "Afficher les raccourcis", "uiOpenLink": "Ouvrir un lien musical", + "uiMiniPlayer": "Basculer le mini lecteur", "focusSeekForward": "Avancer (5s)", "focusSeekBack": "Reculer (5s)", "focusSeekForwardLong": "Avancer (10s)", - "focusSeekBackLong": "Reculer (10s)" + "focusSeekBackLong": "Reculer (10s)", + "miniMicro": "Vue Micro", + "miniCompact": "Vue Compacte", + "miniArtwork": "Vue Pochette", + "miniQueue": "Vue File d'attente", + "miniLyrics": "Vue Paroles" }, "descriptions": { "playbackToggle": "Basculer la lecture", @@ -1435,10 +1442,16 @@ "uiEscape": "Fermer le panneau actif ou la modale", "uiShowShortcuts": "Afficher la modale des raccourcis clavier", "uiOpenLink": "Ouvrir un lien Qobuz dans l'application", + "uiMiniPlayer": "Basculer le mode mini lecteur", "focusSeekForward": "Avancer de 5 secondes", "focusSeekBack": "Reculer de 5 secondes", "focusSeekForwardLong": "Avancer de 10 secondes", - "focusSeekBackLong": "Reculer de 10 secondes" + "focusSeekBackLong": "Reculer de 10 secondes", + "miniMicro": "Passer à la vue micro", + "miniCompact": "Passer à la vue compacte", + "miniArtwork": "Passer à la vue pochette", + "miniQueue": "Passer à la vue file d'attente", + "miniLyrics": "Passer à la vue paroles" } }, "dacWizard": { diff --git a/src/lib/services/miniplayerService.ts b/src/lib/services/miniplayerService.ts index 03140ddeb..fc6823ecd 100644 --- a/src/lib/services/miniplayerService.ts +++ b/src/lib/services/miniplayerService.ts @@ -194,3 +194,21 @@ export async function setMiniplayerAlwaysOnTop(alwaysOnTop: boolean): Promise { await exitMiniplayerMode(); } + +/** + * Close the app from the mini player. + * + * Triggers the main window's close handler which either: + * - Hides all windows to tray (if close-to-tray is enabled) + * - Quits the app (if close-to-tray is disabled) + */ +export async function closeAppFromMiniplayer(): Promise { + try { + const mainWindow = await Window.getByLabel(MAIN_WINDOW_LABEL); + if (mainWindow) { + await mainWindow.close(); + } + } catch (err) { + console.error('[MiniPlayer] Failed to close app:', err); + } +} diff --git a/src/lib/stores/keybindingsStore.ts b/src/lib/stores/keybindingsStore.ts index 9b3274724..950eed3b6 100644 --- a/src/lib/stores/keybindingsStore.ts +++ b/src/lib/stores/keybindingsStore.ts @@ -21,7 +21,7 @@ export { eventToShortcut, formatShortcutDisplay, isInputTarget }; // Types // ============================================================================ -export type KeybindingCategory = 'playback' | 'navigation' | 'ui' | 'focus'; +export type KeybindingCategory = 'playback' | 'navigation' | 'ui' | 'focus' | 'mini'; export interface KeybindingAction { /** Unique identifier, e.g., 'playback.toggle' */ @@ -53,6 +53,7 @@ export const CATEGORIES: CategoryInfo[] = [ { id: 'navigation', label: 'Navigation', description: 'App navigation' }, { id: 'ui', label: 'Interface', description: 'UI elements' }, { id: 'focus', label: 'Immersive', description: 'Immersive mode controls' }, + { id: 'mini', label: 'Mini Player', description: 'Mini player controls' }, ]; export const ACTIONS: KeybindingAction[] = [ @@ -145,6 +146,13 @@ export const ACTIONS: KeybindingAction[] = [ defaultShortcut: 'Ctrl+l', description: 'Paste and open a Qobuz link', }, + { + id: 'ui.miniPlayer', + label: 'Toggle Mini Player', + category: 'ui', + defaultShortcut: 'Shift+M', + description: 'Toggle mini player mode', + }, // Focus Mode specific { @@ -179,6 +187,48 @@ export const ACTIONS: KeybindingAction[] = [ description: 'Seek back 10 seconds', contextual: true, }, + + // Mini Player specific + { + id: 'mini.micro', + label: 'Micro View', + category: 'mini', + defaultShortcut: '1', + description: 'Switch to micro view', + contextual: true, + }, + { + id: 'mini.compact', + label: 'Compact View', + category: 'mini', + defaultShortcut: '2', + description: 'Switch to compact view', + contextual: true, + }, + { + id: 'mini.artwork', + label: 'Artwork View', + category: 'mini', + defaultShortcut: '3', + description: 'Switch to artwork view', + contextual: true, + }, + { + id: 'mini.queue', + label: 'Queue View', + category: 'mini', + defaultShortcut: '4', + description: 'Switch to queue view', + contextual: true, + }, + { + id: 'mini.lyrics', + label: 'Lyrics View', + category: 'mini', + defaultShortcut: '5', + description: 'Switch to lyrics view', + contextual: true, + }, ]; /** Map of default bindings for quick lookup */ @@ -412,7 +462,7 @@ export function getConflictingAction( */ export function handleKeydown( event: KeyboardEvent, - context: { focusMode?: boolean } = {} + context: { focusMode?: boolean; miniPlayerMode?: boolean } = {} ): boolean { // Ignore if we're in an input if (isInputTarget(event)) { @@ -431,6 +481,9 @@ export function handleKeydown( if (action.category === 'focus' && !context.focusMode) { return false; } + if (action.category === 'mini' && !context.miniPlayerMode) { + return false; + } } // Execute callback diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 90e3e0315..3be65b479 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -3015,6 +3015,7 @@ }); registerAction('ui.sidebar', toggleSidebar); registerAction('ui.focusMode', toggleFocusMode); + registerAction('ui.miniPlayer', () => { void enterMiniplayerMode(); }); registerAction('ui.queue', toggleQueue); registerAction('ui.escape', handleUIEscape); registerAction('ui.showShortcuts', () => { isShortcutsModalOpen = true; }); diff --git a/src/routes/miniplayer/+page.svelte b/src/routes/miniplayer/+page.svelte index 5d5c4d175..dfa9a61f4 100644 --- a/src/routes/miniplayer/+page.svelte +++ b/src/routes/miniplayer/+page.svelte @@ -48,7 +48,12 @@ setMiniPlayerOpen, setMiniPlayerSurface } from '$lib/stores/uiStore'; - import { exitMiniplayerMode, setMiniplayerAlwaysOnTop } from '$lib/services/miniplayerService'; + import { exitMiniplayerMode, closeAppFromMiniplayer, setMiniplayerAlwaysOnTop } from '$lib/services/miniplayerService'; + import { + registerAction, + unregisterAll, + handleKeydown as keybindingHandler + } from '$lib/stores/keybindingsStore'; import { playTrack } from '$lib/services/playbackService'; let playerState = $state(getPlayerState()); @@ -60,6 +65,7 @@ let unlistenMoved: (() => void) | null = null; let unlistenResized: (() => void) | null = null; let unlistenCloseRequested: (() => void) | null = null; + let removeKeydownListener: (() => void) | null = null; let unsubscribePlayer: (() => void) | null = null; let unsubscribeQueue: (() => void) | null = null; @@ -219,9 +225,27 @@ unlistenCloseRequested = await appWindow.onCloseRequested((event) => { event.preventDefault(); - void exitMiniplayerMode(); + void closeAppFromMiniplayer(); }); + // Keyboard shortcuts + registerAction('playback.toggle', handleTogglePlay); + registerAction('playback.next', handleSkipForward); + registerAction('playback.prev', handleSkipBack); + registerAction('ui.miniPlayer', exitMiniplayerMode); + registerAction('ui.escape', exitMiniplayerMode); + registerAction('mini.micro', () => { void handleSurfaceChange('micro'); }); + registerAction('mini.compact', () => { void handleSurfaceChange('compact'); }); + registerAction('mini.artwork', () => { void handleSurfaceChange('artwork'); }); + registerAction('mini.queue', () => { void handleSurfaceChange('queue'); }); + registerAction('mini.lyrics', () => { void handleSurfaceChange('lyrics'); }); + + function onKeydown(event: KeyboardEvent) { + keybindingHandler(event, { miniPlayerMode: true }); + } + document.addEventListener('keydown', onKeydown); + removeKeydownListener = () => document.removeEventListener('keydown', onKeydown); + console.info('[MiniPlayer] Window ready'); }); @@ -233,7 +257,9 @@ unlistenMoved?.(); unlistenResized?.(); unlistenCloseRequested?.(); + removeKeydownListener?.(); + unregisterAll(); stopActiveLineUpdates(); stopWatching(); stopQueueEventListener(); @@ -433,7 +459,7 @@ }} onTogglePin={handleToggleAlwaysOnTop} onExpand={exitMiniplayerMode} - onClose={exitMiniplayerMode} + onClose={closeAppFromMiniplayer} onStartDrag={handleStartDrag} onTogglePlay={handleTogglePlay} onSkipBack={handleSkipBack}