diff --git a/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.js b/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.js
index 5918479e8..1dc4cc159 100644
--- a/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.js
+++ b/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.js
@@ -20,18 +20,26 @@ function getShortcuts() {
}
function getShortcutKey(combo, isOsx) {
- const comboMatch = combo.match(/(.+?)\+(.)/);
+ const comboMatch = combo.match(/(.+?)\+(.*)/);
if (!comboMatch) {
return combo;
}
const modifier = comboMatch[1];
- const key = comboMatch[2];
+ let key = comboMatch[2];
let osModifier = modifier;
if (modifier === 'mod') {
- osModifier = isOsx ? 'cmd' : 'ctrl';
+ osModifier = isOsx ? 'cmd' : 'Ctrl';
+ }
+
+ if (key === 'home') {
+ key = isOsx ? '↑' : 'Home';
+ }
+
+ if (key === 'end') {
+ key = isOsx ? '↓' : 'End';
}
return `${osModifier} + ${key}`;
diff --git a/frontend/src/Components/keyboardShortcuts.js b/frontend/src/Components/keyboardShortcuts.js
index d2609aa58..ebf4c9e49 100644
--- a/frontend/src/Components/keyboardShortcuts.js
+++ b/frontend/src/Components/keyboardShortcuts.js
@@ -1,31 +1,52 @@
import Mousetrap from 'mousetrap';
import React, { Component } from 'react';
import getDisplayName from 'Helpers/getDisplayName';
+import translate from 'Utilities/String/translate';
export const shortcuts = {
OPEN_KEYBOARD_SHORTCUTS_MODAL: {
key: '?',
- name: 'Open This Modal'
+ name: translate('OpenThisModal')
},
CLOSE_MODAL: {
key: 'Esc',
- name: 'Close Current Modal'
+ name: translate('CloseCurrentModal')
},
ACCEPT_CONFIRM_MODAL: {
key: 'Enter',
- name: 'Accept Confirmation Modal'
+ name: translate('AcceptConfirmationModal')
},
MOVIE_SEARCH_INPUT: {
key: 's',
- name: 'Focus Search Box'
+ name: translate('FocusSearchBox')
},
SAVE_SETTINGS: {
key: 'mod+s',
- name: 'Save Settings'
+ name: translate('SaveSettings')
+ },
+
+ SCROLL_TOP: {
+ key: 'mod+home',
+ name: translate('MovieIndexScrollTop')
+ },
+
+ SCROLL_BOTTOM: {
+ key: 'mod+end',
+ name: translate('MovieIndexScrollBottom')
+ },
+
+ DETAILS_NEXT: {
+ key: '→',
+ name: translate('MovieDetailsNextMovie')
+ },
+
+ DETAILS_PREVIOUS: {
+ key: '←',
+ name: translate('MovieDetailsPreviousMovie')
}
};
diff --git a/frontend/src/Movie/Delete/DeleteMovieModal.js b/frontend/src/Movie/Delete/DeleteMovieModal.js
index 51dd5bb53..dc4e1de04 100644
--- a/frontend/src/Movie/Delete/DeleteMovieModal.js
+++ b/frontend/src/Movie/Delete/DeleteMovieModal.js
@@ -8,6 +8,7 @@ function DeleteMovieModal(props) {
const {
isOpen,
onModalClose,
+ previousMovie,
...otherProps
} = props;
@@ -20,6 +21,7 @@ function DeleteMovieModal(props) {
);
@@ -27,7 +29,8 @@ function DeleteMovieModal(props) {
DeleteMovieModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
- onModalClose: PropTypes.func.isRequired
+ onModalClose: PropTypes.func.isRequired,
+ previousMovie: PropTypes.string
};
export default DeleteMovieModal;
diff --git a/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js b/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js
index 8aef87c2b..e93b7daf4 100644
--- a/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js
+++ b/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js
@@ -1,3 +1,4 @@
+import { push } from 'connected-react-router';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@@ -16,7 +17,8 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
- deleteMovie
+ deleteMovie,
+ push
};
class DeleteMovieModalContentConnector extends Component {
@@ -32,6 +34,10 @@ class DeleteMovieModalContentConnector extends Component {
});
this.props.onModalClose(true);
+
+ if (this.props.previousMovie) {
+ this.props.push(this.props.previousMovie);
+ }
}
//
@@ -50,7 +56,9 @@ class DeleteMovieModalContentConnector extends Component {
DeleteMovieModalContentConnector.propTypes = {
movieId: PropTypes.number.isRequired,
onModalClose: PropTypes.func.isRequired,
- deleteMovie: PropTypes.func.isRequired
+ deleteMovie: PropTypes.func.isRequired,
+ push: PropTypes.func.isRequired,
+ previousMovie: PropTypes.string
};
export default connect(createMapStateToProps, mapDispatchToProps)(DeleteMovieModalContentConnector);
diff --git a/frontend/src/Movie/Details/MovieDetails.js b/frontend/src/Movie/Details/MovieDetails.js
index 77ace6349..35d6894e5 100644
--- a/frontend/src/Movie/Details/MovieDetails.js
+++ b/frontend/src/Movie/Details/MovieDetails.js
@@ -30,6 +30,7 @@ import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import fonts from 'Styles/Variables/fonts';
+import * as keyCodes from 'Utilities/Constants/keyCodes';
import formatRuntime from 'Utilities/Date/formatRuntime';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
@@ -91,6 +92,7 @@ class MovieDetails extends Component {
window.addEventListener('touchend', this.onTouchEnd);
window.addEventListener('touchcancel', this.onTouchCancel);
window.addEventListener('touchmove', this.onTouchMove);
+ window.addEventListener('keyup', this.onKeyUp);
}
componentWillUnmount() {
@@ -173,6 +175,15 @@ class MovieDetails extends Component {
this.setState({ titleWidth: width });
}
+ onKeyUp = (event) => {
+ if (event.keyCode === keyCodes.LEFT_ARROW) {
+ this.props.onGoToMovie(this.props.previousMovie.titleSlug);
+ }
+ if (event.keyCode === keyCodes.RIGHT_ARROW) {
+ this.props.onGoToMovie(this.props.nextMovie.titleSlug);
+ }
+ }
+
onTouchStart = (event) => {
const touches = event.touches;
const touchStart = touches[0].pageX;
@@ -716,6 +727,7 @@ class MovieDetails extends Component {
isOpen={isDeleteMovieModalOpen}
movieId={id}
onModalClose={this.onDeleteMovieModalClose}
+ previousMovie={`/movie/${previousMovie.titleSlug}`}
/>
{
+ const jumpBarItems = this.state.jumpBarItems.order;
+ if (event.path.length === 4) {
+ if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
+ this.setState({ jumpToCharacter: jumpBarItems[0] });
+ }
+ if (event.keyCode === keyCodes.END && event.ctrlKey) {
+ this.setState({ jumpToCharacter: jumpBarItems[jumpBarItems.length - 1] });
+ }
+ }
+ }
+
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
diff --git a/frontend/src/Utilities/Constants/keyCodes.js b/frontend/src/Utilities/Constants/keyCodes.js
index 9285b10fe..8ca1195f9 100644
--- a/frontend/src/Utilities/Constants/keyCodes.js
+++ b/frontend/src/Utilities/Constants/keyCodes.js
@@ -3,5 +3,9 @@ export const ENTER = 13;
export const SHIFT = 16;
export const CONTROL = 17;
export const ESCAPE = 27;
+export const HOME = 36;
+export const END = 35;
export const UP_ARROW = 38;
export const DOWN_ARROW = 40;
+export const LEFT_ARROW = 37;
+export const RIGHT_ARROW = 39;
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index a6114d260..77544fe49 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -1,5 +1,6 @@
{
"About": "About",
+ "AcceptConfirmationModal": "Accept Confirmation Modal",
"Actions": "Actions",
"Activity": "Activity",
"Added": "Added",
@@ -96,6 +97,7 @@
"CloneIndexer": "Clone Indexer",
"CloneProfile": "Clone Profile",
"Close": "Close",
+ "CloseCurrentModal": "Close Current Modal",
"Collection": "Collection",
"ColonReplacement": "Colon Replacement",
"ColonReplacementFormatHelpText": "Change how Radarr handles colon replacement",
@@ -251,6 +253,7 @@
"FilterPlaceHolder": "Search movies",
"FirstDayOfWeek": "First Day of Week",
"Fixed": "Fixed",
+ "FocusSearchBox": "Focus Search Box",
"Folder": "Folder",
"Folders": "Folders",
"FollowPerson": "Follow Person",
@@ -436,6 +439,10 @@
"MovieNaming": "Movie Naming",
"Movies": "Movies",
"MoviesSelectedInterp": "{0} Movie(s) Selected",
+ "MovieDetailsNextMovie": "Movie Details: Next Movie",
+ "MovieDetailsPreviousMovie": "Movie Details: Previous Movie",
+ "MovieIndexScrollBottom": "Movie Index: Scroll Bottom",
+ "MovieIndexScrollTop": "Movie Index: Scroll Top",
"MovieTitle": "Movie Title",
"MovieTitleHelpText": "The title of the movie to exclude (can be anything meaningful)",
"MovieYear": "Movie Year",
@@ -469,6 +476,7 @@
"OnRenameHelpText": "On Rename",
"OnUpgradeHelpText": "Be notified when movies are upgraded to a better quality",
"OpenBrowserOnStart": "Open browser on start",
+ "OpenThisModal": "Open This Modal",
"Options": "Options",
"Organize": "Organize",
"OrganizeAndRename": "Organize & Rename",
@@ -615,6 +623,7 @@
"Runtime": "Runtime",
"Save": "Save",
"SaveChanges": "Save Changes",
+ "SaveSettings": "Save Settings",
"Scheduled": "Scheduled",
"ScriptPath": "Script Path",
"Search": "Search",