New: Add support for left/right arrows on the movie details to navigate through movies

New: Add support for ctrl+home and ctrl+end to jump to the top and bottom of the movie index
New: Add redirect to previous movie instead of index when deleting one
pull/2/head
nitsua 4 years ago committed by Qstick
parent 870a39278c
commit 7f814a3cb9

@ -20,18 +20,26 @@ function getShortcuts() {
} }
function getShortcutKey(combo, isOsx) { function getShortcutKey(combo, isOsx) {
const comboMatch = combo.match(/(.+?)\+(.)/); const comboMatch = combo.match(/(.+?)\+(.*)/);
if (!comboMatch) { if (!comboMatch) {
return combo; return combo;
} }
const modifier = comboMatch[1]; const modifier = comboMatch[1];
const key = comboMatch[2]; let key = comboMatch[2];
let osModifier = modifier; let osModifier = modifier;
if (modifier === 'mod') { 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}`; return `${osModifier} + ${key}`;

@ -1,31 +1,52 @@
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import React, { Component } from 'react'; import React, { Component } from 'react';
import getDisplayName from 'Helpers/getDisplayName'; import getDisplayName from 'Helpers/getDisplayName';
import translate from 'Utilities/String/translate';
export const shortcuts = { export const shortcuts = {
OPEN_KEYBOARD_SHORTCUTS_MODAL: { OPEN_KEYBOARD_SHORTCUTS_MODAL: {
key: '?', key: '?',
name: 'Open This Modal' name: translate('OpenThisModal')
}, },
CLOSE_MODAL: { CLOSE_MODAL: {
key: 'Esc', key: 'Esc',
name: 'Close Current Modal' name: translate('CloseCurrentModal')
}, },
ACCEPT_CONFIRM_MODAL: { ACCEPT_CONFIRM_MODAL: {
key: 'Enter', key: 'Enter',
name: 'Accept Confirmation Modal' name: translate('AcceptConfirmationModal')
}, },
MOVIE_SEARCH_INPUT: { MOVIE_SEARCH_INPUT: {
key: 's', key: 's',
name: 'Focus Search Box' name: translate('FocusSearchBox')
}, },
SAVE_SETTINGS: { SAVE_SETTINGS: {
key: 'mod+s', 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')
} }
}; };

@ -8,6 +8,7 @@ function DeleteMovieModal(props) {
const { const {
isOpen, isOpen,
onModalClose, onModalClose,
previousMovie,
...otherProps ...otherProps
} = props; } = props;
@ -20,6 +21,7 @@ function DeleteMovieModal(props) {
<DeleteMovieModalContentConnector <DeleteMovieModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
previousMovie={previousMovie}
/> />
</Modal> </Modal>
); );
@ -27,7 +29,8 @@ function DeleteMovieModal(props) {
DeleteMovieModal.propTypes = { DeleteMovieModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired,
previousMovie: PropTypes.string
}; };
export default DeleteMovieModal; export default DeleteMovieModal;

@ -1,3 +1,4 @@
import { push } from 'connected-react-router';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -16,7 +17,8 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
deleteMovie deleteMovie,
push
}; };
class DeleteMovieModalContentConnector extends Component { class DeleteMovieModalContentConnector extends Component {
@ -32,6 +34,10 @@ class DeleteMovieModalContentConnector extends Component {
}); });
this.props.onModalClose(true); this.props.onModalClose(true);
if (this.props.previousMovie) {
this.props.push(this.props.previousMovie);
}
} }
// //
@ -50,7 +56,9 @@ class DeleteMovieModalContentConnector extends Component {
DeleteMovieModalContentConnector.propTypes = { DeleteMovieModalContentConnector.propTypes = {
movieId: PropTypes.number.isRequired, movieId: PropTypes.number.isRequired,
onModalClose: PropTypes.func.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); export default connect(createMapStateToProps, mapDispatchToProps)(DeleteMovieModalContentConnector);

@ -30,6 +30,7 @@ import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector'; import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import fonts from 'Styles/Variables/fonts'; import fonts from 'Styles/Variables/fonts';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import formatRuntime from 'Utilities/Date/formatRuntime'; import formatRuntime from 'Utilities/Date/formatRuntime';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
@ -91,6 +92,7 @@ class MovieDetails extends Component {
window.addEventListener('touchend', this.onTouchEnd); window.addEventListener('touchend', this.onTouchEnd);
window.addEventListener('touchcancel', this.onTouchCancel); window.addEventListener('touchcancel', this.onTouchCancel);
window.addEventListener('touchmove', this.onTouchMove); window.addEventListener('touchmove', this.onTouchMove);
window.addEventListener('keyup', this.onKeyUp);
} }
componentWillUnmount() { componentWillUnmount() {
@ -173,6 +175,15 @@ class MovieDetails extends Component {
this.setState({ titleWidth: width }); 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) => { onTouchStart = (event) => {
const touches = event.touches; const touches = event.touches;
const touchStart = touches[0].pageX; const touchStart = touches[0].pageX;
@ -716,6 +727,7 @@ class MovieDetails extends Component {
isOpen={isDeleteMovieModalOpen} isOpen={isDeleteMovieModalOpen}
movieId={id} movieId={id}
onModalClose={this.onDeleteMovieModalClose} onModalClose={this.onDeleteMovieModalClose}
previousMovie={`/movie/${previousMovie.titleSlug}`}
/> />
<InteractiveImportModal <InteractiveImportModal

@ -16,6 +16,7 @@ import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import MovieEditorFooter from 'Movie/Editor/MovieEditorFooter.js'; import MovieEditorFooter from 'Movie/Editor/MovieEditorFooter.js';
import OrganizeMovieModal from 'Movie/Editor/Organize/OrganizeMovieModal'; import OrganizeMovieModal from 'Movie/Editor/Organize/OrganizeMovieModal';
import NoMovie from 'Movie/NoMovie'; import NoMovie from 'Movie/NoMovie';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
@ -75,6 +76,8 @@ class MovieIndex extends Component {
componentDidMount() { componentDidMount() {
this.setJumpBarItems(); this.setJumpBarItems();
this.setSelectedState(); this.setSelectedState();
window.addEventListener('keyup', this.onKeyUp);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -241,6 +244,18 @@ class MovieIndex extends Component {
this.setState({ jumpToCharacter }); this.setState({ jumpToCharacter });
} }
onKeyUp = (event) => {
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 }) => { onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value)); this.setState(selectAll(this.state.selectedState, value));
} }

@ -3,5 +3,9 @@ export const ENTER = 13;
export const SHIFT = 16; export const SHIFT = 16;
export const CONTROL = 17; export const CONTROL = 17;
export const ESCAPE = 27; export const ESCAPE = 27;
export const HOME = 36;
export const END = 35;
export const UP_ARROW = 38; export const UP_ARROW = 38;
export const DOWN_ARROW = 40; export const DOWN_ARROW = 40;
export const LEFT_ARROW = 37;
export const RIGHT_ARROW = 39;

@ -1,5 +1,6 @@
{ {
"About": "About", "About": "About",
"AcceptConfirmationModal": "Accept Confirmation Modal",
"Actions": "Actions", "Actions": "Actions",
"Activity": "Activity", "Activity": "Activity",
"Added": "Added", "Added": "Added",
@ -96,6 +97,7 @@
"CloneIndexer": "Clone Indexer", "CloneIndexer": "Clone Indexer",
"CloneProfile": "Clone Profile", "CloneProfile": "Clone Profile",
"Close": "Close", "Close": "Close",
"CloseCurrentModal": "Close Current Modal",
"Collection": "Collection", "Collection": "Collection",
"ColonReplacement": "Colon Replacement", "ColonReplacement": "Colon Replacement",
"ColonReplacementFormatHelpText": "Change how Radarr handles colon replacement", "ColonReplacementFormatHelpText": "Change how Radarr handles colon replacement",
@ -251,6 +253,7 @@
"FilterPlaceHolder": "Search movies", "FilterPlaceHolder": "Search movies",
"FirstDayOfWeek": "First Day of Week", "FirstDayOfWeek": "First Day of Week",
"Fixed": "Fixed", "Fixed": "Fixed",
"FocusSearchBox": "Focus Search Box",
"Folder": "Folder", "Folder": "Folder",
"Folders": "Folders", "Folders": "Folders",
"FollowPerson": "Follow Person", "FollowPerson": "Follow Person",
@ -436,6 +439,10 @@
"MovieNaming": "Movie Naming", "MovieNaming": "Movie Naming",
"Movies": "Movies", "Movies": "Movies",
"MoviesSelectedInterp": "{0} Movie(s) Selected", "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", "MovieTitle": "Movie Title",
"MovieTitleHelpText": "The title of the movie to exclude (can be anything meaningful)", "MovieTitleHelpText": "The title of the movie to exclude (can be anything meaningful)",
"MovieYear": "Movie Year", "MovieYear": "Movie Year",
@ -469,6 +476,7 @@
"OnRenameHelpText": "On Rename", "OnRenameHelpText": "On Rename",
"OnUpgradeHelpText": "Be notified when movies are upgraded to a better quality", "OnUpgradeHelpText": "Be notified when movies are upgraded to a better quality",
"OpenBrowserOnStart": "Open browser on start", "OpenBrowserOnStart": "Open browser on start",
"OpenThisModal": "Open This Modal",
"Options": "Options", "Options": "Options",
"Organize": "Organize", "Organize": "Organize",
"OrganizeAndRename": "Organize & Rename", "OrganizeAndRename": "Organize & Rename",
@ -615,6 +623,7 @@
"Runtime": "Runtime", "Runtime": "Runtime",
"Save": "Save", "Save": "Save",
"SaveChanges": "Save Changes", "SaveChanges": "Save Changes",
"SaveSettings": "Save Settings",
"Scheduled": "Scheduled", "Scheduled": "Scheduled",
"ScriptPath": "Script Path", "ScriptPath": "Script Path",
"Search": "Search", "Search": "Search",

Loading…
Cancel
Save