Files
Iris/src/js/components/Hotkeys.js
2021-11-21 19:57:37 +13:00

245 lines
7.9 KiB
JavaScript
Executable File

import ReactGA from 'react-ga';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useHotkeys } from 'react-hotkeys-hook';
import { indexToArray, sortItems } from '../util/arrays';
import { collate } from '../util/format';
import * as uiActions from '../services/ui/actions';
import * as mopidyActions from '../services/mopidy/actions';
import * as snapcastActions from '../services/snapcast/actions';
const Hotkeys = () => {
const history = useHistory();
const dispatch = useDispatch();
const volume = useSelector((state) => state.mopidy.volume);
const mute = useSelector((state) => state.mopidy.mute);
const play_state = useSelector((state) => state.mopidy.play_state);
const play_time_position = useSelector((state) => parseInt(state.mopidy.time_position, 10));
const dragging = useSelector((state) => state.ui.dragger?.dragging);
const allow_reporting = useSelector((state) => state.ui.allow_reporting);
const snapcast_groups = useSelector((state) => state.snapcast.groups);
const snapcast_clients = useSelector((state) => state.snapcast.clients);
const show_disconnected_clients = useSelector((state) => state.ui?.snapcast_show_disconnected_clients);
const prepare = ({ e, label, callback }) => {
const {
target,
altKey,
ctrlKey,
metaKey,
shiftKey,
key,
} = e;
// Ignore buttons (spacebar triggers it); input fields are ignored by the plugin
if (target.nodeName === 'BUTTON' && key === ' ') return;
// Ignore when there are any key modifiers. This enables us to avoid interfering
// with browser- and OS-default functions.
if (altKey || ctrlKey || metaKey || shiftKey) {
return;
}
if (allow_reporting) {
ReactGA.event({ category: 'Hotkey', action: e.key, label });
}
e.preventDefault();
callback();
};
const getSnapcastGroup = (index) => {
const simpleGroups = indexToArray(snapcast_groups);
if (simpleGroups.length <= 0) return false;
const group = sortItems(simpleGroups, 'name')[index];
if (!group) return false;
return collate(group, { clients: snapcast_clients });
}
const setSnapcastVolume = (index, adjustment) => {
const group = getSnapcastGroup(index);
let { clients: groupClients } = group || {};
if (!groupClients) return false;
if (!show_disconnected_clients) {
groupClients = groupClients.filter((c) => c.connected);
}
const groupVolume = groupClients.reduce(
(acc, client) => acc + (client.volume || 0),
0,
) / groupClients.length;
if (group.mute) snapcastActions.setGroupMute(group.id, false);
dispatch(snapcastActions.setGroupVolume(group.id, groupVolume + adjustment, groupVolume));
dispatch(uiActions.createNotification({
content: adjustment > 0 ? 'volume_up' : 'volume_down',
title: group.name,
type: 'shortcut',
}));
};
const toggleSnapcastMute = (index) => {
const group = getSnapcastGroup(index);
const nextMute = group.mute !== true;
dispatch(snapcastActions.setGroupMute(group.id, nextMute));
dispatch(uiActions.createNotification({
content: nextMute ? 'volume_off' : 'volume_up',
title: group.name,
type: 'shortcut',
}));
};
useHotkeys('i', (e) => {
prepare({
e,
label: 'Hotkey info',
callback: () => {
history.push('/hotkeys');
},
});
});
useHotkeys('space,p', (e) => prepare({
e,
label: 'Play/pause',
callback: () => {
if (play_state === 'playing') {
dispatch(mopidyActions.pause());
dispatch(uiActions.createNotification({ content: 'pause', type: 'shortcut' }));
} else {
dispatch(mopidyActions.play());
dispatch(uiActions.createNotification({ content: 'play_arrow', type: 'shortcut' }));
}
},
}), {}, [play_state]);
useHotkeys('s', (e) => prepare({
e,
label: 'Stop',
callback: () => {
dispatch(mopidyActions.stop());
dispatch(uiActions.createNotification({ content: 'stop', type: 'shortcut' }));
},
}));
useHotkeys('r', (e) => prepare({
e,
label: 'Rewind',
callback: () => {
let new_position = play_time_position - 30000;
if (new_position < 0) {
new_position = 0;
}
dispatch(mopidyActions.setTimePosition(new_position));
dispatch(uiActions.createNotification({ content: 'fast_rewind', type: 'shortcut' }));
},
}), {}, [play_time_position]);
useHotkeys('f', (e) => prepare({
e,
label: 'Fastforward',
callback: () => {
dispatch(mopidyActions.setTimePosition(play_time_position + 30000));
dispatch(uiActions.createNotification({ content: 'fast_forward', type: 'shortcut' }));
},
}), {}, [play_time_position]);
useHotkeys(',', (e) => prepare({
e,
label: 'Previous',
callback: () => {
dispatch(mopidyActions.previous());
dispatch(uiActions.createNotification({ content: 'skip_previous', type: 'shortcut' }));
},
}));
useHotkeys('.', (e) => prepare({
e,
label: 'Next',
callback: () => {
dispatch(mopidyActions.next());
dispatch(uiActions.createNotification({ content: 'skip_next', type: 'shortcut' }));
},
}));
useHotkeys('=,1+=,2+=,3+=,4+=,5+=', (e, handler) => prepare({
e,
label: 'Volume up',
callback: () => {
if (handler.key === '=') {
if (volume !== 'false') {
dispatch(uiActions.createNotification({ content: 'volume_up', title: 'Master', type: 'shortcut' }));
let nextVolume = volume + 5;
if (nextVolume > 100) nextVolume = 100;
dispatch(mopidyActions.setVolume(nextVolume));
if (mute) {
dispatch(mopidyActions.setMute(false));
}
}
} else {
const index = parseInt(handler.key.replace('+='), 10);
setSnapcastVolume(index - 1, 5);
}
},
}), {}, [volume, mute, snapcast_groups]);
useHotkeys('-,1+-,2+-,3+-,4+-,5+-', (e, handler) => prepare({
e,
label: 'Volume down',
callback: () => {
if (handler.key === '-') {
if (volume !== 'false') {
dispatch(uiActions.createNotification({ content: 'volume_down', title: 'Master', type: 'shortcut' }));
let nextVolume = volume - 5;
if (nextVolume < 0) nextVolume = 0;
dispatch(mopidyActions.setVolume(nextVolume));
if (mute) {
dispatch(mopidyActions.setMute(false));
}
}
} else {
const index = parseInt(handler.key.replace('+-'), 10);
setSnapcastVolume(index - 1, -5);
}
},
}), {}, [volume, mute, snapcast_groups]);
useHotkeys('0,1+0,2+0,3+0,4+0,5+0', (e, handler) => prepare({
e,
label: 'Mute/unmute',
callback: () => {
if (handler.key === '0') {
if (mute) {
dispatch(mopidyActions.setMute(false));
dispatch(uiActions.createNotification({ content: 'volume_up', title: 'Master', type: 'shortcut' }));
} else {
dispatch(mopidyActions.setMute(true));
dispatch(uiActions.createNotification({ content: 'volume_off', title: 'Master', type: 'shortcut' }));
}
} else {
const index = parseInt(handler.key.replace('+0'), 10);
toggleSnapcastMute(index - 1);
}
},
}), {}, [volume, mute, snapcast_groups]);
useHotkeys('escape', (e) => prepare({
e,
label: 'Escape',
callback: () => {
if (dragging) {
dispatch(uiActions.dragEnd());
e.preventDefault();
} else if ($('body').hasClass('modal-open')) {
window.history.back();
e.preventDefault();
}
},
}), {}, [dragging]);
return null;
};
export default Hotkeys;