Add hotkey controls to relevant pages

This commit is contained in:
jeffvli 2023-05-13 00:59:19 -07:00 committed by Jeff
parent d7f24262fd
commit 4c98afb613
6 changed files with 155 additions and 37 deletions

View file

@ -3,6 +3,8 @@ import { TextInputProps } from '@mantine/core';
import { useFocusWithin, useHotkeys, useMergedRef } from '@mantine/hooks';
import { RiSearchLine } from 'react-icons/ri';
import { TextInput } from '/@/renderer/components/input';
import { useSettingsStore } from '/@/renderer/store';
import { shallow } from 'zustand/shallow';
interface SearchInputProps extends TextInputProps {
initialWidth?: number;
@ -18,18 +20,12 @@ export const SearchInput = ({
}: SearchInputProps) => {
const { ref, focused } = useFocusWithin();
const mergedRef = useMergedRef<HTMLInputElement>(ref);
const binding = useSettingsStore((state) => state.hotkeys.bindings.localSearch, shallow);
const isOpened = focused || ref.current?.value;
const showIcon = !isOpened || (openedWidth || 100) > 100;
useHotkeys([
[
'ctrl+F',
() => {
ref.current.select();
},
],
]);
useHotkeys([[binding.hotkey, () => ref.current.select()]]);
const handleEscape = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.code === 'Escape') {

View file

@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { useHotkeys } from '@mantine/hooks';
import formatDuration from 'format-duration';
import isElectron from 'is-electron';
import { IoIosPause } from 'react-icons/io';
@ -25,7 +26,11 @@ import {
useShuffleStatus,
useCurrentTime,
} from '/@/renderer/store';
import { usePlayerType, useSettingsStore } from '/@/renderer/store/settings.store';
import {
useHotkeySettings,
usePlayerType,
useSettingsStore,
} from '/@/renderer/store/settings.store';
import { PlayerStatus, PlaybackType, PlayerShuffle, PlayerRepeat } from '/@/renderer/types';
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
@ -78,6 +83,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
const setCurrentTime = useSetCurrentTime();
const repeat = useRepeatStatus();
const shuffle = useShuffleStatus();
const { bindings } = useHotkeySettings();
const {
handleNextTrack,
@ -88,6 +94,9 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
handleSkipForward,
handleToggleRepeat,
handleToggleShuffle,
handleStop,
handlePause,
handlePlay,
} = useCenterControls({ playersRef });
const currentTime = useCurrentTime();
@ -113,6 +122,25 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
const [seekValue, setSeekValue] = useState(0);
useHotkeys([
[bindings.playPause.isGlobal ? '' : bindings.playPause.hotkey, handlePlayPause],
[bindings.play.isGlobal ? '' : bindings.play.hotkey, handlePlay],
[bindings.pause.isGlobal ? '' : bindings.pause.hotkey, handlePause],
[bindings.stop.isGlobal ? '' : bindings.stop.hotkey, handleStop],
[bindings.next.isGlobal ? '' : bindings.next.hotkey, handleNextTrack],
[bindings.previous.isGlobal ? '' : bindings.previous.hotkey, handlePrevTrack],
[bindings.toggleRepeat.isGlobal ? '' : bindings.toggleRepeat.hotkey, handleToggleRepeat],
[bindings.toggleShuffle.isGlobal ? '' : bindings.toggleShuffle.hotkey, handleToggleShuffle],
[
bindings.skipBackward.isGlobal ? '' : bindings.skipBackward.hotkey,
() => handleSkipBackward(skip?.skipBackwardSeconds || 5),
],
[
bindings.skipForward.isGlobal ? '' : bindings.skipForward.hotkey,
() => handleSkipForward(skip?.skipForwardSeconds || 5),
],
]);
return (
<>
<ControlsContainer>

View file

@ -1,5 +1,6 @@
import React, { MouseEvent } from 'react';
import { Center, Group } from '@mantine/core';
import { useHotkeys } from '@mantine/hooks';
import { motion, AnimatePresence, LayoutGroup } from 'framer-motion';
import { RiArrowUpSLine, RiDiscLine, RiMore2Fill } from 'react-icons/ri';
import { generatePath, Link } from 'react-router-dom';
@ -12,6 +13,7 @@ import {
useSetFullScreenPlayerStore,
useFullScreenPlayerStore,
useSidebarStore,
useHotkeySettings,
} from '/@/renderer/store';
import { fadeIn } from '/@/renderer/styles';
import { LibraryItem } from '/@/renderer/api/types';
@ -91,6 +93,7 @@ export const LeftControls = () => {
const currentSong = useCurrentSong();
const title = currentSong?.name;
const artists = currentSong?.artists;
const { bindings } = useHotkeySettings();
const isSongDefined = Boolean(currentSong?.id);
@ -99,16 +102,23 @@ export const LeftControls = () => {
SONG_CONTEXT_MENU_ITEMS,
);
const handleToggleFullScreenPlayer = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
const handleToggleFullScreenPlayer = (e?: MouseEvent<HTMLDivElement> | KeyboardEvent) => {
e?.stopPropagation();
setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded });
};
const handleToggleSidebarImage = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
const handleToggleSidebarImage = (e?: MouseEvent<HTMLButtonElement>) => {
e?.stopPropagation();
setSideBar({ image: true });
};
useHotkeys([
[
bindings.toggleFullscreenPlayer.allowGlobal ? '' : bindings.toggleFullscreenPlayer.hotkey,
handleToggleFullScreenPlayer,
],
]);
return (
<LeftControlsContainer>
<LayoutGroup>

View file

@ -1,5 +1,6 @@
import { MouseEvent } from 'react';
import { Flex, Group } from '@mantine/core';
import { useHotkeys } from '@mantine/hooks';
import { HiOutlineQueueList } from 'react-icons/hi2';
import {
RiVolumeUpFill,
@ -12,6 +13,7 @@ import {
useAppStoreActions,
useCurrentServer,
useCurrentSong,
useHotkeySettings,
useMuted,
useSidebarStore,
useVolume,
@ -30,7 +32,9 @@ export const RightControls = () => {
const currentSong = useCurrentSong();
const { setSideBar } = useAppStoreActions();
const { rightExpanded: isQueueExpanded } = useSidebarStore();
const { handleVolumeSlider, handleVolumeWheel, handleMute } = useRightControls();
const { bindings } = useHotkeySettings();
const { handleVolumeSlider, handleVolumeWheel, handleMute, handleVolumeDown, handleVolumeUp } =
useRightControls();
const updateRatingMutation = useSetRating({});
const addToFavoritesMutation = useCreateFavorite({});
@ -94,9 +98,20 @@ export const RightControls = () => {
}
};
const handleToggleQueue = () => {
setSideBar({ rightExpanded: !isQueueExpanded });
};
const isSongDefined = Boolean(currentSong?.id);
const showRating = isSongDefined && server?.type === ServerType.NAVIDROME;
useHotkeys([
[bindings.volumeDown.isGlobal ? '' : bindings.volumeDown.hotkey, handleVolumeDown],
[bindings.volumeUp.isGlobal ? '' : bindings.volumeUp.hotkey, handleVolumeUp],
[bindings.volumeMute.isGlobal ? '' : bindings.volumeMute.hotkey, handleMute],
[bindings.toggleQueue.isGlobal ? '' : bindings.toggleQueue.hotkey, handleToggleQueue],
]);
return (
<Flex
align="flex-end"
@ -147,7 +162,7 @@ export const RightControls = () => {
icon={<HiOutlineQueueList size="1.1rem" />}
tooltip={{ label: 'View queue', openDelay: 500 }}
variant="secondary"
onClick={() => setSideBar({ rightExpanded: !isQueueExpanded })}
onClick={handleToggleQueue}
/>
<Group
noWrap

View file

@ -564,6 +564,14 @@ export const useCenterControls = (args: { playersRef: any }) => {
mpvPlayerListener.rendererQuit(() => {
handleQuit();
});
mpvPlayerListener.rendererToggleShuffle(() => {
handleToggleShuffle();
});
mpvPlayerListener.rendererToggleRepeat(() => {
handleToggleRepeat();
});
}
return () => {
@ -576,6 +584,8 @@ export const useCenterControls = (args: { playersRef: any }) => {
ipc?.removeAllListeners('renderer-player-current-time');
ipc?.removeAllListeners('renderer-player-auto-next');
ipc?.removeAllListeners('renderer-player-quit');
ipc?.removeAllListeners('renderer-player-toggle-shuffle');
ipc?.removeAllListeners('renderer-player-toggle-repeat');
};
}, [
autoNext,
@ -587,6 +597,8 @@ export const useCenterControls = (args: { playersRef: any }) => {
handlePrevTrack,
handleQuit,
handleStop,
handleToggleRepeat,
handleToggleShuffle,
isMpvPlayer,
next,
pause,
@ -684,6 +696,8 @@ export const useCenterControls = (args: { playersRef: any }) => {
return {
handleNextTrack,
handlePause,
handlePlay,
handlePlayPause,
handlePrevTrack,
handleSeekSlider,

View file

@ -1,9 +1,35 @@
import { useEffect, WheelEvent } from 'react';
import { useCallback, useEffect, WheelEvent } from 'react';
import isElectron from 'is-electron';
import { useMuted, usePlayerControls, useVolume } from '/@/renderer/store';
import { useGeneralSettings } from '/@/renderer/store/settings.store';
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const mpvPlayerListener = isElectron() ? window.electron.mpvPlayerListener : null;
const ipc = isElectron() ? window.electron.ipc : null;
const calculateVolumeUp = (volume: number, volumeWheelStep: number) => {
let volumeToSet;
const newVolumeGreaterThanHundred = volume + volumeWheelStep > 100;
if (newVolumeGreaterThanHundred) {
volumeToSet = 100;
} else {
volumeToSet = volume + volumeWheelStep;
}
return volumeToSet;
};
const calculateVolumeDown = (volume: number, volumeWheelStep: number) => {
let volumeToSet;
const newVolumeLessThanZero = volume - volumeWheelStep < 0;
if (newVolumeLessThanZero) {
volumeToSet = 0;
} else {
volumeToSet = volume - volumeWheelStep;
}
return volumeToSet;
};
export const useRightControls = () => {
const { setVolume, setMuted } = usePlayerControls();
@ -33,37 +59,66 @@ export const useRightControls = () => {
setVolume(e);
};
const handleVolumeWheel = (e: WheelEvent<HTMLDivElement>) => {
const handleVolumeDown = useCallback(() => {
const volumeToSet = calculateVolumeDown(volume, volumeWheelStep);
mpvPlayer?.volume(volumeToSet);
setVolume(volumeToSet);
}, [setVolume, volume, volumeWheelStep]);
const handleVolumeUp = useCallback(() => {
const volumeToSet = calculateVolumeUp(volume, volumeWheelStep);
mpvPlayer?.volume(volumeToSet);
setVolume(volumeToSet);
}, [setVolume, volume, volumeWheelStep]);
const handleVolumeWheel = useCallback(
(e: WheelEvent<HTMLDivElement>) => {
let volumeToSet;
if (e.deltaY > 0) {
const newVolumeLessThanZero = volume - volumeWheelStep < 0;
if (newVolumeLessThanZero) {
volumeToSet = 0;
volumeToSet = calculateVolumeDown(volume, volumeWheelStep);
} else {
volumeToSet = volume - volumeWheelStep;
}
} else {
const newVolumeGreaterThanHundred = volume + volumeWheelStep > 100;
if (newVolumeGreaterThanHundred) {
volumeToSet = 100;
} else {
volumeToSet = volume + volumeWheelStep;
}
volumeToSet = calculateVolumeUp(volume, volumeWheelStep);
}
mpvPlayer?.volume(volumeToSet);
setVolume(volumeToSet);
};
},
[setVolume, volume, volumeWheelStep],
);
const handleMute = () => {
const handleMute = useCallback(() => {
setMuted(!muted);
mpvPlayer?.mute();
}, [muted, setMuted]);
useEffect(() => {
if (isElectron()) {
mpvPlayerListener?.rendererVolumeMute(() => {
handleMute();
});
mpvPlayerListener?.rendererVolumeUp(() => {
handleVolumeUp();
});
mpvPlayerListener?.rendererVolumeDown(() => {
handleVolumeDown();
});
}
return () => {
ipc?.removeAllListeners('renderer-player-volume-mute');
ipc?.removeAllListeners('renderer-player-volume-up');
ipc?.removeAllListeners('renderer-player-volume-down');
};
}, [handleMute, handleVolumeDown, handleVolumeUp]);
return {
handleMute,
handleVolumeDown,
handleVolumeSlider,
handleVolumeSliderState,
handleVolumeUp,
handleVolumeWheel,
};
};