diff --git a/frontend/build/webpack.config.js b/frontend/build/webpack.config.js index 6f324ac95..1c86e39d4 100644 --- a/frontend/build/webpack.config.js +++ b/frontend/build/webpack.config.js @@ -44,7 +44,8 @@ module.exports = (env) => { 'node_modules' ], alias: { - jquery: 'jquery/src/jquery' + jquery: 'jquery/src/jquery', + 'react-middle-truncate': 'react-middle-truncate/lib/react-middle-truncate' }, fallback: { buffer: false, diff --git a/frontend/src/Activity/Blocklist/BlocklistRow.js b/frontend/src/Activity/Blocklist/BlocklistRow.js index 3fc813196..9dcfc47cd 100644 --- a/frontend/src/Activity/Blocklist/BlocklistRow.js +++ b/frontend/src/Activity/Blocklist/BlocklistRow.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import AlbumFormats from 'Album/AlbumFormats'; import TrackQuality from 'Album/TrackQuality'; import ArtistNameLink from 'Artist/ArtistNameLink'; import IconButton from 'Components/Link/IconButton'; @@ -45,6 +46,7 @@ class BlocklistRow extends Component { artist, sourceTitle, quality, + customFormats, date, protocol, indexer, @@ -110,6 +112,16 @@ class BlocklistRow extends Component { ); } + if (name === 'customFormats') { + return ( + + + + ); + } + if (name === 'date') { return ( : + null + } + + { + nzbInfoUrl ? Info URL @@ -114,7 +125,8 @@ function HistoryDetails(props) { {nzbInfoUrl} - + : + null } { @@ -179,6 +191,7 @@ function HistoryDetails(props) { if (eventType === 'trackFileImported') { const { + customFormatScore, droppedPath, importedPath } = data; @@ -201,12 +214,22 @@ function HistoryDetails(props) { } { - !!importedPath && + importedPath ? + /> : + null + } + + { + customFormatScore && customFormatScore !== '0' ? + : + null } ); @@ -214,7 +237,8 @@ function HistoryDetails(props) { if (eventType === 'trackFileDeleted') { const { - reason + reason, + customFormatScore } = data; let reasonMessage = ''; @@ -244,6 +268,15 @@ function HistoryDetails(props) { title={translate('Reason')} data={reasonMessage} /> + + { + customFormatScore && customFormatScore !== '0' ? + : + null + } ); } diff --git a/frontend/src/Activity/History/HistoryRow.css b/frontend/src/Activity/History/HistoryRow.css index 669377fdb..039804b63 100644 --- a/frontend/src/Activity/History/HistoryRow.css +++ b/frontend/src/Activity/History/HistoryRow.css @@ -10,6 +10,12 @@ width: 80px; } +.customFormatScore { + composes: cell from '~Components/Table/Cells/TableRowCell.css'; + + width: 55px; +} + .releaseGroup { composes: cell from '~Components/Table/Cells/TableRowCell.css'; diff --git a/frontend/src/Activity/History/HistoryRow.js b/frontend/src/Activity/History/HistoryRow.js index 98718f70f..9bac266f4 100644 --- a/frontend/src/Activity/History/HistoryRow.js +++ b/frontend/src/Activity/History/HistoryRow.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import AlbumFormats from 'Album/AlbumFormats'; import AlbumTitleLink from 'Album/AlbumTitleLink'; import TrackQuality from 'Album/TrackQuality'; import ArtistNameLink from 'Artist/ArtistNameLink'; @@ -8,6 +9,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import { icons } from 'Helpers/Props'; +import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore'; import HistoryDetailsModal from './Details/HistoryDetailsModal'; import HistoryEventTypeCell from './HistoryEventTypeCell'; import styles from './HistoryRow.css'; @@ -55,6 +57,7 @@ class HistoryRow extends Component { album, track, quality, + customFormats, qualityCutoffNotMet, eventType, sourceTitle, @@ -136,6 +139,16 @@ class HistoryRow extends Component { ); } + if (name === 'customFormats') { + return ( + + + + ); + } + if (name === 'date') { return ( + {formatPreferredWordScore(data.customFormatScore)} + + ); + } + if (name === 'releaseGroup') { return ( + + + ); + } + if (name === 'protocol') { return ( @@ -382,6 +394,7 @@ QueueRow.propTypes = { artist: PropTypes.object, album: PropTypes.object, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object), protocol: PropTypes.string.isRequired, indexer: PropTypes.string, outputPath: PropTypes.string, diff --git a/frontend/src/Album/AlbumFormats.js b/frontend/src/Album/AlbumFormats.js new file mode 100644 index 000000000..1591fa714 --- /dev/null +++ b/frontend/src/Album/AlbumFormats.js @@ -0,0 +1,33 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Label from 'Components/Label'; +import { kinds } from 'Helpers/Props'; + +function AlbumFormats({ formats }) { + return ( +
+ { + formats.map((format) => { + return ( + + ); + }) + } +
+ ); +} + +AlbumFormats.propTypes = { + formats: PropTypes.arrayOf(PropTypes.object).isRequired +}; + +AlbumFormats.defaultProps = { + formats: [] +}; + +export default AlbumFormats; diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index 0285559a8..223e0f90e 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -13,6 +13,7 @@ import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import NotFound from 'Components/NotFound'; import Switch from 'Components/Router/Switch'; import AddNewItemConnector from 'Search/AddNewItemConnector'; +import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector'; import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector'; import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector'; import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector'; @@ -167,6 +168,11 @@ function AppRoutes(props) { component={QualityConnector} /> + + { + this._input = ref; + }; + + selectionChange() { + if (this._selectionTimeout) { + this._selectionTimeout = clearTimeout(this._selectionTimeout); + } + + this._selectionTimeout = setTimeout(() => { + const selectionStart = this._input.selectionStart; + const selectionEnd = this._input.selectionEnd; + + const selectionChanged = ( + this._selectionStart !== selectionStart || + this._selectionEnd !== selectionEnd + ); + + this._selectionStart = selectionStart; + this._selectionEnd = selectionEnd; + + if (this.props.onSelectionChange && selectionChanged) { + this.props.onSelectionChange(selectionStart, selectionEnd); + } + }, 10); + } + + // + // Listeners + + onChange = (event) => { + const { + name, + onChange + } = this.props; + + const payload = { + name, + value: event.target.value + }; + + onChange(payload); + }; + + onFocus = (event) => { + if (this.props.onFocus) { + this.props.onFocus(event); + } + + this.selectionChange(); + }; + + onKeyUp = () => { + this.selectionChange(); + }; + + onMouseDown = () => { + this._isMouseTarget = true; + }; + + onMouseUp = () => { + this.selectionChange(); + }; + + onDocumentMouseUp = () => { + if (this._isMouseTarget) { + this.selectionChange(); + } + + this._isMouseTarget = false; + }; + + // + // Render + + render() { + const { + className, + readOnly, + autoFocus, + placeholder, + name, + value, + hasError, + hasWarning, + onBlur + } = this.props; + + return ( +