diff --git a/frontend/src/Components/Link/IconButton.js b/frontend/src/Components/Link/IconButton.js index 6f5e56d0e..fffbe13e0 100644 --- a/frontend/src/Components/Link/IconButton.js +++ b/frontend/src/Components/Link/IconButton.js @@ -2,6 +2,7 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import Icon from 'Components/Icon'; +import translate from 'Utilities/String/translate'; import Link from './Link'; import styles from './IconButton.css'; @@ -23,7 +24,7 @@ function IconButton(props) { className, isDisabled && styles.isDisabled )} - aria-label="Table Options Button" + aria-label={translate('TableOptionsButton')} isDisabled={isDisabled} {...otherProps} > diff --git a/frontend/src/Movie/Details/MovieDetails.js b/frontend/src/Movie/Details/MovieDetails.js index 4b27d50a5..558791a13 100644 --- a/frontend/src/Movie/Details/MovieDetails.js +++ b/frontend/src/Movie/Details/MovieDetails.js @@ -654,7 +654,7 @@ class MovieDetails extends Component { } - + diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorRow.css b/frontend/src/MovieFile/Editor/MovieFileEditorRow.css index a0ace4ada..35883e345 100644 --- a/frontend/src/MovieFile/Editor/MovieFileEditorRow.css +++ b/frontend/src/MovieFile/Editor/MovieFileEditorRow.css @@ -28,6 +28,9 @@ white-space: nowrap; } +.languages, +.audio, +.video, .actions { composes: cell from '~Components/Table/Cells/TableRowCell.css'; diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorRow.css.d.ts b/frontend/src/MovieFile/Editor/MovieFileEditorRow.css.d.ts index 21444b886..2989c594d 100644 --- a/frontend/src/MovieFile/Editor/MovieFileEditorRow.css.d.ts +++ b/frontend/src/MovieFile/Editor/MovieFileEditorRow.css.d.ts @@ -3,17 +3,20 @@ interface CssExports { 'actions': string; 'age': string; + 'audio': string; 'audioLanguages': string; 'customFormatScore': string; 'download': string; 'formats': string; 'language': string; + 'languages': string; 'quality': string; 'rejected': string; 'relativePath': string; 'releaseGroup': string; 'size': string; 'subtitles': string; + 'video': string; } export const cssExports: CssExports; export default cssExports; diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorRow.js b/frontend/src/MovieFile/Editor/MovieFileEditorRow.js index 3a941b0f4..89e3900ce 100644 --- a/frontend/src/MovieFile/Editor/MovieFileEditorRow.js +++ b/frontend/src/MovieFile/Editor/MovieFileEditorRow.js @@ -81,7 +81,8 @@ class MovieFileEditorRow extends Component { qualityCutoffNotMet, customFormats, customFormatScore, - languages + languages, + columns } = this.props; const { @@ -91,138 +92,214 @@ class MovieFileEditorRow extends Component { } = this.state; const showQualityPlaceholder = !quality; - const showLanguagePlaceholder = !languages; return ( - - {relativePath} - - - - - - - - - - - - - - - - - - - - {formatBytes(size)} - - - - { - showLanguagePlaceholder && - - } - - { - !showLanguagePlaceholder && !!languages && - - } - - - - { - showQualityPlaceholder && - - } - - { - !showQualityPlaceholder && !!quality && - - } - - - - {releaseGroup} - - - - - - - - } - position={tooltipPositions.TOP} - /> - - - - - - - - - + { + columns.map((column) => { + const { + name, + isVisible + } = column; + + if (!isVisible) { + return null; + } + + if (name === 'relativePath') { + return ( + + {relativePath} + + ); + } + + if (name === 'customFormats') { + return ( + + + + ); + } + + if (name === 'customFormatScore') { + return ( + + } + position={tooltipPositions.TOP} + /> + + ); + } + + if (name === 'languages') { + return ( + + { + showLanguagePlaceholder ? + : + null + } + + { + !showLanguagePlaceholder && !!languages && + + } + + ); + } + + if (name === 'quality') { + return ( + + { + showQualityPlaceholder ? + : + null + } + + { + !showQualityPlaceholder && !!quality && + + } + + ); + } + + if (name === 'audioInfo') { + return ( + + + + ); + } + + if (name === 'audioLanguages') { + return ( + + + + ); + } + + if (name === 'subtitleLanguages') { + return ( + + + + ); + } + + if (name === 'videoCodec') { + return ( + + + + ); + } + + if (name === 'size') { + return ( + + {formatBytes(size)} + + ); + } + + if (name === 'releaseGroup') { + return ( + + {releaseGroup} + + ); + } + + if (name === 'actions') { + return ( + + + + + + + + ); + } + + return null; + }) + } translate('RelativePath'), - isVisible: true - }, - { - name: 'videoCodec', - label: () => translate('VideoCodec'), - isVisible: true - }, - { - name: 'audioInfo', - label: () => translate('AudioInfo'), - isVisible: true - }, - { - name: 'audioLanguages', - label: () => translate('AudioLanguages'), - isVisible: true - }, - { - name: 'subtitleLanguages', - label: () => translate('SubtitleLanguages'), - isVisible: true - }, - { - name: 'size', - label: () => translate('Size'), - isVisible: true - }, - { - name: 'languages', - label: () => translate('Languages'), - isVisible: true - }, - { - name: 'quality', - label: () => translate('Quality'), - isVisible: true - }, - { - name: 'releaseGroup', - label: () => translate('ReleaseGroup'), - isVisible: true - }, - { - name: 'customFormats', - label: () => translate('Formats'), - isVisible: true - }, - { - name: 'customFormatScore', - label: React.createElement(Icon, { - name: icons.SCORE, - title: () => translate('CustomFormatScore') - }), - isVisible: true - }, - { - name: 'action', - label: React.createElement(IconButton, { name: icons.ADVANCED_SETTINGS }), - isVisible: true - } -]; - class MovieFileEditorTableContent extends Component { // @@ -82,7 +12,9 @@ class MovieFileEditorTableContent extends Component { render() { const { - items + items, + columns, + onTableOptionChange } = this.props; return ( @@ -96,13 +28,17 @@ class MovieFileEditorTableContent extends Component { { !!items.length && - +
{ items.map((item) => { return ( @@ -122,6 +58,8 @@ MovieFileEditorTableContent.propTypes = { movieId: PropTypes.number, isDeleting: PropTypes.bool.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, + columns: PropTypes.arrayOf(PropTypes.object).isRequired, + onTableOptionChange: PropTypes.func.isRequired, onDeletePress: PropTypes.func.isRequired }; diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js b/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js index 7597741fe..59763222d 100644 --- a/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js +++ b/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js @@ -1,9 +1,8 @@ -/* eslint max-params: 0 */ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { deleteMovieFile, updateMovieFiles } from 'Store/Actions/movieFileActions'; +import { deleteMovieFile, setMovieFilesTableOption, updateMovieFiles } from 'Store/Actions/movieFileActions'; import { fetchLanguages, fetchQualityProfileSchema } from 'Store/Actions/settingsActions'; import createMovieSelector from 'Store/Selectors/createMovieSelector'; import getQualities from 'Utilities/Quality/getQualities'; @@ -30,6 +29,7 @@ function createMapStateToProps() { return { items: filesForMovie, + columns: movieFiles.columns, isDeleting: movieFiles.isDeleting, isSaving: movieFiles.isSaving, error: null, @@ -54,6 +54,10 @@ function createMapDispatchToProps(dispatch, props) { dispatch(updateMovieFiles(updateProps)); }, + onTableOptionChange(payload) { + dispatch(setMovieFilesTableOption(payload)); + }, + onDeletePress(movieFileId) { dispatch(deleteMovieFile({ id: movieFileId diff --git a/frontend/src/Store/Actions/movieFileActions.js b/frontend/src/Store/Actions/movieFileActions.js index 1160fee86..51210274a 100644 --- a/frontend/src/Store/Actions/movieFileActions.js +++ b/frontend/src/Store/Actions/movieFileActions.js @@ -1,9 +1,15 @@ import _ from 'lodash'; +import React from 'react'; import { createAction } from 'redux-actions'; import { batchActions } from 'redux-batched-actions'; +import Icon from 'Components/Icon'; +import IconButton from 'Components/Link/IconButton'; +import { icons } from 'Helpers/Props'; import movieEntities from 'Movie/movieEntities'; +import createSetTableOptionReducer from 'Store/Actions/Creators/Reducers/createSetTableOptionReducer'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; +import translate from 'Utilities/String/translate'; import { removeItem, set, updateItem } from './baseActions'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; @@ -25,9 +31,82 @@ export const defaultState = { deleteError: null, isSaving: false, saveError: null, - items: [] + items: [], + + columns: [ + { + name: 'relativePath', + label: () => translate('RelativePath'), + isVisible: true + }, + { + name: 'videoCodec', + label: () => translate('VideoCodec'), + isVisible: true + }, + { + name: 'audioInfo', + label: () => translate('AudioInfo'), + isVisible: true + }, + { + name: 'audioLanguages', + label: () => translate('AudioLanguages'), + isVisible: false + }, + { + name: 'subtitleLanguages', + label: () => translate('SubtitleLanguages'), + isVisible: false + }, + { + name: 'size', + label: () => translate('Size'), + isVisible: true + }, + { + name: 'languages', + label: () => translate('Languages'), + isVisible: true + }, + { + name: 'quality', + label: () => translate('Quality'), + isVisible: true + }, + { + name: 'releaseGroup', + label: () => translate('ReleaseGroup'), + isVisible: true + }, + { + name: 'customFormats', + label: () => translate('Formats'), + isVisible: true + }, + { + name: 'customFormatScore', + columnLabel: () => translate('CustomFormatScore'), + label: React.createElement(Icon, { + name: icons.SCORE, + title: () => translate('CustomFormatScore') + }), + isVisible: true + }, + { + name: 'actions', + columnLabel: () => translate('Actions'), + label: React.createElement(IconButton, { name: icons.ADVANCED_SETTINGS }), + isVisible: true, + isModifiable: false + } + ] }; +export const persistState = [ + 'movieFiles.columns' +]; + // // Actions Types @@ -36,6 +115,7 @@ export const DELETE_MOVIE_FILE = 'movieFiles/deleteMovieFile'; export const DELETE_MOVIE_FILES = 'movieFiles/deleteMovieFiles'; export const UPDATE_MOVIE_FILES = 'movieFiles/updateMovieFiles'; export const CLEAR_MOVIE_FILES = 'movieFiles/clearMovieFiles'; +export const SET_MOVIE_FILES_TABLE_OPTION = 'movieFiles/setMovieFilesTableOption'; // // Action Creators @@ -45,6 +125,7 @@ export const deleteMovieFile = createThunk(DELETE_MOVIE_FILE); export const deleteMovieFiles = createThunk(DELETE_MOVIE_FILES); export const updateMovieFiles = createThunk(UPDATE_MOVIE_FILES); export const clearMovieFiles = createAction(CLEAR_MOVIE_FILES); +export const setMovieFilesTableOption = createAction(SET_MOVIE_FILES_TABLE_OPTION); // // Helpers @@ -234,9 +315,19 @@ export const actionHandlers = handleThunks({ // Reducers export const reducers = createHandleActions({ + [SET_MOVIE_FILES_TABLE_OPTION]: createSetTableOptionReducer(section), [CLEAR_MOVIE_FILES]: (state) => { - return Object.assign({}, state, defaultState); + return Object.assign({}, state, { + isFetching: false, + isPopulated: false, + error: null, + isDeleting: false, + deleteError: null, + isSaving: false, + saveError: null, + items: [] + }); } }, defaultState, section); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index dcf5b0165..0d3983afd 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1109,6 +1109,7 @@ "TMDb": "TMDb", "Table": "Table", "TableOptions": "Table Options", + "TableOptionsButton": "Table Options Button", "TableOptionsColumnsMessage": "Choose which columns are visible and which order they appear in", "TagCannotBeDeletedWhileInUse": "Cannot be deleted while in use", "TagDetails": "Tag Details - {label}",