mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
feat(mini-player): user-bindable keyboard shortcut to toggle mini player
Adds an 'open-mini-player' entry to the in-app keybindings (Settings → Shortcuts), default unbound. Pressing the chord from the main window opens the mini and minimises main; pressing it from the mini hides the mini and restores main — both paths invoke open_mini_player which already handles the toggle. The mini window has its own keydown listener (same pattern as Space / arrows for play/skip) that reads the binding from useKeybindingsStore. Binding changes in main propagate to an open mini through the existing storage-event sync (now also covers psysonic_keybindings) so the user doesn't need to restart the mini after rebinding. Localized in all 8 supported locales. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -812,6 +812,9 @@ function TauriEventBridge() {
|
|||||||
win.isFullscreen().then(fs => win.setFullscreen(!fs));
|
win.isFullscreen().then(fs => win.setFullscreen(!fs));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'open-mini-player':
|
||||||
|
invoke('open_mini_player').catch(() => {});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('keydown', onKey);
|
window.addEventListener('keydown', onKey);
|
||||||
@@ -983,6 +986,7 @@ export default function App() {
|
|||||||
if (!e.key) return;
|
if (!e.key) return;
|
||||||
if (e.key === 'psysonic_theme') useThemeStore.persist.rehydrate();
|
if (e.key === 'psysonic_theme') useThemeStore.persist.rehydrate();
|
||||||
else if (e.key === 'psysonic_font') useFontStore.persist.rehydrate();
|
else if (e.key === 'psysonic_font') useFontStore.persist.rehydrate();
|
||||||
|
else if (e.key === 'psysonic_keybindings') useKeybindingsStore.persist.rehydrate();
|
||||||
else if (e.key === 'psysonic_language' && e.newValue) {
|
else if (e.key === 'psysonic_language' && e.newValue) {
|
||||||
import('./i18n').then(m => m.default.changeLanguage(e.newValue!));
|
import('./i18n').then(m => m.default.changeLanguage(e.newValue!));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Play, Pause, SkipBack, SkipForward, Pin, PinOff, Maximize2, X, ListMusi
|
|||||||
import CachedImage from './CachedImage';
|
import CachedImage from './CachedImage';
|
||||||
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
|
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
|
import { useKeybindingsStore, matchInAppBinding } from '../store/keybindingsStore';
|
||||||
import { useDragDrop } from '../contexts/DragDropContext';
|
import { useDragDrop } from '../contexts/DragDropContext';
|
||||||
import MiniContextMenu from './MiniContextMenu';
|
import MiniContextMenu from './MiniContextMenu';
|
||||||
import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '../utils/miniPlayerBridge';
|
import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '../utils/miniPlayerBridge';
|
||||||
@@ -178,11 +179,21 @@ export default function MiniPlayer() {
|
|||||||
}, [alwaysOnTop]);
|
}, [alwaysOnTop]);
|
||||||
|
|
||||||
// Keyboard: Space → toggle, ← / → → prev / next. Ignore when typing.
|
// Keyboard: Space → toggle, ← / → → prev / next. Ignore when typing.
|
||||||
|
// Also honour the user-configured 'open-mini-player' shortcut so the
|
||||||
|
// same chord that opens the mini from main also closes it from here.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
const tgt = e.target as HTMLElement | null;
|
const tgt = e.target as HTMLElement | null;
|
||||||
const tag = tgt?.tagName;
|
const tag = tgt?.tagName;
|
||||||
if (tag === 'INPUT' || tag === 'TEXTAREA' || tgt?.isContentEditable) return;
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tgt?.isContentEditable) return;
|
||||||
|
|
||||||
|
const openMiniBinding = useKeybindingsStore.getState().bindings['open-mini-player'];
|
||||||
|
if (matchInAppBinding(e, openMiniBinding)) {
|
||||||
|
e.preventDefault();
|
||||||
|
invoke('open_mini_player').catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === ' ' || e.code === 'Space') {
|
if (e.key === ' ' || e.code === 'Space') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
emit('mini:control', 'toggle').catch(() => {});
|
emit('mini:control', 'toggle').catch(() => {});
|
||||||
|
|||||||
@@ -642,6 +642,7 @@ export const deTranslation = {
|
|||||||
shortcutOpenFolderBrowser: '{{folderBrowser}} öffnen',
|
shortcutOpenFolderBrowser: '{{folderBrowser}} öffnen',
|
||||||
shortcutFullscreenPlayer: 'Vollbild-Player',
|
shortcutFullscreenPlayer: 'Vollbild-Player',
|
||||||
shortcutNativeFullscreen: 'Nativer Vollbildmodus',
|
shortcutNativeFullscreen: 'Nativer Vollbildmodus',
|
||||||
|
shortcutOpenMiniPlayer: 'Mini-Player öffnen',
|
||||||
tabSystem: 'System',
|
tabSystem: 'System',
|
||||||
tabGeneral: 'Allgemein',
|
tabGeneral: 'Allgemein',
|
||||||
ratingsSectionTitle: 'Bewertungen',
|
ratingsSectionTitle: 'Bewertungen',
|
||||||
|
|||||||
@@ -667,6 +667,7 @@ export const enTranslation = {
|
|||||||
shortcutOpenFolderBrowser: 'Open {{folderBrowser}}',
|
shortcutOpenFolderBrowser: 'Open {{folderBrowser}}',
|
||||||
shortcutFullscreenPlayer: 'Fullscreen player',
|
shortcutFullscreenPlayer: 'Fullscreen player',
|
||||||
shortcutNativeFullscreen: 'Native fullscreen',
|
shortcutNativeFullscreen: 'Native fullscreen',
|
||||||
|
shortcutOpenMiniPlayer: 'Open mini player',
|
||||||
playbackTitle: 'Playback',
|
playbackTitle: 'Playback',
|
||||||
replayGain: 'Replay Gain',
|
replayGain: 'Replay Gain',
|
||||||
replayGainDesc: 'Normalize track volume using ReplayGain metadata',
|
replayGainDesc: 'Normalize track volume using ReplayGain metadata',
|
||||||
|
|||||||
@@ -658,6 +658,7 @@ export const esTranslation = {
|
|||||||
shortcutOpenFolderBrowser: 'Abrir {{folderBrowser}}',
|
shortcutOpenFolderBrowser: 'Abrir {{folderBrowser}}',
|
||||||
shortcutFullscreenPlayer: 'Reproductor pantalla completa',
|
shortcutFullscreenPlayer: 'Reproductor pantalla completa',
|
||||||
shortcutNativeFullscreen: 'Pantalla completa nativa',
|
shortcutNativeFullscreen: 'Pantalla completa nativa',
|
||||||
|
shortcutOpenMiniPlayer: 'Abrir mini reproductor',
|
||||||
playbackTitle: 'Reproducción',
|
playbackTitle: 'Reproducción',
|
||||||
replayGain: 'Replay Gain',
|
replayGain: 'Replay Gain',
|
||||||
replayGainDesc: 'Normalizar volumen de pistas usando metadatos ReplayGain',
|
replayGainDesc: 'Normalizar volumen de pistas usando metadatos ReplayGain',
|
||||||
|
|||||||
@@ -630,6 +630,7 @@ export const frTranslation = {
|
|||||||
shortcutOpenFolderBrowser: 'Ouvrir {{folderBrowser}}',
|
shortcutOpenFolderBrowser: 'Ouvrir {{folderBrowser}}',
|
||||||
shortcutFullscreenPlayer: 'Lecteur plein écran',
|
shortcutFullscreenPlayer: 'Lecteur plein écran',
|
||||||
shortcutNativeFullscreen: 'Plein écran natif',
|
shortcutNativeFullscreen: 'Plein écran natif',
|
||||||
|
shortcutOpenMiniPlayer: 'Ouvrir le mini-lecteur',
|
||||||
tabSystem: 'Système',
|
tabSystem: 'Système',
|
||||||
tabGeneral: 'Général',
|
tabGeneral: 'Général',
|
||||||
ratingsSectionTitle: 'Notes',
|
ratingsSectionTitle: 'Notes',
|
||||||
|
|||||||
@@ -652,6 +652,7 @@ export const nbTranslation = {
|
|||||||
shortcutOpenFolderBrowser: 'Åpne {{folderBrowser}}',
|
shortcutOpenFolderBrowser: 'Åpne {{folderBrowser}}',
|
||||||
shortcutFullscreenPlayer: 'Fullskjermsspiller',
|
shortcutFullscreenPlayer: 'Fullskjermsspiller',
|
||||||
shortcutNativeFullscreen: 'Naturlig fullskjerm',
|
shortcutNativeFullscreen: 'Naturlig fullskjerm',
|
||||||
|
shortcutOpenMiniPlayer: 'Åpne minispiller',
|
||||||
playbackTitle: 'Avspilling',
|
playbackTitle: 'Avspilling',
|
||||||
replayGain: 'Replay Gain',
|
replayGain: 'Replay Gain',
|
||||||
replayGainDesc: 'Normaliser sporvolumet ved hjelp av ReplayGain-metadata',
|
replayGainDesc: 'Normaliser sporvolumet ved hjelp av ReplayGain-metadata',
|
||||||
|
|||||||
@@ -629,6 +629,7 @@ export const nlTranslation = {
|
|||||||
shortcutOpenFolderBrowser: '{{folderBrowser}} openen',
|
shortcutOpenFolderBrowser: '{{folderBrowser}} openen',
|
||||||
shortcutFullscreenPlayer: 'Volledigschermspeler',
|
shortcutFullscreenPlayer: 'Volledigschermspeler',
|
||||||
shortcutNativeFullscreen: 'Systeemvolledig scherm',
|
shortcutNativeFullscreen: 'Systeemvolledig scherm',
|
||||||
|
shortcutOpenMiniPlayer: 'Mini-speler openen',
|
||||||
tabSystem: 'Systeem',
|
tabSystem: 'Systeem',
|
||||||
tabGeneral: 'Algemeen',
|
tabGeneral: 'Algemeen',
|
||||||
ratingsSectionTitle: 'Beoordelingen',
|
ratingsSectionTitle: 'Beoordelingen',
|
||||||
|
|||||||
@@ -682,6 +682,7 @@ export const ruTranslation = {
|
|||||||
shortcutOpenFolderBrowser: 'Открыть {{folderBrowser}}',
|
shortcutOpenFolderBrowser: 'Открыть {{folderBrowser}}',
|
||||||
shortcutFullscreenPlayer: 'Полноэкранный плеер',
|
shortcutFullscreenPlayer: 'Полноэкранный плеер',
|
||||||
shortcutNativeFullscreen: 'Системный полный экран',
|
shortcutNativeFullscreen: 'Системный полный экран',
|
||||||
|
shortcutOpenMiniPlayer: 'Открыть мини-плеер',
|
||||||
playbackTitle: 'Воспроизведение',
|
playbackTitle: 'Воспроизведение',
|
||||||
replayGain: 'Replay Gain',
|
replayGain: 'Replay Gain',
|
||||||
replayGainDesc: 'Выравнивание громкости по метаданным ReplayGain',
|
replayGainDesc: 'Выравнивание громкости по метаданным ReplayGain',
|
||||||
|
|||||||
@@ -648,6 +648,7 @@ export const zhTranslation = {
|
|||||||
shortcutOpenFolderBrowser: '打开{{folderBrowser}}',
|
shortcutOpenFolderBrowser: '打开{{folderBrowser}}',
|
||||||
shortcutFullscreenPlayer: '全屏播放器',
|
shortcutFullscreenPlayer: '全屏播放器',
|
||||||
shortcutNativeFullscreen: '原生全屏',
|
shortcutNativeFullscreen: '原生全屏',
|
||||||
|
shortcutOpenMiniPlayer: '打开迷你播放器',
|
||||||
playbackTitle: '播放',
|
playbackTitle: '播放',
|
||||||
replayGain: '回放增益',
|
replayGain: '回放增益',
|
||||||
replayGainDesc: '使用 ReplayGain 元数据标准化曲目音量',
|
replayGainDesc: '使用 ReplayGain 元数据标准化曲目音量',
|
||||||
|
|||||||
@@ -2010,6 +2010,7 @@ export default function Settings() {
|
|||||||
['open-folder-browser', t('settings.shortcutOpenFolderBrowser', { folderBrowser: t('sidebar.folderBrowser') })],
|
['open-folder-browser', t('settings.shortcutOpenFolderBrowser', { folderBrowser: t('sidebar.folderBrowser') })],
|
||||||
['fullscreen-player', t('settings.shortcutFullscreenPlayer')],
|
['fullscreen-player', t('settings.shortcutFullscreenPlayer')],
|
||||||
['native-fullscreen', t('settings.shortcutNativeFullscreen')],
|
['native-fullscreen', t('settings.shortcutNativeFullscreen')],
|
||||||
|
['open-mini-player', t('settings.shortcutOpenMiniPlayer')],
|
||||||
] as [KeyAction, string][]).map(([action, label]) => {
|
] as [KeyAction, string][]).map(([action, label]) => {
|
||||||
const bound = kb.bindings[action];
|
const bound = kb.bindings[action];
|
||||||
const isListening = listeningFor === action;
|
const isListening = listeningFor === action;
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export type KeyAction =
|
|||||||
| 'toggle-queue'
|
| 'toggle-queue'
|
||||||
| 'open-folder-browser'
|
| 'open-folder-browser'
|
||||||
| 'fullscreen-player'
|
| 'fullscreen-player'
|
||||||
| 'native-fullscreen';
|
| 'native-fullscreen'
|
||||||
|
| 'open-mini-player';
|
||||||
|
|
||||||
/** Physical keys only — ignore for binding capture */
|
/** Physical keys only — ignore for binding capture */
|
||||||
export const MODIFIER_KEY_CODES = [
|
export const MODIFIER_KEY_CODES = [
|
||||||
@@ -35,6 +36,7 @@ export const DEFAULT_BINDINGS: Bindings = {
|
|||||||
'open-folder-browser': null,
|
'open-folder-browser': null,
|
||||||
'fullscreen-player': null,
|
'fullscreen-player': null,
|
||||||
'native-fullscreen': 'F11',
|
'native-fullscreen': 'F11',
|
||||||
|
'open-mini-player': null,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface KeybindingsState {
|
interface KeybindingsState {
|
||||||
|
|||||||
Reference in New Issue
Block a user