diff --git a/ui/src/album/AlbumList.js b/ui/src/album/AlbumList.js index dd90fce8..10ed0860 100644 --- a/ui/src/album/AlbumList.js +++ b/ui/src/album/AlbumList.js @@ -17,7 +17,7 @@ import { List, QuickFilter, Title, useAlbumsPerPage } from '../common' import AlbumListActions from './AlbumListActions' import AlbumListView from './AlbumListView' import AlbumGridView from './AlbumGridView' -import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' +import { AddToPlaylistDialog } from '../dialogs' import albumLists, { defaultAlbumList } from './albumLists' const AlbumFilter = (props) => { diff --git a/ui/src/album/AlbumSongs.js b/ui/src/album/AlbumSongs.js index bb34f658..8bcac53a 100644 --- a/ui/src/album/AlbumSongs.js +++ b/ui/src/album/AlbumSongs.js @@ -20,7 +20,7 @@ import { SongDetails, SongTitleField, } from '../common' -import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' +import { AddToPlaylistDialog } from '../dialogs' const useStyles = makeStyles( (theme) => ({ diff --git a/ui/src/artist/ArtistList.js b/ui/src/artist/ArtistList.js index 20600a79..1e59bc45 100644 --- a/ui/src/artist/ArtistList.js +++ b/ui/src/artist/ArtistList.js @@ -10,7 +10,7 @@ import { import { useMediaQuery, withWidth } from '@material-ui/core' import StarIcon from '@material-ui/icons/Star' import StarBorderIcon from '@material-ui/icons/StarBorder' -import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' +import { AddToPlaylistDialog } from '../dialogs' import { ArtistContextMenu, List, diff --git a/ui/src/layout/AboutDialog.js b/ui/src/dialogs/AboutDialog.js similarity index 74% rename from ui/src/layout/AboutDialog.js rename to ui/src/dialogs/AboutDialog.js index 00e59caf..fa2be42c 100644 --- a/ui/src/layout/AboutDialog.js +++ b/ui/src/dialogs/AboutDialog.js @@ -1,13 +1,8 @@ import React from 'react' import PropTypes from 'prop-types' -import { withStyles } from '@material-ui/core/styles' import Link from '@material-ui/core/Link' import Dialog from '@material-ui/core/Dialog' -import MuiDialogTitle from '@material-ui/core/DialogTitle' -import MuiDialogContent from '@material-ui/core/DialogContent' import IconButton from '@material-ui/core/IconButton' -import CloseIcon from '@material-ui/icons/Close' -import Typography from '@material-ui/core/Typography' import TableContainer from '@material-ui/core/TableContainer' import Table from '@material-ui/core/Table' import TableBody from '@material-ui/core/TableBody' @@ -18,19 +13,8 @@ import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder' import inflection from 'inflection' import { useTranslate } from 'react-admin' import config from '../config' - -const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), - }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, -}) +import { DialogTitle } from './DialogTitle' +import { DialogContent } from './DialogContent' const links = { homepage: 'navidrome.org', @@ -41,30 +25,6 @@ const links = { featureRequests: 'github.com/deluan/navidrome/issues', } -const DialogTitle = withStyles(styles)((props) => { - const { children, classes, onClose, ...other } = props - return ( - - {children} - {onClose ? ( - - - - ) : null} - - ) -}) - -const DialogContent = withStyles((theme) => ({ - root: { - padding: theme.spacing(2), - }, -}))(MuiDialogContent) - const AboutDialog = ({ open, onClose }) => { const translate = useTranslate() return ( @@ -79,7 +39,7 @@ const AboutDialog = ({ open, onClose }) => { - +
@@ -143,4 +103,4 @@ AboutDialog.propTypes = { onClose: PropTypes.func.isRequired, } -export default AboutDialog +export { AboutDialog } diff --git a/ui/src/dialogs/AddToPlaylistDialog.js b/ui/src/dialogs/AddToPlaylistDialog.js index de0d9a44..00dbec9b 100644 --- a/ui/src/dialogs/AddToPlaylistDialog.js +++ b/ui/src/dialogs/AddToPlaylistDialog.js @@ -14,9 +14,9 @@ import { DialogTitle, } from '@material-ui/core' import { closeAddToPlaylist } from '../actions' -import SelectPlaylistInput from './SelectPlaylistInput' +import { SelectPlaylistInput } from './SelectPlaylistInput' -const AddToPlaylistDialog = () => { +export const AddToPlaylistDialog = () => { const { open, selectedIds, onSuccess } = useSelector( (state) => state.addToPlaylistDialog ) @@ -96,5 +96,3 @@ const AddToPlaylistDialog = () => { ) } - -export default AddToPlaylistDialog diff --git a/ui/src/dialogs/DialogContent.js b/ui/src/dialogs/DialogContent.js new file mode 100644 index 00000000..cc650611 --- /dev/null +++ b/ui/src/dialogs/DialogContent.js @@ -0,0 +1,8 @@ +import { withStyles } from '@material-ui/core/styles' +import MuiDialogContent from '@material-ui/core/DialogContent' + +export const DialogContent = withStyles((theme) => ({ + root: { + padding: theme.spacing(2), + }, +}))(MuiDialogContent) diff --git a/ui/src/dialogs/DialogTitle.js b/ui/src/dialogs/DialogTitle.js new file mode 100644 index 00000000..959031e7 --- /dev/null +++ b/ui/src/dialogs/DialogTitle.js @@ -0,0 +1,35 @@ +import { withStyles } from '@material-ui/core/styles' +import MuiDialogTitle from '@material-ui/core/DialogTitle' +import Typography from '@material-ui/core/Typography' +import IconButton from '@material-ui/core/IconButton' +import CloseIcon from '@material-ui/icons/Close' +import React from 'react' + +const styles = (theme) => ({ + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, +}) + +export const DialogTitle = withStyles(styles)((props) => { + const { children, classes, onClose, ...other } = props + return ( + + {children} + + + + + ) +}) diff --git a/ui/src/dialogs/HelpDialog.js b/ui/src/dialogs/HelpDialog.js new file mode 100644 index 00000000..0e7621fe --- /dev/null +++ b/ui/src/dialogs/HelpDialog.js @@ -0,0 +1,78 @@ +import React, { useCallback, useState } from 'react' +import ReactDOM from 'react-dom' +import { Dialog } from '@material-ui/core' +import { getApplicationKeyMap, GlobalHotKeys } from 'react-hotkeys' +import TableContainer from '@material-ui/core/TableContainer' +import Paper from '@material-ui/core/Paper' +import Table from '@material-ui/core/Table' +import TableBody from '@material-ui/core/TableBody' +import TableRow from '@material-ui/core/TableRow' +import TableCell from '@material-ui/core/TableCell' +import { useTranslate } from 'react-admin' +import inflection from 'inflection' +import { keyMap } from '../hotkeys' +import { DialogTitle } from './DialogTitle' +import { DialogContent } from './DialogContent' + +const HelpTable = (props) => { + const keyMap = getApplicationKeyMap() + const translate = useTranslate() + return ReactDOM.createPortal( + + + {translate('help.title')} + + + +
+ + {Object.keys(keyMap).map((key) => { + const { sequences, name } = keyMap[key] + const description = translate(`help.hotkeys.${name}`, { + _: inflection.humanize(name), + }) + return ( + + + {description} + + + {sequences.map(({ sequence }) => ( + {sequence} + ))} + + + ) + })} + +
+
+
+ , + document.body + ) +} + +export const HelpDialog = (props) => { + const [open, setOpen] = useState(false) + + const handleClickClose = (e) => { + setOpen(false) + e.stopPropagation() + } + + const handlers = { + SHOW_HELP: useCallback(() => setOpen(true), [setOpen]), + } + + return ( + <> + + + + ) +} diff --git a/ui/src/dialogs/SelectPlaylistInput.js b/ui/src/dialogs/SelectPlaylistInput.js index 2e4e5312..97569484 100644 --- a/ui/src/dialogs/SelectPlaylistInput.js +++ b/ui/src/dialogs/SelectPlaylistInput.js @@ -6,11 +6,11 @@ import Autocomplete, { } from '@material-ui/lab/Autocomplete' import { useGetList, useTranslate } from 'react-admin' import PropTypes from 'prop-types' -import { isWritable } from '../common/Writable' +import { isWritable } from '../common' const filter = createFilterOptions() -const SelectPlaylistInput = ({ onChange }) => { +export const SelectPlaylistInput = ({ onChange }) => { const translate = useTranslate() const { ids, data } = useGetList( 'playlist', @@ -94,5 +94,3 @@ const SelectPlaylistInput = ({ onChange }) => { SelectPlaylistInput.propTypes = { onChange: PropTypes.func.isRequired, } - -export default SelectPlaylistInput diff --git a/ui/src/dialogs/index.js b/ui/src/dialogs/index.js new file mode 100644 index 00000000..79c43130 --- /dev/null +++ b/ui/src/dialogs/index.js @@ -0,0 +1,4 @@ +export * from './AboutDialog' +export * from './AddToPlaylistDialog' +export * from './SelectPlaylistInput' +export * from './HelpDialog' diff --git a/ui/src/hotkeys.js b/ui/src/hotkeys.js index 5086afa5..8ad02e06 100644 --- a/ui/src/hotkeys.js +++ b/ui/src/hotkeys.js @@ -1,12 +1,12 @@ const keyMap = { - TOGGLE_MENU: { name: 'Toggle Menu Side Bar', sequence: 'm', group: 'Global' }, - - TOGGLE_PLAY: { name: 'Play / Pause', sequence: 'p', group: 'Player' }, - PREV_SONG: { name: 'Previous Songs', sequence: 'left', group: 'Player' }, - NEXT_SONG: { name: 'Next Song', sequence: 'right', group: 'Player' }, - VOL_UP: { name: 'Volume Up', sequence: '=', group: 'Player' }, - VOL_DOWN: { name: 'Volume Down', sequence: '-', group: 'Player' }, - TOGGLE_STAR: { name: 'Toggle Star', sequence: 's', group: 'Player' }, + SHOW_HELP: { name: 'show_help', sequence: 'shift+?', group: 'Global' }, + TOGGLE_MENU: { name: 'toggle_menu', sequence: 'm', group: 'Global' }, + TOGGLE_PLAY: { name: 'toggle_play', sequence: 'p', group: 'Player' }, + PREV_SONG: { name: 'prev_song', sequence: 'left', group: 'Player' }, + NEXT_SONG: { name: 'next_song', sequence: 'right', group: 'Player' }, + VOL_UP: { name: 'vol_up', sequence: '=', group: 'Player' }, + VOL_DOWN: { name: 'vol_down', sequence: '-', group: 'Player' }, + TOGGLE_STAR: { name: 'toggle_star', sequence: 's', group: 'Player' }, } export { keyMap } diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index aefac515..b01377d3 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -324,5 +324,18 @@ "fullScan": "Full Scan", "serverUptime": "Server Uptime", "serverDown": "OFFLINE" + }, + "help": { + "title": "Navidrome Hotkeys", + "hotkeys": { + "show_help": "Show This Help", + "toggle_menu": "Toggle Menu Side Bar", + "toggle_play": "Play / Pause", + "prev_song": "Previous Song", + "next_song": "Next Song", + "vol_up": "Volume Up", + "vol_down": "Volume Down", + "toggle_star": "Toggle Current Song's Star" + } } } diff --git a/ui/src/layout/AppBar.js b/ui/src/layout/AppBar.js index 353fbd52..ccbc9402 100644 --- a/ui/src/layout/AppBar.js +++ b/ui/src/layout/AppBar.js @@ -10,7 +10,7 @@ import { useSelector } from 'react-redux' import { makeStyles, MenuItem, ListItemIcon, Divider } from '@material-ui/core' import ViewListIcon from '@material-ui/icons/ViewList' import InfoIcon from '@material-ui/icons/Info' -import AboutDialog from './AboutDialog' +import { AboutDialog } from '../dialogs' import PersonalMenu from './PersonalMenu' import ActivityPanel from './ActivityPanel' import UserMenu from './UserMenu' diff --git a/ui/src/layout/Menu.js b/ui/src/layout/Menu.js index 2ef57cff..9aae9c05 100644 --- a/ui/src/layout/Menu.js +++ b/ui/src/layout/Menu.js @@ -9,6 +9,7 @@ import AlbumIcon from '@material-ui/icons/Album' import SubMenu from './SubMenu' import inflection from 'inflection' import albumLists from '../album/albumLists' +import { HelpDialog } from '../dialogs' const translatedResourceName = (resource, translate) => translate(`resources.${resource.name}.name`, { @@ -108,6 +109,7 @@ const Menu = ({ onMenuClick, dense, logout }) => { {resources.filter(subItems(undefined)).map(renderResourceMenuItemLink)} {isXsmall && logout} + ) } diff --git a/ui/src/playlist/PlaylistSongs.js b/ui/src/playlist/PlaylistSongs.js index 2be7f823..9bf302cd 100644 --- a/ui/src/playlist/PlaylistSongs.js +++ b/ui/src/playlist/PlaylistSongs.js @@ -21,7 +21,7 @@ import { SongDatagrid, SongTitleField, } from '../common' -import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' +import { AddToPlaylistDialog } from '../dialogs' import { AlbumLinkField } from '../song/AlbumLinkField' import { playTracks } from '../actions' import PlaylistSongBulkActions from './PlaylistSongBulkActions' diff --git a/ui/src/song/SongList.js b/ui/src/song/SongList.js index f4c8b32a..59dec1f0 100644 --- a/ui/src/song/SongList.js +++ b/ui/src/song/SongList.js @@ -23,7 +23,7 @@ import { setTrack } from '../actions' import { SongBulkActions } from '../common' import { SongListActions } from './SongListActions' import { AlbumLinkField } from './AlbumLinkField' -import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' +import { AddToPlaylistDialog } from '../dialogs' import { makeStyles } from '@material-ui/core/styles' import StarBorderIcon from '@material-ui/icons/StarBorder'