mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-18 21:35:51 -04:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 957be99401 | |||
| 4bcde25e29 | |||
| 1d70f36e7d | |||
| cc0a448bc8 | |||
| c9e977baea | |||
| 6cb9a46cd4 | |||
| eef379277a | |||
| 41fef47684 | |||
| fcda6faf3d | |||
| 79bbf9c50b | |||
| 43d2f2804b | |||
| fa62f3f66a | |||
| 229d91fe40 | |||
| 2673d1eee4 | |||
| e59fd1118f | |||
| c1fd33b152 | |||
| 2f58c8676f | |||
| defc448304 | |||
| 3ec3358728 | |||
| d4072cdfe2 | |||
| 136a030c07 | |||
| 6d89ae89a4 | |||
| 98e4273b7a | |||
| ecf9983ea6 | |||
| a059a700eb | |||
| ced624c2ff | |||
| 7c32061e17 | |||
| bc4847cdc7 | |||
| 65d79dd078 | |||
| 238ddbbe1f | |||
| 3f444406da | |||
| d7aaa1cdc2 | |||
| 263534717d | |||
| 073d15160d | |||
| c5075e5d49 | |||
| fc345047ee | |||
| bffab87da7 | |||
| a8a9d3b833 | |||
| ff1987be84 | |||
| cb08c0767d | |||
| 5f1d7ddc11 | |||
| 0ba3c08ea6 | |||
| 6b9a378eaf | |||
| b4562e6236 | |||
| bbffff78ed | |||
| 740f0f1e5f | |||
| 45b38b44c1 | |||
| 318d59bb99 | |||
| ed54d071c4 | |||
| cff15de4fc | |||
| 88c0e24c58 | |||
| 8e0645670b | |||
| 40eeb31a21 | |||
| 3e534cf8bf | |||
| c96b3c4b0b | |||
| 78bc9f9b4b | |||
| b737f05a83 | |||
| 8d96fd2387 | |||
| 6c487ead00 | |||
| 22e3cf844c | |||
| 14b4b5e122 | |||
| 10f5f3c5c8 | |||
| c687b552f0 |
+2
-2
@@ -9,13 +9,13 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '5.2.1'
|
majorVersion: '5.2.5'
|
||||||
minorVersion: $[counter('minorVersion', 2000)]
|
minorVersion: $[counter('minorVersion', 2000)]
|
||||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.413'
|
dotnetVersion: '6.0.417'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '16.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class Blocklist extends Component {
|
|||||||
lastToggled: null,
|
lastToggled: null,
|
||||||
selectedState: {},
|
selectedState: {},
|
||||||
isConfirmRemoveModalOpen: false,
|
isConfirmRemoveModalOpen: false,
|
||||||
|
isConfirmClearModalOpen: false,
|
||||||
items: props.items
|
items: props.items
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -90,6 +91,19 @@ class Blocklist extends Component {
|
|||||||
this.setState({ isConfirmRemoveModalOpen: false });
|
this.setState({ isConfirmRemoveModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onClearBlocklistPress = () => {
|
||||||
|
this.setState({ isConfirmClearModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onClearBlocklistConfirmed = () => {
|
||||||
|
this.props.onClearBlocklistPress();
|
||||||
|
this.setState({ isConfirmClearModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
onConfirmClearModalClose = () => {
|
||||||
|
this.setState({ isConfirmClearModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -103,7 +117,6 @@ class Blocklist extends Component {
|
|||||||
totalRecords,
|
totalRecords,
|
||||||
isRemoving,
|
isRemoving,
|
||||||
isClearingBlocklistExecuting,
|
isClearingBlocklistExecuting,
|
||||||
onClearBlocklistPress,
|
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -111,7 +124,8 @@ class Blocklist extends Component {
|
|||||||
allSelected,
|
allSelected,
|
||||||
allUnselected,
|
allUnselected,
|
||||||
selectedState,
|
selectedState,
|
||||||
isConfirmRemoveModalOpen
|
isConfirmRemoveModalOpen,
|
||||||
|
isConfirmClearModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
@@ -131,8 +145,9 @@ class Blocklist extends Component {
|
|||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('Clear')}
|
label={translate('Clear')}
|
||||||
iconName={icons.CLEAR}
|
iconName={icons.CLEAR}
|
||||||
|
isDisabled={!items.length}
|
||||||
isSpinning={isClearingBlocklistExecuting}
|
isSpinning={isClearingBlocklistExecuting}
|
||||||
onPress={onClearBlocklistPress}
|
onPress={this.onClearBlocklistPress}
|
||||||
/>
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
@@ -215,6 +230,16 @@ class Blocklist extends Component {
|
|||||||
onConfirm={this.onRemoveSelectedConfirmed}
|
onConfirm={this.onRemoveSelectedConfirmed}
|
||||||
onCancel={this.onConfirmRemoveModalClose}
|
onCancel={this.onConfirmRemoveModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isConfirmClearModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('ClearBlocklist')}
|
||||||
|
message={translate('ClearBlocklistMessageText')}
|
||||||
|
confirmLabel={translate('Clear')}
|
||||||
|
onConfirm={this.onClearBlocklistConfirmed}
|
||||||
|
onCancel={this.onConfirmClearModalClose}
|
||||||
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,8 +85,13 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.studio,
|
||||||
|
.genres {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.links {
|
.links {
|
||||||
margin-left: 8px;
|
margin-left: 5px;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ interface CssExports {
|
|||||||
'certification': string;
|
'certification': string;
|
||||||
'content': string;
|
'content': string;
|
||||||
'exclusionIcon': string;
|
'exclusionIcon': string;
|
||||||
|
'genres': string;
|
||||||
'icons': string;
|
'icons': string;
|
||||||
'links': string;
|
'links': string;
|
||||||
'overlay': string;
|
'overlay': string;
|
||||||
@@ -14,6 +15,7 @@ interface CssExports {
|
|||||||
'runtime': string;
|
'runtime': string;
|
||||||
'searchResult': string;
|
'searchResult': string;
|
||||||
'statusContainer': string;
|
'statusContainer': string;
|
||||||
|
'studio': string;
|
||||||
'title': string;
|
'title': string;
|
||||||
'titleContainer': string;
|
'titleContainer': string;
|
||||||
'titleRow': string;
|
'titleRow': string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
|
import ImdbRating from 'Components/ImdbRating';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import TmdbRating from 'Components/TmdbRating';
|
import TmdbRating from 'Components/TmdbRating';
|
||||||
@@ -61,6 +62,7 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
titleSlug,
|
titleSlug,
|
||||||
year,
|
year,
|
||||||
studio,
|
studio,
|
||||||
|
genres,
|
||||||
status,
|
status,
|
||||||
overview,
|
overview,
|
||||||
ratings,
|
ratings,
|
||||||
@@ -76,6 +78,7 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
hasFile,
|
hasFile,
|
||||||
isAvailable,
|
isAvailable,
|
||||||
movieFile,
|
movieFile,
|
||||||
|
queueItem,
|
||||||
runtime,
|
runtime,
|
||||||
movieRuntimeFormat,
|
movieRuntimeFormat,
|
||||||
certification
|
certification
|
||||||
@@ -197,13 +200,46 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
|
{
|
||||||
|
ratings.imdb ?
|
||||||
|
<Label size={sizes.LARGE}>
|
||||||
|
<ImdbRating
|
||||||
|
ratings={ratings}
|
||||||
|
iconSize={13}
|
||||||
|
/>
|
||||||
|
</Label> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!studio &&
|
!!studio &&
|
||||||
<Label size={sizes.LARGE}>
|
<Label size={sizes.LARGE}>
|
||||||
{studio}
|
<Icon
|
||||||
|
name={icons.STUDIO}
|
||||||
|
size={13}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className={styles.studio}>
|
||||||
|
{studio}
|
||||||
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
genres.length > 0 ?
|
||||||
|
<Label size={sizes.LARGE}>
|
||||||
|
<Icon
|
||||||
|
name={icons.GENRE}
|
||||||
|
size={13}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className={styles.genres}>
|
||||||
|
{genres.slice(0, 3).join(', ')}
|
||||||
|
</span>
|
||||||
|
</Label> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchor={
|
anchor={
|
||||||
<Label
|
<Label
|
||||||
@@ -215,15 +251,15 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span className={styles.links}>
|
<span className={styles.links}>
|
||||||
Links
|
{translate('Links')}
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
tooltip={
|
tooltip={
|
||||||
<MovieDetailsLinks
|
<MovieDetailsLinks
|
||||||
tmdbId={tmdbId}
|
tmdbId={tmdbId}
|
||||||
youTubeTrailerId={youTubeTrailerId}
|
|
||||||
imdbId={imdbId}
|
imdbId={imdbId}
|
||||||
|
youTubeTrailerId={youTubeTrailerId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
canFlip={true}
|
canFlip={true}
|
||||||
@@ -237,6 +273,7 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
hasMovieFiles={hasFile}
|
hasMovieFiles={hasFile}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
isAvailable={isAvailable}
|
isAvailable={isAvailable}
|
||||||
|
queueItem={queueItem}
|
||||||
id={id}
|
id={id}
|
||||||
useLabel={true}
|
useLabel={true}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
@@ -273,6 +310,7 @@ AddNewMovieSearchResult.propTypes = {
|
|||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
year: PropTypes.number.isRequired,
|
year: PropTypes.number.isRequired,
|
||||||
studio: PropTypes.string,
|
studio: PropTypes.string,
|
||||||
|
genres: PropTypes.arrayOf(PropTypes.string),
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
overview: PropTypes.string,
|
overview: PropTypes.string,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
@@ -283,15 +321,19 @@ AddNewMovieSearchResult.propTypes = {
|
|||||||
isExclusionMovie: PropTypes.bool.isRequired,
|
isExclusionMovie: PropTypes.bool.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
queueItems: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
hasFile: PropTypes.bool.isRequired,
|
||||||
isAvailable: PropTypes.bool.isRequired,
|
isAvailable: PropTypes.bool.isRequired,
|
||||||
movieFile: PropTypes.object,
|
movieFile: PropTypes.object,
|
||||||
|
queueItem: PropTypes.object,
|
||||||
colorImpairedMode: PropTypes.bool,
|
colorImpairedMode: PropTypes.bool,
|
||||||
runtime: PropTypes.number.isRequired,
|
runtime: PropTypes.number.isRequired,
|
||||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||||
certification: PropTypes.string
|
certification: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddNewMovieSearchResult.defaultProps = {
|
||||||
|
genres: []
|
||||||
|
};
|
||||||
|
|
||||||
export default AddNewMovieSearchResult;
|
export default AddNewMovieSearchResult;
|
||||||
|
|||||||
@@ -10,14 +10,18 @@ function createMapStateToProps() {
|
|||||||
createExistingMovieSelector(),
|
createExistingMovieSelector(),
|
||||||
createExclusionMovieSelector(),
|
createExclusionMovieSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
|
(state) => state.queue.details.items,
|
||||||
(state, { internalId }) => internalId,
|
(state, { internalId }) => internalId,
|
||||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||||
(isExistingMovie, isExclusionMovie, dimensions, internalId, movieRuntimeFormat) => {
|
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId, movieRuntimeFormat) => {
|
||||||
|
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
existingMovieId: internalId,
|
existingMovieId: internalId,
|
||||||
isExistingMovie,
|
isExistingMovie,
|
||||||
isExclusionMovie,
|
isExclusionMovie,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
queueItem,
|
||||||
movieRuntimeFormat
|
movieRuntimeFormat
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('AppUpdated', { appName: 'Radarr' })}
|
{translate('AppUpdated')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
|
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
{translate('ConnectionLostToBackend', { appName: 'Radarr' })}
|
{translate('ConnectionLostToBackend')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.automatic}>
|
<div className={styles.automatic}>
|
||||||
{translate('ConnectionLostReconnect', { appName: 'Radarr' })}
|
{translate('ConnectionLostReconnect')}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class CalendarConnector extends Component {
|
|||||||
gotoCalendarToday
|
gotoCalendarToday
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate);
|
registerPagePopulator(this.repopulate, ['movieFileUpdated', 'movieFileDeleted']);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchCalendar();
|
fetchCalendar();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as commandNames from 'Commands/commandNames';
|
|||||||
import withScrollPosition from 'Components/withScrollPosition';
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
||||||
|
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
||||||
@@ -38,6 +39,12 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatchFetchRootFolders() {
|
dispatchFetchRootFolders() {
|
||||||
dispatch(fetchRootFolders());
|
dispatch(fetchRootFolders());
|
||||||
},
|
},
|
||||||
|
dispatchFetchQueueDetails() {
|
||||||
|
dispatch(fetchQueueDetails());
|
||||||
|
},
|
||||||
|
dispatchClearQueueDetails() {
|
||||||
|
dispatch(clearQueueDetails());
|
||||||
|
},
|
||||||
onUpdateSelectedPress(payload) {
|
onUpdateSelectedPress(payload) {
|
||||||
dispatch(saveMovieCollections(payload));
|
dispatch(saveMovieCollections(payload));
|
||||||
},
|
},
|
||||||
@@ -63,10 +70,12 @@ class CollectionConnector extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
registerPagePopulator(this.repopulate);
|
registerPagePopulator(this.repopulate);
|
||||||
this.props.dispatchFetchRootFolders();
|
this.props.dispatchFetchRootFolders();
|
||||||
|
this.props.dispatchFetchQueueDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
unregisterPagePopulator(this.repopulate);
|
unregisterPagePopulator(this.repopulate);
|
||||||
|
this.props.dispatchClearQueueDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -99,7 +108,9 @@ CollectionConnector.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
onUpdateSelectedPress: PropTypes.func.isRequired,
|
onUpdateSelectedPress: PropTypes.func.isRequired,
|
||||||
dispatchFetchRootFolders: PropTypes.func.isRequired
|
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchQueueDetails: PropTypes.func.isRequired,
|
||||||
|
dispatchClearQueueDetails: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withScrollPosition(
|
export default withScrollPosition(
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class CollectionMovie extends Component {
|
|||||||
hasFile,
|
hasFile,
|
||||||
folder,
|
folder,
|
||||||
isAvailable,
|
isAvailable,
|
||||||
|
movieFile,
|
||||||
isExistingMovie,
|
isExistingMovie,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
@@ -131,6 +132,8 @@ class CollectionMovie extends Component {
|
|||||||
id ?
|
id ?
|
||||||
<div className={styles.overlayStatus}>
|
<div className={styles.overlayStatus}>
|
||||||
<MovieIndexProgressBar
|
<MovieIndexProgressBar
|
||||||
|
movieId={id}
|
||||||
|
movieFile={movieFile}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
hasFile={hasFile}
|
hasFile={hasFile}
|
||||||
status={status}
|
status={status}
|
||||||
@@ -180,6 +183,7 @@ CollectionMovie.propTypes = {
|
|||||||
hasFile: PropTypes.bool,
|
hasFile: PropTypes.bool,
|
||||||
folder: PropTypes.string,
|
folder: PropTypes.string,
|
||||||
isAvailable: PropTypes.bool,
|
isAvailable: PropTypes.bool,
|
||||||
|
movieFile: PropTypes.object,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
posterHeight: PropTypes.number.isRequired,
|
posterHeight: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
.description {
|
|
||||||
line-height: $lineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
line-height: $lineHeight;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
|||||||
@@ -63,6 +63,12 @@
|
|||||||
width: 1280px;
|
width: 1280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.extraExtraLarge {
|
||||||
|
composes: modal;
|
||||||
|
|
||||||
|
width: 1600px;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointExtraLarge) {
|
@media only screen and (max-width: $breakpointExtraLarge) {
|
||||||
.modal.extraLarge {
|
.modal.extraLarge {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
@@ -90,7 +96,8 @@
|
|||||||
.modal.small,
|
.modal.small,
|
||||||
.modal.medium,
|
.modal.medium,
|
||||||
.modal.large,
|
.modal.large,
|
||||||
.modal.extraLarge {
|
.modal.extraLarge,
|
||||||
|
.modal.extraExtraLarge {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
|
'extraExtraLarge': string;
|
||||||
'extraLarge': string;
|
'extraLarge': string;
|
||||||
'large': string;
|
'large': string;
|
||||||
'medium': string;
|
'medium': string;
|
||||||
|
|||||||
@@ -187,6 +187,8 @@ class SignalRConnector extends Component {
|
|||||||
repopulatePage('movieFileUpdated');
|
repopulatePage('movieFileUpdated');
|
||||||
} else if (body.action === 'deleted') {
|
} else if (body.action === 'deleted') {
|
||||||
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
||||||
|
|
||||||
|
repopulatePage('movieFileDeleted');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
"start_url": "../../../../",
|
"start_url": "../../../../",
|
||||||
"theme_color": "#3a3f51",
|
"theme_color": "#3a3f51",
|
||||||
"background_color": "#3a3f51",
|
"background_color": "#3a3f51",
|
||||||
"display": "standalone"
|
"display": "minimal-ui"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
|
|
||||||
|
// This file contains some helpers for power users in a browser console
|
||||||
|
|
||||||
|
let hasWarned = false;
|
||||||
|
|
||||||
|
function checkActivationWarning() {
|
||||||
|
if (!hasWarned) {
|
||||||
|
console.log('Activated RadarrApi console helpers.');
|
||||||
|
console.warn('Be warned: There will be no further confirmation checks.');
|
||||||
|
hasWarned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachAsyncActions(promise) {
|
||||||
|
promise.filter = function() {
|
||||||
|
const args = arguments;
|
||||||
|
const res = this.then((d) => d.filter(...args));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.map = function() {
|
||||||
|
const args = arguments;
|
||||||
|
const res = this.then((d) => d.map(...args));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.all = function() {
|
||||||
|
const res = this.then((d) => Promise.all(d));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.forEach = function(action) {
|
||||||
|
const res = this.then((d) => Promise.all(d.map(action)));
|
||||||
|
attachAsyncActions(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourceApi {
|
||||||
|
constructor(api, url) {
|
||||||
|
this.api = api;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
single(id) {
|
||||||
|
return this.api.fetch(`${this.url}/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
all() {
|
||||||
|
return this.api.fetch(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter(pred) {
|
||||||
|
return this.all().filter(pred);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(resource) {
|
||||||
|
return this.api.fetch(`${this.url}/${resource.id}`, { method: 'PUT', data: resource });
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(resource) {
|
||||||
|
if (typeof resource === 'object' && resource !== null && resource.id) {
|
||||||
|
resource = resource.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resource || !Number.isInteger(resource)) {
|
||||||
|
throw Error('Invalid resource', resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.api.fetch(`${this.url}/${resource}`, { method: 'DELETE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, options) {
|
||||||
|
return this.api.fetch(`${this.url}${url}`, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleApi {
|
||||||
|
constructor() {
|
||||||
|
this.movie = new ResourceApi(this, '/movie');
|
||||||
|
}
|
||||||
|
|
||||||
|
resource(url) {
|
||||||
|
return new ResourceApi(this, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, options) {
|
||||||
|
checkActivationWarning();
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
url,
|
||||||
|
method: options.method || 'GET'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.data) {
|
||||||
|
req.dataType = 'json';
|
||||||
|
req.data = JSON.stringify(options.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = createAjaxRequest(req).request;
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
console.error(`Failed to fetch ${url}`, xhr);
|
||||||
|
});
|
||||||
|
|
||||||
|
attachAsyncActions(promise);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.RadarrApi = new ConsoleApi();
|
||||||
|
|
||||||
|
export default ConsoleApi;
|
||||||
@@ -50,7 +50,7 @@ $hoverScale: 1.05;
|
|||||||
.title {
|
.title {
|
||||||
@add-mixin truncate;
|
@add-mixin truncate;
|
||||||
|
|
||||||
background-color: #fafbfc;
|
background-color: var(--movieBackgroundColor);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
}
|
}
|
||||||
@@ -68,6 +68,19 @@ $hoverScale: 1.05;
|
|||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.existing {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-width: 25px 25px 0 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #37bc9b transparent transparent;
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface CssExports {
|
|||||||
'controls': string;
|
'controls': string;
|
||||||
'editorSelect': string;
|
'editorSelect': string;
|
||||||
'excluded': string;
|
'excluded': string;
|
||||||
|
'existing': string;
|
||||||
'externalLinks': string;
|
'externalLinks': string;
|
||||||
'link': string;
|
'link': string;
|
||||||
'overlayTitle': string;
|
'overlayTitle': string;
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class DiscoverMoviePoster extends Component {
|
|||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
|
movieRuntimeFormat,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ class DiscoverMoviePoster extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer} title={title}>
|
||||||
{
|
{
|
||||||
<div className={styles.editorSelect}>
|
<div className={styles.editorSelect}>
|
||||||
<CheckInput
|
<CheckInput
|
||||||
@@ -158,6 +159,14 @@ class DiscoverMoviePoster extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isExisting &&
|
||||||
|
<div
|
||||||
|
className={styles.existing}
|
||||||
|
title={translate('Existing')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
style={elementStyle}
|
style={elementStyle}
|
||||||
@@ -185,7 +194,7 @@ class DiscoverMoviePoster extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
showTitle &&
|
showTitle &&
|
||||||
<div className={styles.title}>
|
<div className={styles.title} title={title}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -194,6 +203,7 @@ class DiscoverMoviePoster extends Component {
|
|||||||
showRelativeDates={showRelativeDates}
|
showRelativeDates={showRelativeDates}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
|
movieRuntimeFormat={movieRuntimeFormat}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -236,6 +246,7 @@ DiscoverMoviePoster.propTypes = {
|
|||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||||
isExisting: PropTypes.bool.isRequired,
|
isExisting: PropTypes.bool.isRequired,
|
||||||
isExcluded: PropTypes.bool.isRequired,
|
isExcluded: PropTypes.bool.isRequired,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import DiscoverMoviePoster from './DiscoverMoviePoster';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
|
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
( dimensions) => {
|
(movieRuntimeFormat, dimensions) => {
|
||||||
return {
|
return {
|
||||||
|
movieRuntimeFormat,
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
isSmallScreen: dimensions.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.info {
|
.info {
|
||||||
background-color: #fafbfc;
|
background-color: var(--movieBackgroundColor);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
import TmdbRating from 'Components/TmdbRating';
|
import TmdbRating from 'Components/TmdbRating';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
import { getMovieStatusDetails } from 'Movie/MovieStatus';
|
import { getMovieStatusDetails } from 'Movie/MovieStatus';
|
||||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './DiscoverMoviePosterInfo.css';
|
import styles from './DiscoverMoviePosterInfo.css';
|
||||||
|
|
||||||
function DiscoverMoviePosterInfo(props) {
|
function DiscoverMoviePosterInfo(props) {
|
||||||
@@ -19,12 +22,13 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
sortKey,
|
sortKey,
|
||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat
|
timeFormat,
|
||||||
|
movieRuntimeFormat
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (sortKey === 'status' && status) {
|
if (sortKey === 'status' && status) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info} title={translate('Status')}>
|
||||||
{getMovieStatusDetails(status).title}
|
{getMovieStatusDetails(status).title}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -32,7 +36,7 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
|
|
||||||
if (sortKey === 'studio' && studio) {
|
if (sortKey === 'studio' && studio) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info} title={translate('Studio')}>
|
||||||
{studio}
|
{studio}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -50,8 +54,8 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info} title={translate('InCinemas')}>
|
||||||
{`In Cinemas ${inCinemasDate}`}
|
<Icon name={icons.IN_CINEMAS} /> {inCinemasDate}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -68,8 +72,8 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info} title={translate('DigitalRelease')}>
|
||||||
{`Digital ${digitalReleaseDate}`}
|
<Icon name={icons.MOVIE_FILE} /> {digitalReleaseDate}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -86,15 +90,15 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info} title={translate('PhysicalRelease')}>
|
||||||
{`Released ${physicalReleaseDate}`}
|
<Icon name={icons.DISC} /> {physicalReleaseDate}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortKey === 'certification' && certification) {
|
if (sortKey === 'certification' && certification) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info} title={translate('Certification')}>
|
||||||
{certification}
|
{certification}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -102,8 +106,8 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
|
|
||||||
if (sortKey === 'runtime' && runtime) {
|
if (sortKey === 'runtime' && runtime) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info} title={translate('Runtime')}>
|
||||||
{formatRuntime(runtime)}
|
{formatRuntime(runtime, movieRuntimeFormat)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -111,9 +115,7 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
if (sortKey === 'ratings' && ratings) {
|
if (sortKey === 'ratings' && ratings) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<TmdbRating
|
<TmdbRating ratings={ratings} />
|
||||||
ratings={ratings}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -133,7 +135,8 @@ DiscoverMoviePosterInfo.propTypes = {
|
|||||||
sortKey: PropTypes.string.isRequired,
|
sortKey: PropTypes.string.isRequired,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
movieRuntimeFormat: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DiscoverMoviePosterInfo;
|
export default DiscoverMoviePosterInfo;
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class DiscoverMovieRow extends Component {
|
|||||||
ratings,
|
ratings,
|
||||||
popularity,
|
popularity,
|
||||||
certification,
|
certification,
|
||||||
|
movieRuntimeFormat,
|
||||||
collection,
|
collection,
|
||||||
columns,
|
columns,
|
||||||
isExisting,
|
isExisting,
|
||||||
@@ -230,7 +231,7 @@ class DiscoverMovieRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles[name]}
|
className={styles[name]}
|
||||||
>
|
>
|
||||||
{formatRuntime(runtime)}
|
{formatRuntime(runtime, movieRuntimeFormat)}
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -397,6 +398,7 @@ DiscoverMovieRow.propTypes = {
|
|||||||
popularity: PropTypes.number.isRequired,
|
popularity: PropTypes.number.isRequired,
|
||||||
certification: PropTypes.string,
|
certification: PropTypes.string,
|
||||||
collection: PropTypes.object,
|
collection: PropTypes.object,
|
||||||
|
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isExisting: PropTypes.bool.isRequired,
|
isExisting: PropTypes.bool.isRequired,
|
||||||
isExcluded: PropTypes.bool.isRequired,
|
isExcluded: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import DiscoverMovieRow from './DiscoverMovieRow';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
|
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(dimensions) => {
|
(movieRuntimeFormat, dimensions) => {
|
||||||
return {
|
return {
|
||||||
|
movieRuntimeFormat,
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
isSmallScreen: dimensions.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
authenticationMethod,
|
authenticationMethod,
|
||||||
authenticationRequired,
|
authenticationRequired,
|
||||||
username,
|
username,
|
||||||
password
|
password,
|
||||||
|
passwordConfirmation
|
||||||
} = settings;
|
} = settings;
|
||||||
|
|
||||||
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||||
@@ -63,7 +64,7 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
className={styles.authRequiredAlert}
|
className={styles.authRequiredAlert}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
>
|
>
|
||||||
{translate('AuthenticationRequiredWarning', { appName: 'Radarr' })}
|
{translate('AuthenticationRequiredWarning')}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -76,7 +77,7 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText={translate('AuthenticationMethodHelpText', { appName: 'Radarr' })}
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
|
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
|
||||||
helpLink="https://wiki.servarr.com/radarr/faq#forced-authentication"
|
helpLink="https://wiki.servarr.com/radarr/faq#forced-authentication"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
@@ -120,6 +121,18 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
{...password}
|
{...password}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="passwordConfirmation"
|
||||||
|
onChange={onInputChange}
|
||||||
|
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
|
||||||
|
{...passwordConfirmation}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
</div> :
|
</div> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ export const SMALL = 'small';
|
|||||||
export const MEDIUM = 'medium';
|
export const MEDIUM = 'medium';
|
||||||
export const LARGE = 'large';
|
export const LARGE = 'large';
|
||||||
export const EXTRA_LARGE = 'extraLarge';
|
export const EXTRA_LARGE = 'extraLarge';
|
||||||
|
export const EXTRA_EXTRA_LARGE = 'extraExtraLarge';
|
||||||
|
|
||||||
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE];
|
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE, EXTRA_EXTRA_LARGE];
|
||||||
|
|||||||
+47
-28
@@ -3,13 +3,16 @@ import React, { Fragment } from 'react';
|
|||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||||
|
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { icons, kinds, sortDirections } from 'Helpers/Props';
|
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||||
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
|
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
|
||||||
import styles from './InteractiveSearchContent.css';
|
import styles from './InteractiveSearch.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -24,23 +27,6 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'releaseWeight',
|
|
||||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
|
||||||
isSortable: true,
|
|
||||||
fixedSortDirection: sortDirections.ASCENDING,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rejections',
|
|
||||||
label: React.createElement(Icon, {
|
|
||||||
name: icons.DANGER,
|
|
||||||
title: () => translate('Rejections')
|
|
||||||
}),
|
|
||||||
isSortable: true,
|
|
||||||
fixedSortDirection: sortDirections.ASCENDING,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
label: () => translate('Title'),
|
label: () => translate('Title'),
|
||||||
@@ -84,12 +70,6 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'customFormat',
|
|
||||||
label: () => translate('Formats'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'customFormatScore',
|
name: 'customFormatScore',
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
@@ -107,10 +87,27 @@ const columns = [
|
|||||||
}),
|
}),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'releaseWeight',
|
||||||
|
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||||
|
isSortable: true,
|
||||||
|
fixedSortDirection: sortDirections.ASCENDING,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rejections',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.DANGER,
|
||||||
|
title: () => translate('Rejections')
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
fixedSortDirection: sortDirections.ASCENDING,
|
||||||
|
isVisible: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function InteractiveSearchContent(props) {
|
function InteractiveSearch(props) {
|
||||||
const {
|
const {
|
||||||
searchPayload,
|
searchPayload,
|
||||||
isFetching,
|
isFetching,
|
||||||
@@ -118,18 +115,36 @@ function InteractiveSearchContent(props) {
|
|||||||
error,
|
error,
|
||||||
totalReleasesCount,
|
totalReleasesCount,
|
||||||
items,
|
items,
|
||||||
|
selectedFilterKey,
|
||||||
|
filters,
|
||||||
|
customFilters,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
|
onFilterSelect,
|
||||||
onGrabPress
|
onGrabPress
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
const type = 'movies';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className={styles.filterMenuContainer}>
|
||||||
|
<FilterMenu
|
||||||
|
alignMenu={align.RIGHT}
|
||||||
|
selectedFilterKey={selectedFilterKey}
|
||||||
|
filters={filters}
|
||||||
|
customFilters={customFilters}
|
||||||
|
buttonComponent={PageMenuButton}
|
||||||
|
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
||||||
|
filterModalConnectorComponentProps={{ type }}
|
||||||
|
onFilterSelect={onFilterSelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
isFetching ? <LoadingIndicator /> : null
|
isFetching ? <LoadingIndicator /> : null
|
||||||
}
|
}
|
||||||
@@ -203,19 +218,23 @@ function InteractiveSearchContent(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearchContent.propTypes = {
|
InteractiveSearch.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
totalReleasesCount: PropTypes.number.isRequired,
|
totalReleasesCount: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
|
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.string,
|
sortDirection: PropTypes.string,
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
onGrabPress: PropTypes.func.isRequired
|
onGrabPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InteractiveSearchContent;
|
export default InteractiveSearch;
|
||||||
+25
-12
@@ -2,10 +2,11 @@ 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';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
||||||
import * as releaseActions from 'Store/Actions/releaseActions';
|
import * as releaseActions from 'Store/Actions/releaseActions';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import InteractiveSearchContent from './InteractiveSearchContent';
|
import InteractiveSearch from './InteractiveSearch';
|
||||||
|
|
||||||
function createMapStateToProps(appState) {
|
function createMapStateToProps(appState) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -29,8 +30,12 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(releaseActions.fetchReleases(payload));
|
dispatch(releaseActions.fetchReleases(payload));
|
||||||
},
|
},
|
||||||
|
|
||||||
dispatchClearReleases(payload) {
|
dispatchFetchMovieHistory({ movieId }) {
|
||||||
dispatch(releaseActions.clearReleases(payload));
|
dispatch(fetchMovieHistory({ movieId }));
|
||||||
|
},
|
||||||
|
|
||||||
|
dispatchClearMovieHistory() {
|
||||||
|
dispatch(clearMovieHistory());
|
||||||
},
|
},
|
||||||
|
|
||||||
onSortPress(sortKey, sortDirection) {
|
onSortPress(sortKey, sortDirection) {
|
||||||
@@ -38,8 +43,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onFilterSelect(selectedFilterKey) {
|
onFilterSelect(selectedFilterKey) {
|
||||||
const action = releaseActions.setReleasesFilter;
|
dispatch(releaseActions.setReleasesFilter({ selectedFilterKey }));
|
||||||
dispatch(action({ selectedFilterKey }));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onGrabPress(payload) {
|
onGrabPress(payload) {
|
||||||
@@ -48,7 +52,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class InteractiveSearchContentConnector extends Component {
|
class InteractiveSearchConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -57,7 +61,8 @@ class InteractiveSearchContentConnector extends Component {
|
|||||||
const {
|
const {
|
||||||
searchPayload,
|
searchPayload,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
dispatchFetchReleases
|
dispatchFetchReleases,
|
||||||
|
dispatchFetchMovieHistory
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// If search results are not yet isPopulated fetch them,
|
// If search results are not yet isPopulated fetch them,
|
||||||
@@ -65,6 +70,12 @@ class InteractiveSearchContentConnector extends Component {
|
|||||||
if (!isPopulated) {
|
if (!isPopulated) {
|
||||||
dispatchFetchReleases(searchPayload);
|
dispatchFetchReleases(searchPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatchFetchMovieHistory(searchPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.dispatchClearMovieHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -73,24 +84,26 @@ class InteractiveSearchContentConnector extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
dispatchFetchReleases,
|
dispatchFetchReleases,
|
||||||
dispatchClearReleases,
|
dispatchFetchMovieHistory,
|
||||||
|
dispatchClearMovieHistory,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<InteractiveSearchContent
|
<InteractiveSearch
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearchContentConnector.propTypes = {
|
InteractiveSearchConnector.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
dispatchFetchReleases: PropTypes.func.isRequired,
|
dispatchFetchReleases: PropTypes.func.isRequired,
|
||||||
dispatchClearReleases: PropTypes.func.isRequired
|
dispatchFetchMovieHistory: PropTypes.func.isRequired,
|
||||||
|
dispatchClearMovieHistory: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchContentConnector);
|
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchConnector);
|
||||||
@@ -4,7 +4,7 @@ import FilterMenu from 'Components/Menu/FilterMenu';
|
|||||||
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
||||||
import { align } from 'Helpers/Props';
|
import { align } from 'Helpers/Props';
|
||||||
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||||
import styles from './InteractiveSearchContent.css';
|
import styles from './InteractiveSearch.css';
|
||||||
|
|
||||||
function InteractiveSearchFilterMenu(props) {
|
function InteractiveSearchFilterMenu(props) {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
.cell {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.protocol {
|
.protocol {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.titleContent {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
.indexer {
|
.indexer {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 85px;
|
width: 85px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quality,
|
.quality,
|
||||||
.customFormat,
|
|
||||||
.languages {
|
.languages {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.languages {
|
.languages {
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.customFormatScore {
|
.customFormatScore {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 55px;
|
width: 55px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -33,31 +35,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rejected,
|
.rejected,
|
||||||
.indexerFlags {
|
.indexerFlags,
|
||||||
composes: cell;
|
.download {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
.size {
|
.size {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.peers {
|
.peers {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleContent {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history {
|
.history {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
@@ -67,7 +66,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.download {
|
.download {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
interface CssExports {
|
interface CssExports {
|
||||||
'age': string;
|
'age': string;
|
||||||
'blocklist': string;
|
'blocklist': string;
|
||||||
'cell': string;
|
|
||||||
'customFormat': string;
|
|
||||||
'customFormatScore': string;
|
'customFormatScore': string;
|
||||||
'download': string;
|
'download': string;
|
||||||
'downloadIcon': string;
|
'downloadIcon': string;
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
grabError,
|
grabError,
|
||||||
historyGrabbedData,
|
historyGrabbedData = {} as MovieHistory,
|
||||||
historyFailedData,
|
historyFailedData = {} as MovieHistory,
|
||||||
blocklistData,
|
blocklistData = {} as MovieBlocklist,
|
||||||
searchPayload,
|
searchPayload,
|
||||||
onGrabPress,
|
onGrabPress,
|
||||||
} = props;
|
} = props;
|
||||||
@@ -199,53 +199,6 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
{formatAge(age, ageHours, ageMinutes)}
|
{formatAge(age, ageHours, ageMinutes)}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.download}>
|
|
||||||
<SpinnerIconButton
|
|
||||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
|
||||||
kind={getDownloadKind(isGrabbed, grabError)}
|
|
||||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
|
||||||
isSpinning={isGrabbing}
|
|
||||||
onPress={onGrabPressWrapper}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
className={styles.manualDownloadContent}
|
|
||||||
title={translate('OverrideAndAddToDownloadQueue')}
|
|
||||||
onPress={onOverridePress}
|
|
||||||
>
|
|
||||||
<div className={styles.manualDownloadContent}>
|
|
||||||
<Icon
|
|
||||||
className={styles.interactiveIcon}
|
|
||||||
name={icons.INTERACTIVE}
|
|
||||||
size={12}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
className={styles.downloadIcon}
|
|
||||||
name={icons.CIRCLE_DOWN}
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.rejected}>
|
|
||||||
{rejections.length ? (
|
|
||||||
<Popover
|
|
||||||
anchor={<Icon name={icons.DANGER} kind={kinds.DANGER} />}
|
|
||||||
title={translate('ReleaseRejected')}
|
|
||||||
body={
|
|
||||||
<ul>
|
|
||||||
{rejections.map((rejection, index) => {
|
|
||||||
return <li key={index}>{rejection}</li>;
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
position={tooltipPositions.RIGHT}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
<div className={styles.titleContent}>
|
<div className={styles.titleContent}>
|
||||||
<Link to={infoUrl} title={title}>
|
<Link to={infoUrl} title={title}>
|
||||||
@@ -316,10 +269,6 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
<MovieQuality quality={quality} />
|
<MovieQuality quality={quality} />
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.customFormat}>
|
|
||||||
<MovieFormats formats={customFormats} />
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.customFormatScore}>
|
<TableRowCell className={styles.customFormatScore}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchor={formatCustomFormatScore(
|
anchor={formatCustomFormatScore(
|
||||||
@@ -348,6 +297,53 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.rejected}>
|
||||||
|
{rejections.length ? (
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon name={icons.DANGER} kind={kinds.DANGER} />}
|
||||||
|
title={translate('ReleaseRejected')}
|
||||||
|
body={
|
||||||
|
<ul>
|
||||||
|
{rejections.map((rejection, index) => {
|
||||||
|
return <li key={index}>{rejection}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.download}>
|
||||||
|
<SpinnerIconButton
|
||||||
|
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||||
|
kind={getDownloadKind(isGrabbed, grabError)}
|
||||||
|
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||||
|
isSpinning={isGrabbing}
|
||||||
|
onPress={onGrabPressWrapper}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className={styles.manualDownloadContent}
|
||||||
|
title={translate('OverrideAndAddToDownloadQueue')}
|
||||||
|
onPress={onOverridePress}
|
||||||
|
>
|
||||||
|
<div className={styles.manualDownloadContent}>
|
||||||
|
<Icon
|
||||||
|
className={styles.interactiveIcon}
|
||||||
|
name={icons.INTERACTIVE}
|
||||||
|
size={12}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
className={styles.downloadIcon}
|
||||||
|
name={icons.CIRCLE_DOWN}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={isConfirmGrabModalOpen}
|
isOpen={isConfirmGrabModalOpen}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import InteractiveSearchContentConnector from './InteractiveSearchContentConnector';
|
|
||||||
|
|
||||||
function InteractiveSearchTable(props) {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InteractiveSearchContentConnector
|
|
||||||
searchPayload={props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InteractiveSearchTable.propTypes = {
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InteractiveSearchTable;
|
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Label from 'Components/Label';
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from '../MovieCreditPoster.css';
|
import styles from '../MovieCreditPoster.css';
|
||||||
|
|
||||||
class MovieCastPoster extends Component {
|
class MovieCastPoster extends Component {
|
||||||
@@ -60,7 +57,7 @@ class MovieCastPoster extends Component {
|
|||||||
images,
|
images,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
importListId
|
importList
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -69,36 +66,31 @@ class MovieCastPoster extends Component {
|
|||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
height: `${posterHeight}px`
|
height: `${posterHeight}px`,
|
||||||
|
borderRadius: '5px'
|
||||||
};
|
};
|
||||||
|
|
||||||
const contentStyle = {
|
const contentStyle = {
|
||||||
width: `${posterWidth}px`
|
width: `${posterWidth}px`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
||||||
|
const importListId = importList ? importList.id : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.content}
|
className={styles.content}
|
||||||
style={contentStyle}
|
style={contentStyle}
|
||||||
>
|
>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
<Label className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{
|
<MonitorToggleButton
|
||||||
importListId > 0 ?
|
className={styles.action}
|
||||||
<IconButton
|
monitored={monitored}
|
||||||
className={styles.action}
|
size={20}
|
||||||
name={icons.EDIT}
|
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
||||||
title={translate('EditPerson')}
|
/>
|
||||||
onPress={this.onEditImportListPress}
|
</div>
|
||||||
/> :
|
|
||||||
<IconButton
|
|
||||||
className={styles.action}
|
|
||||||
name={icons.ADD}
|
|
||||||
title={translate('FollowPerson')}
|
|
||||||
onPress={this.onAddImportListPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={elementStyle}
|
style={elementStyle}
|
||||||
@@ -148,12 +140,8 @@ MovieCastPoster.propTypes = {
|
|||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
posterHeight: PropTypes.number.isRequired,
|
posterHeight: PropTypes.number.isRequired,
|
||||||
importListId: PropTypes.number.isRequired,
|
importList: PropTypes.object,
|
||||||
onImportListSelect: PropTypes.func.isRequired
|
onImportListSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
MovieCastPoster.defaultProps = {
|
|
||||||
importListId: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieCastPoster;
|
export default MovieCastPoster;
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Label from 'Components/Label';
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from '../MovieCreditPoster.css';
|
import styles from '../MovieCreditPoster.css';
|
||||||
|
|
||||||
class MovieCrewPoster extends Component {
|
class MovieCrewPoster extends Component {
|
||||||
@@ -60,7 +57,7 @@ class MovieCrewPoster extends Component {
|
|||||||
images,
|
images,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
importListId
|
importList
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -69,36 +66,31 @@ class MovieCrewPoster extends Component {
|
|||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
height: `${posterHeight}px`
|
height: `${posterHeight}px`,
|
||||||
|
borderRadius: '5px'
|
||||||
};
|
};
|
||||||
|
|
||||||
const contentStyle = {
|
const contentStyle = {
|
||||||
width: `${posterWidth}px`
|
width: `${posterWidth}px`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
||||||
|
const importListId = importList ? importList.id : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.content}
|
className={styles.content}
|
||||||
style={contentStyle}
|
style={contentStyle}
|
||||||
>
|
>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
<Label className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{
|
<MonitorToggleButton
|
||||||
importListId > 0 ?
|
className={styles.action}
|
||||||
<IconButton
|
monitored={monitored}
|
||||||
className={styles.action}
|
size={20}
|
||||||
name={icons.EDIT}
|
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
||||||
title={translate('EditPerson')}
|
/>
|
||||||
onPress={this.onEditImportListPress}
|
</div>
|
||||||
/> :
|
|
||||||
<IconButton
|
|
||||||
className={styles.action}
|
|
||||||
name={icons.ADD}
|
|
||||||
title={translate('FollowPerson')}
|
|
||||||
onPress={this.onAddImportListPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={elementStyle}
|
style={elementStyle}
|
||||||
@@ -148,12 +140,8 @@ MovieCrewPoster.propTypes = {
|
|||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
posterHeight: PropTypes.number.isRequired,
|
posterHeight: PropTypes.number.isRequired,
|
||||||
importListId: PropTypes.number.isRequired,
|
importList: PropTypes.object,
|
||||||
onImportListSelect: PropTypes.func.isRequired
|
onImportListSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
MovieCrewPoster.defaultProps = {
|
|
||||||
importListId: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieCrewPoster;
|
export default MovieCrewPoster;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function createMapStateToProps() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: crew
|
items: _.uniqBy(crew, 'personName')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
$hoverScale: 1.05;
|
$hoverScale: 1.05;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
border-radius: '5px';
|
||||||
transition: all 200ms ease-in;
|
transition: all 200ms ease-in;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
box-shadow: 0 0 12px var(--black);
|
box-shadow: 0 0 12px var(--black);
|
||||||
transition: all 200ms ease-in;
|
transition: all 200ms ease-in;
|
||||||
|
|
||||||
.controls {
|
|
||||||
opacity: 0.9;
|
|
||||||
transition: opacity 200ms linear 150ms;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,22 +46,18 @@ $hoverScale: 1.05;
|
|||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
top: 10px;
|
||||||
left: 10px;
|
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #707070;
|
|
||||||
color: var(--white);
|
|
||||||
font-size: $smallFontSize;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
composes: button from '~Components/Link/IconButton.css';
|
composes: toggleButton from '~Components/MonitorToggleButton.css';
|
||||||
|
|
||||||
|
width: 25px;
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--radarrYellow);
|
color: var(--iconButtonHoverLightColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
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';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
import { selectImportListSchema, setImportListFieldValue, setImportListValue } from 'Store/Actions/settingsActions';
|
import { selectImportListSchema, setImportListFieldValue, setImportListValue } from 'Store/Actions/settingsActions';
|
||||||
import createMovieCreditListSelector from 'Store/Selectors/createMovieCreditListSelector';
|
import createMovieCreditListSelector from 'Store/Selectors/createMovieCreditListSelector';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createMovieCreditListSelector();
|
return createSelector(
|
||||||
|
createMovieCreditListSelector(),
|
||||||
|
(importList) => {
|
||||||
|
return {
|
||||||
|
importList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
@@ -20,7 +28,7 @@ class MovieCreditPosterConnector extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onImportListSelect = () => {
|
onImportListSelect = () => {
|
||||||
this.props.selectImportListSchema({ implementation: 'TMDbPersonImport', presetName: undefined });
|
this.props.selectImportListSchema({ implementation: 'TMDbPersonImport', implementationName: 'TMDb Person', presetName: undefined });
|
||||||
this.props.setImportListFieldValue({ name: 'personId', value: this.props.tmdbId.toString() });
|
this.props.setImportListFieldValue({ name: 'personId', value: this.props.tmdbId.toString() });
|
||||||
this.props.setImportListValue({ name: 'name', value: `${this.props.personName} - ${this.props.tmdbId}` });
|
this.props.setImportListValue({ name: 'name', value: `${this.props.personName} - ${this.props.tmdbId}` });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movie {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
interface CssExports {
|
interface CssExports {
|
||||||
'container': string;
|
'container': string;
|
||||||
'grid': string;
|
'grid': string;
|
||||||
|
'movie': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Grid, WindowScroller } from 'react-virtualized';
|
import { Navigation } from 'swiper';
|
||||||
import Measure from 'Components/Measure';
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
|
||||||
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
||||||
import styles from './MovieCreditPosters.css';
|
import styles from './MovieCreditPosters.css';
|
||||||
|
|
||||||
|
// Import Swiper styles
|
||||||
|
import 'swiper/css';
|
||||||
|
import 'swiper/css/navigation';
|
||||||
|
|
||||||
// Poster container dimensions
|
// Poster container dimensions
|
||||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||||
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
||||||
@@ -65,39 +68,11 @@ class MovieCreditPosters extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this._isInitialized = false;
|
this._isInitialized = false;
|
||||||
this._grid = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
const {
|
|
||||||
items
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
columnWidth,
|
|
||||||
columnCount,
|
|
||||||
rowHeight
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (this._grid &&
|
|
||||||
(prevState.width !== width ||
|
|
||||||
prevState.columnWidth !== columnWidth ||
|
|
||||||
prevState.columnCount !== columnCount ||
|
|
||||||
prevState.rowHeight !== rowHeight ||
|
|
||||||
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
|
||||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
|
||||||
this._grid.recomputeGridSize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
setGridRef = (ref) => {
|
|
||||||
this._grid = ref;
|
|
||||||
};
|
|
||||||
|
|
||||||
calculateGrid = (width = this.state.width, isSmallScreen) => {
|
calculateGrid = (width = this.state.width, isSmallScreen) => {
|
||||||
|
|
||||||
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||||
@@ -117,7 +92,10 @@ class MovieCreditPosters extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
cellRenderer = ({ key, rowIndex, columnIndex, style }) => {
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
itemComponent
|
itemComponent
|
||||||
@@ -126,99 +104,44 @@ class MovieCreditPosters extends Component {
|
|||||||
const {
|
const {
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
columnCount
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const movieIdx = rowIndex * columnCount + columnIndex;
|
|
||||||
const movie = items[movieIdx];
|
|
||||||
|
|
||||||
if (!movie) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.container}
|
|
||||||
key={key}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
<MovieCreditPosterConnector
|
|
||||||
key={movie.order}
|
|
||||||
component={itemComponent}
|
|
||||||
posterWidth={posterWidth}
|
|
||||||
posterHeight={posterHeight}
|
|
||||||
tmdbId={movie.personTmdbId}
|
|
||||||
personName={movie.personName}
|
|
||||||
job={movie.job}
|
|
||||||
character={movie.character}
|
|
||||||
images={movie.images}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onMeasure = ({ width }) => {
|
|
||||||
this.calculateGrid(width, this.props.isSmallScreen);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
items
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
columnWidth,
|
|
||||||
columnCount,
|
|
||||||
rowHeight
|
rowHeight
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const rowCount = Math.ceil(items.length / columnCount);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Measure
|
|
||||||
whitelist={['width']}
|
|
||||||
onMeasure={this.onMeasure}
|
|
||||||
>
|
|
||||||
<WindowScroller
|
|
||||||
scrollElement={undefined}
|
|
||||||
>
|
|
||||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
|
||||||
if (!height) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
<div className={styles.sliderContainer}>
|
||||||
<div ref={registerChild}>
|
<Swiper
|
||||||
<Grid
|
slidesPerView='auto'
|
||||||
ref={this.setGridRef}
|
spaceBetween={10}
|
||||||
className={styles.grid}
|
slidesPerGroup={3}
|
||||||
autoHeight={true}
|
loop={false}
|
||||||
height={height}
|
loopFillGroupWithBlank={true}
|
||||||
columnCount={columnCount}
|
className="mySwiper"
|
||||||
columnWidth={columnWidth}
|
modules={[Navigation]}
|
||||||
rowCount={rowCount}
|
onInit={(swiper) => {
|
||||||
rowHeight={rowHeight}
|
swiper.params.navigation.prevEl = this._swiperPrevRef;
|
||||||
width={width}
|
swiper.params.navigation.nextEl = this._swiperNextRef;
|
||||||
onScroll={onChildScroll}
|
swiper.navigation.init();
|
||||||
scrollTop={scrollTop}
|
swiper.navigation.update();
|
||||||
overscanRowCount={2}
|
}}
|
||||||
cellRenderer={this.cellRenderer}
|
>
|
||||||
scrollToAlignment={'start'}
|
{items.map((credit) => (
|
||||||
isScrollingOptOut={true}
|
<SwiperSlide key={credit.id} style={{ width: posterWidth, height: rowHeight }}>
|
||||||
/>
|
<MovieCreditPosterConnector
|
||||||
</div>
|
key={credit.id}
|
||||||
);
|
component={itemComponent}
|
||||||
}
|
posterWidth={posterWidth}
|
||||||
}
|
posterHeight={posterHeight}
|
||||||
</WindowScroller>
|
tmdbId={credit.personTmdbId}
|
||||||
</Measure>
|
personName={credit.personName}
|
||||||
|
job={credit.job}
|
||||||
|
character={credit.character}
|
||||||
|
images={credit.images}
|
||||||
|
/>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.alternateTitle {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import styles from './MovieAlternateTitles.css';
|
|
||||||
|
|
||||||
function MovieAlternateTitles({ alternateTitles }) {
|
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
alternateTitles.filter((x, i, a) => a.indexOf(x) === i).map((alternateTitle) => {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={alternateTitle}
|
|
||||||
className={styles.alternateTitle}
|
|
||||||
>
|
|
||||||
{alternateTitle}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieAlternateTitles.propTypes = {
|
|
||||||
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieAlternateTitles;
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
.header {
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 375px;
|
height: 425px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errorMessage {
|
.errorMessage {
|
||||||
@@ -39,10 +39,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
|
z-index: 2;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 35px;
|
margin-right: 35px;
|
||||||
width: 217px;
|
width: 250px;
|
||||||
height: 319px;
|
height: 368px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import ImdbRating from 'Components/ImdbRating';
|
import ImdbRating from 'Components/ImdbRating';
|
||||||
import InfoLabel from 'Components/InfoLabel';
|
import InfoLabel from 'Components/InfoLabel';
|
||||||
@@ -23,12 +23,11 @@ import Popover from 'Components/Tooltip/Popover';
|
|||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
|
||||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
|
||||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
|
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
|
import MovieInteractiveSearchModalConnector from 'Movie/Search/MovieInteractiveSearchModalConnector';
|
||||||
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
||||||
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
@@ -38,8 +37,6 @@ 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';
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
|
||||||
import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
|
import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
|
||||||
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
|
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
|
||||||
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
|
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
|
||||||
@@ -57,14 +54,6 @@ function getFanartUrl(images) {
|
|||||||
return _.find(images, { coverType: 'fanart' })?.url;
|
return _.find(images, { coverType: 'fanart' })?.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExpandedState(newState) {
|
|
||||||
return {
|
|
||||||
allExpanded: newState.allSelected,
|
|
||||||
allCollapsed: newState.allUnselected,
|
|
||||||
expandedState: newState.selectedState
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class MovieDetails extends Component {
|
class MovieDetails extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -78,10 +67,8 @@ class MovieDetails extends Component {
|
|||||||
isEditMovieModalOpen: false,
|
isEditMovieModalOpen: false,
|
||||||
isDeleteMovieModalOpen: false,
|
isDeleteMovieModalOpen: false,
|
||||||
isInteractiveImportModalOpen: false,
|
isInteractiveImportModalOpen: false,
|
||||||
allExpanded: false,
|
isInteractiveSearchModalOpen: false,
|
||||||
allCollapsed: false,
|
isMovieHistoryModalOpen: false,
|
||||||
expandedState: {},
|
|
||||||
selectedTabIndex: 0,
|
|
||||||
overviewHeight: 0,
|
overviewHeight: 0,
|
||||||
titleWidth: 0
|
titleWidth: 0
|
||||||
};
|
};
|
||||||
@@ -114,10 +101,6 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isOrganizeModalOpen: false });
|
this.setState({ isOrganizeModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onManageEpisodesPress = () => {
|
|
||||||
this.setState({ isManageEpisodesOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onInteractiveImportPress = () => {
|
onInteractiveImportPress = () => {
|
||||||
this.setState({ isInteractiveImportModalOpen: true });
|
this.setState({ isInteractiveImportModalOpen: true });
|
||||||
};
|
};
|
||||||
@@ -134,6 +117,14 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isEditMovieModalOpen: false });
|
this.setState({ isEditMovieModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onInteractiveSearchPress = () => {
|
||||||
|
this.setState({ isInteractiveSearchModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onInteractiveSearchModalClose = () => {
|
||||||
|
this.setState({ isInteractiveSearchModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
onDeleteMoviePress = () => {
|
onDeleteMoviePress = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditMovieModalOpen: false,
|
isEditMovieModalOpen: false,
|
||||||
@@ -145,27 +136,12 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isDeleteMovieModalOpen: false });
|
this.setState({ isDeleteMovieModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onExpandAllPress = () => {
|
onMovieHistoryPress = () => {
|
||||||
const {
|
this.setState({ isMovieHistoryModalOpen: true });
|
||||||
allExpanded,
|
|
||||||
expandedState
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onExpandPress = (seasonNumber, isExpanded) => {
|
onMovieHistoryModalClose = () => {
|
||||||
this.setState((state) => {
|
this.setState({ isMovieHistoryModalOpen: false });
|
||||||
const convertedState = {
|
|
||||||
allSelected: state.allExpanded,
|
|
||||||
allUnselected: state.allCollapsed,
|
|
||||||
selectedState: state.expandedState
|
|
||||||
};
|
|
||||||
|
|
||||||
const newState = toggleSelected(convertedState, [], seasonNumber, isExpanded, false);
|
|
||||||
|
|
||||||
return getExpandedState(newState);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMeasure = ({ height }) => {
|
onMeasure = ({ height }) => {
|
||||||
@@ -204,7 +180,12 @@ class MovieDetails extends Component {
|
|||||||
if (
|
if (
|
||||||
touchStart < 50 ||
|
touchStart < 50 ||
|
||||||
this.props.isSidebarVisible ||
|
this.props.isSidebarVisible ||
|
||||||
this.state.isEventModalOpen
|
this.state.isOrganizeModalOpen ||
|
||||||
|
this.state.isEditMovieModalOpen ||
|
||||||
|
this.state.isDeleteMovieModalOpen ||
|
||||||
|
this.state.isInteractiveImportModalOpen ||
|
||||||
|
this.state.isInteractiveSearchModalOpen ||
|
||||||
|
this.state.isMovieHistoryModalOpen
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -239,10 +220,6 @@ class MovieDetails extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onTabSelect = (index, lastIndex) => {
|
|
||||||
this.setState({ selectedTabIndex: index });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -295,9 +272,10 @@ class MovieDetails extends Component {
|
|||||||
isEditMovieModalOpen,
|
isEditMovieModalOpen,
|
||||||
isDeleteMovieModalOpen,
|
isDeleteMovieModalOpen,
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
|
isInteractiveSearchModalOpen,
|
||||||
|
isMovieHistoryModalOpen,
|
||||||
overviewHeight,
|
overviewHeight,
|
||||||
titleWidth,
|
titleWidth
|
||||||
selectedTabIndex
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const fanartUrl = getFanartUrl(images);
|
const fanartUrl = getFanartUrl(images);
|
||||||
@@ -324,6 +302,14 @@ class MovieDetails extends Component {
|
|||||||
onPress={onSearchPress}
|
onPress={onSearchPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('InteractiveSearch')}
|
||||||
|
iconName={icons.INTERACTIVE}
|
||||||
|
isSpinning={isSearching}
|
||||||
|
title={undefined}
|
||||||
|
onPress={this.onInteractiveSearchPress}
|
||||||
|
/>
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -339,6 +325,12 @@ class MovieDetails extends Component {
|
|||||||
onPress={this.onInteractiveImportPress}
|
onPress={this.onInteractiveImportPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('History')}
|
||||||
|
iconName={icons.HISTORY}
|
||||||
|
onPress={this.onMovieHistoryPress}
|
||||||
|
/>
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -654,101 +646,33 @@ class MovieDetails extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
<Tabs selectedIndex={selectedTabIndex} onSelect={this.onTabSelect}>
|
<FieldSet legend={translate('Files')}>
|
||||||
<TabList
|
<MovieFileEditorTable
|
||||||
className={styles.tabList}
|
movieId={id}
|
||||||
>
|
/>
|
||||||
<Tab
|
|
||||||
className={styles.tab}
|
|
||||||
selectedClassName={styles.selectedTab}
|
|
||||||
>
|
|
||||||
{translate('History')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<ExtraFileTable
|
||||||
className={styles.tab}
|
movieId={id}
|
||||||
selectedClassName={styles.selectedTab}
|
/>
|
||||||
>
|
</FieldSet>
|
||||||
{translate('Search')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<FieldSet legend={translate('Cast')}>
|
||||||
className={styles.tab}
|
<MovieCastPostersConnector
|
||||||
selectedClassName={styles.selectedTab}
|
isSmallScreen={isSmallScreen}
|
||||||
>
|
/>
|
||||||
{translate('Files')}
|
</FieldSet>
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<FieldSet legend={translate('Crew')}>
|
||||||
className={styles.tab}
|
<MovieCrewPostersConnector
|
||||||
selectedClassName={styles.selectedTab}
|
isSmallScreen={isSmallScreen}
|
||||||
>
|
/>
|
||||||
{translate('Titles')}
|
</FieldSet>
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
className={styles.tab}
|
|
||||||
selectedClassName={styles.selectedTab}
|
|
||||||
>
|
|
||||||
{translate('Cast')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
className={styles.tab}
|
|
||||||
selectedClassName={styles.selectedTab}
|
|
||||||
>
|
|
||||||
{translate('Crew')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
{
|
|
||||||
selectedTabIndex === 1 &&
|
|
||||||
<div className={styles.filterIcon}>
|
|
||||||
<InteractiveSearchFilterMenuConnector />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</TabList>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieHistoryTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<InteractiveSearchTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieFileEditorTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
<ExtraFileTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieTitlesTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieCastPostersConnector
|
|
||||||
isSmallScreen={isSmallScreen}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieCrewPostersConnector
|
|
||||||
isSmallScreen={isSmallScreen}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
|
<FieldSet legend={translate('Titles')}>
|
||||||
|
<MovieTitlesTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
|
</FieldSet>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OrganizePreviewModalConnector
|
<OrganizePreviewModalConnector
|
||||||
@@ -764,6 +688,12 @@ class MovieDetails extends Component {
|
|||||||
onDeleteMoviePress={this.onDeleteMoviePress}
|
onDeleteMoviePress={this.onDeleteMoviePress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MovieHistoryModal
|
||||||
|
isOpen={isMovieHistoryModalOpen}
|
||||||
|
movieId={id}
|
||||||
|
onModalClose={this.onMovieHistoryModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<DeleteMovieModal
|
<DeleteMovieModal
|
||||||
isOpen={isDeleteMovieModalOpen}
|
isOpen={isDeleteMovieModalOpen}
|
||||||
movieId={id}
|
movieId={id}
|
||||||
@@ -780,6 +710,12 @@ class MovieDetails extends Component {
|
|||||||
showImportMode={false}
|
showImportMode={false}
|
||||||
onModalClose={this.onInteractiveImportModalClose}
|
onModalClose={this.onInteractiveImportModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MovieInteractiveSearchModalConnector
|
||||||
|
isOpen={isInteractiveSearchModalOpen}
|
||||||
|
movieId={id}
|
||||||
|
onModalClose={this.onInteractiveSearchModalClose}
|
||||||
|
/>
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { toggleMovieMonitored } from 'Store/Actions/movieActions';
|
|||||||
import { clearMovieBlocklist, fetchMovieBlocklist } from 'Store/Actions/movieBlocklistActions';
|
import { clearMovieBlocklist, fetchMovieBlocklist } from 'Store/Actions/movieBlocklistActions';
|
||||||
import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
|
import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
|
||||||
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
|
||||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||||
import { fetchImportListSchema } from 'Store/Actions/settingsActions';
|
import { fetchImportListSchema } from 'Store/Actions/settingsActions';
|
||||||
@@ -182,12 +181,6 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatchClearMovieFiles() {
|
dispatchClearMovieFiles() {
|
||||||
dispatch(clearMovieFiles());
|
dispatch(clearMovieFiles());
|
||||||
},
|
},
|
||||||
dispatchFetchMovieHistory({ movieId }) {
|
|
||||||
dispatch(fetchMovieHistory({ movieId }));
|
|
||||||
},
|
|
||||||
dispatchClearMovieHistory() {
|
|
||||||
dispatch(clearMovieHistory());
|
|
||||||
},
|
|
||||||
dispatchFetchMovieCredits({ movieId }) {
|
dispatchFetchMovieCredits({ movieId }) {
|
||||||
dispatch(fetchMovieCredits({ movieId }));
|
dispatch(fetchMovieCredits({ movieId }));
|
||||||
},
|
},
|
||||||
@@ -283,7 +276,6 @@ class MovieDetailsConnector extends Component {
|
|||||||
|
|
||||||
this.props.dispatchFetchMovieFiles({ movieId });
|
this.props.dispatchFetchMovieFiles({ movieId });
|
||||||
this.props.dispatchFetchMovieBlocklist({ movieId });
|
this.props.dispatchFetchMovieBlocklist({ movieId });
|
||||||
this.props.dispatchFetchMovieHistory({ movieId });
|
|
||||||
this.props.dispatchFetchExtraFiles({ movieId });
|
this.props.dispatchFetchExtraFiles({ movieId });
|
||||||
this.props.dispatchFetchMovieCredits({ movieId });
|
this.props.dispatchFetchMovieCredits({ movieId });
|
||||||
this.props.dispatchFetchQueueDetails({ movieId });
|
this.props.dispatchFetchQueueDetails({ movieId });
|
||||||
@@ -294,7 +286,6 @@ class MovieDetailsConnector extends Component {
|
|||||||
this.props.dispatchCancelFetchReleases();
|
this.props.dispatchCancelFetchReleases();
|
||||||
this.props.dispatchClearMovieBlocklist();
|
this.props.dispatchClearMovieBlocklist();
|
||||||
this.props.dispatchClearMovieFiles();
|
this.props.dispatchClearMovieFiles();
|
||||||
this.props.dispatchClearMovieHistory();
|
|
||||||
this.props.dispatchClearExtraFiles();
|
this.props.dispatchClearExtraFiles();
|
||||||
this.props.dispatchClearMovieCredits();
|
this.props.dispatchClearMovieCredits();
|
||||||
this.props.dispatchClearQueueDetails();
|
this.props.dispatchClearQueueDetails();
|
||||||
@@ -351,8 +342,6 @@ MovieDetailsConnector.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
dispatchFetchMovieFiles: PropTypes.func.isRequired,
|
dispatchFetchMovieFiles: PropTypes.func.isRequired,
|
||||||
dispatchClearMovieFiles: PropTypes.func.isRequired,
|
dispatchClearMovieFiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchMovieHistory: PropTypes.func.isRequired,
|
|
||||||
dispatchClearMovieHistory: PropTypes.func.isRequired,
|
|
||||||
dispatchFetchExtraFiles: PropTypes.func.isRequired,
|
dispatchFetchExtraFiles: PropTypes.func.isRequired,
|
||||||
dispatchClearExtraFiles: PropTypes.func.isRequired,
|
dispatchClearExtraFiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchMovieCredits: PropTypes.func.isRequired,
|
dispatchFetchMovieCredits: PropTypes.func.isRequired,
|
||||||
|
|||||||
-1
@@ -1,5 +1,4 @@
|
|||||||
.container {
|
.container {
|
||||||
margin-top: 20px;
|
|
||||||
border: 1px solid var(--borderColor);
|
border: 1px solid var(--borderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--inputBackgroundColor);
|
background-color: var(--inputBackgroundColor);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
||||||
|
import styles from './MovieTitlesTable.css';
|
||||||
|
|
||||||
function MovieTitlesTable(props) {
|
function MovieTitlesTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -7,9 +8,11 @@ function MovieTitlesTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MovieTitlesTableContentConnector
|
<div className={styles.container}>
|
||||||
{...otherProps}
|
<MovieTitlesTableContentConnector
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import MovieHistoryModalContentConnector from './MovieHistoryModalContentConnector';
|
||||||
|
|
||||||
|
function MovieHistoryModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
size={sizes.EXTRA_LARGE}
|
||||||
|
>
|
||||||
|
<MovieHistoryModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieHistoryModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieHistoryModal;
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import MovieHistoryRowConnector from './MovieHistoryRowConnector';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'eventType',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sourceTitle',
|
||||||
|
label: () => translate('SourceTitle'),
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'languages',
|
||||||
|
label: () => translate('Languages'),
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quality',
|
||||||
|
label: () => translate('Quality'),
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormats',
|
||||||
|
label: () => translate('CustomFormats'),
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormatScore',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.SCORE,
|
||||||
|
title: () => translate('CustomFormatScore')
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
label: () => translate('Date'),
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
label: () => translate('Actions'),
|
||||||
|
isVisible: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class MovieHistoryModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
onMarkAsFailedPress,
|
||||||
|
onModalClose
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const hasItems = !!items.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
{translate('History')}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<Alert kind={kinds.DANGER}>{translate('HistoryLoadError')}</Alert>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !hasItems && !error &&
|
||||||
|
<div>{translate('NoHistory')}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && hasItems && !error &&
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
items.map((item) => {
|
||||||
|
return (
|
||||||
|
<MovieHistoryRowConnector
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
{translate('Close')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieHistoryModalContent.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onMarkAsFailedPress: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieHistoryModalContent;
|
||||||
+27
-6
@@ -2,8 +2,8 @@ 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';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
import { clearMovieHistory, fetchMovieHistory, movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
||||||
import MovieHistoryTableContent from './MovieHistoryTableContent';
|
import MovieHistoryModalContent from './MovieHistoryModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -15,10 +15,29 @@ function createMapStateToProps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
fetchMovieHistory,
|
||||||
|
clearMovieHistory,
|
||||||
movieHistoryMarkAsFailed
|
movieHistoryMarkAsFailed
|
||||||
};
|
};
|
||||||
|
|
||||||
class MovieHistoryTableContentConnector extends Component {
|
class MovieHistoryModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {
|
||||||
|
movieId
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
this.props.fetchMovieHistory({
|
||||||
|
movieId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.clearMovieHistory();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
@@ -39,7 +58,7 @@ class MovieHistoryTableContentConnector extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<MovieHistoryTableContent
|
<MovieHistoryModalContent
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||||
/>
|
/>
|
||||||
@@ -47,9 +66,11 @@ class MovieHistoryTableContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieHistoryTableContentConnector.propTypes = {
|
MovieHistoryModalContentConnector.propTypes = {
|
||||||
movieId: PropTypes.number.isRequired,
|
movieId: PropTypes.number.isRequired,
|
||||||
|
fetchMovieHistory: PropTypes.func.isRequired,
|
||||||
|
clearMovieHistory: PropTypes.func.isRequired,
|
||||||
movieHistoryMarkAsFailed: PropTypes.func.isRequired
|
movieHistoryMarkAsFailed: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryTableContentConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryModalContentConnector);
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector';
|
|
||||||
import styles from './MovieHistoryTable.css';
|
|
||||||
|
|
||||||
function MovieHistoryTable(props) {
|
|
||||||
const {
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<MovieHistoryTableContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieHistoryTable.propTypes = {
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieHistoryTable;
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import MovieHistoryRowConnector from './MovieHistoryRowConnector';
|
|
||||||
import styles from './MovieHistoryTableContent.css';
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
name: 'eventType',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sourceTitle',
|
|
||||||
label: () => translate('SourceTitle'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'languages',
|
|
||||||
label: () => translate('Languages'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'quality',
|
|
||||||
label: () => translate('Quality'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'customFormats',
|
|
||||||
label: () => translate('CustomFormats'),
|
|
||||||
isSortable: false,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'customFormatScore',
|
|
||||||
label: React.createElement(Icon, {
|
|
||||||
name: icons.SCORE,
|
|
||||||
title: 'Custom format score'
|
|
||||||
}),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'date',
|
|
||||||
label: () => translate('Date'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'actions',
|
|
||||||
label: React.createElement(IconButton, { name: icons.ADVANCED_SETTINGS }),
|
|
||||||
isVisible: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
class MovieHistoryTableContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items,
|
|
||||||
onMarkAsFailedPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const hasItems = !!items.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{
|
|
||||||
isFetching &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && !!error &&
|
|
||||||
<div className={styles.blankpad}>
|
|
||||||
{translate('UnableToLoadHistory')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && !hasItems && !error &&
|
|
||||||
<div className={styles.blankpad}>
|
|
||||||
{translate('NoHistory')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && hasItems && !error &&
|
|
||||||
<Table columns={columns}>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<MovieHistoryRowConnector
|
|
||||||
key={item.id}
|
|
||||||
{...item}
|
|
||||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieHistoryTableContent.propTypes = {
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieHistoryTableContent;
|
|
||||||
@@ -26,7 +26,7 @@ import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
|||||||
import { SelectStateInputProps } from 'typings/props';
|
import { SelectStateInputProps } from 'typings/props';
|
||||||
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 titleCase from 'Utilities/String/titleCase';
|
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import MovieIndexProgressBar from '../ProgressBar/MovieIndexProgressBar';
|
import MovieIndexProgressBar from '../ProgressBar/MovieIndexProgressBar';
|
||||||
import MovieStatusCell from './MovieStatusCell';
|
import MovieStatusCell from './MovieStatusCell';
|
||||||
@@ -286,7 +286,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
|||||||
if (name === 'minimumAvailability') {
|
if (name === 'minimumAvailability') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
{titleCase(minimumAvailability)}
|
{translate(firstCharToUpper(minimumAvailability))}
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import MovieInteractiveSearchModalContent from './MovieInteractiveSearchModalContent';
|
||||||
|
|
||||||
|
function MovieInteractiveSearchModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
movieId,
|
||||||
|
onModalClose
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeOnBackgroundClick={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
size={sizes.EXTRA_EXTRA_LARGE}
|
||||||
|
>
|
||||||
|
<MovieInteractiveSearchModalContent
|
||||||
|
movieId={movieId}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieInteractiveSearchModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
movieId: PropTypes.number.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieInteractiveSearchModal;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||||
|
import MovieInteractiveSearchModal from './MovieInteractiveSearchModal';
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
return {
|
||||||
|
dispatchCancelFetchReleases() {
|
||||||
|
dispatch(cancelFetchReleases());
|
||||||
|
},
|
||||||
|
|
||||||
|
dispatchClearReleases() {
|
||||||
|
dispatch(clearReleases());
|
||||||
|
},
|
||||||
|
|
||||||
|
onModalClose() {
|
||||||
|
dispatch(cancelFetchReleases());
|
||||||
|
dispatch(clearReleases());
|
||||||
|
props.onModalClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class MovieInteractiveSearchModalConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.dispatchCancelFetchReleases();
|
||||||
|
this.props.dispatchClearReleases();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatchCancelFetchReleases,
|
||||||
|
dispatchClearReleases,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MovieInteractiveSearchModal
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieInteractiveSearchModalConnector.propTypes = {
|
||||||
|
...MovieInteractiveSearchModal.propTypes,
|
||||||
|
dispatchCancelFetchReleases: PropTypes.func.isRequired,
|
||||||
|
dispatchClearReleases: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, createMapDispatchToProps)(MovieInteractiveSearchModalConnector);
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
|
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
function MovieInteractiveSearchModalContent(props) {
|
||||||
|
const {
|
||||||
|
movieId,
|
||||||
|
onModalClose
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
{translate('InteractiveSearchModalHeader')}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
||||||
|
<InteractiveSearchConnector
|
||||||
|
searchPayload={{ movieId }}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
{translate('Close')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieInteractiveSearchModalContent.propTypes = {
|
||||||
|
movieId: PropTypes.number.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieInteractiveSearchModalContent;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
.container {
|
.container {
|
||||||
margin-top: 20px;
|
|
||||||
border: 1px solid var(--borderColor);
|
border: 1px solid var(--borderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--inputBackgroundColor);
|
background-color: var(--inputBackgroundColor);
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class CustomFormat extends Component {
|
|||||||
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteCustomFormat')}
|
title={translate('DeleteCustomFormat')}
|
||||||
message={translate('DeleteCustomFormatMessageText', { name })}
|
message={translate('DeleteCustomFormatMessageText', { customFormatName: name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
isSpinning={isDeleting}
|
isSpinning={isDeleting}
|
||||||
onConfirm={this.onConfirmDeleteCustomFormat}
|
onConfirm={this.onConfirmDeleteCustomFormat}
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ class SecuritySettings extends Component {
|
|||||||
authenticationRequired,
|
authenticationRequired,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
apiKey,
|
apiKey,
|
||||||
certificateValidation
|
certificateValidation
|
||||||
} = settings;
|
} = settings;
|
||||||
@@ -139,8 +140,8 @@ class SecuritySettings extends Component {
|
|||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText={translate('AuthenticationMethodHelpText', { appName: 'Radarr' })}
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
helpTextWarning={translate('AuthenticationRequiredWarning', { appName: 'Radarr' })}
|
helpTextWarning={translate('AuthenticationRequiredWarning')}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...authenticationMethod}
|
{...authenticationMethod}
|
||||||
/>
|
/>
|
||||||
@@ -193,6 +194,21 @@ class SecuritySettings extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="passwordConfirmation"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...passwordConfirmation}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('ApiKey')}</FormLabel>
|
<FormLabel>{translate('ApiKey')}</FormLabel>
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ function UpdateSettings(props) {
|
|||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="updateAutomatically"
|
name="updateAutomatically"
|
||||||
helpText={translate('UpdateAutomaticallyHelpText')}
|
helpText={translate('UpdateAutomaticallyHelpText')}
|
||||||
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Radarr' }) : undefined}
|
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...updateAutomatically}
|
{...updateAutomatically}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+24
@@ -15,6 +15,7 @@ interface SavePayload {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
enableAuto?: boolean;
|
enableAuto?: boolean;
|
||||||
qualityProfileId?: number;
|
qualityProfileId?: number;
|
||||||
|
minimumAvailability?: string;
|
||||||
rootFolderPath?: string;
|
rootFolderPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ function ManageImportListsEditModalContent(
|
|||||||
const [qualityProfileId, setQualityProfileId] = useState<string | number>(
|
const [qualityProfileId, setQualityProfileId] = useState<string | number>(
|
||||||
NO_CHANGE
|
NO_CHANGE
|
||||||
);
|
);
|
||||||
|
const [minimumAvailability, setMinimumAvailability] = useState(NO_CHANGE);
|
||||||
const [rootFolderPath, setRootFolderPath] = useState(NO_CHANGE);
|
const [rootFolderPath, setRootFolderPath] = useState(NO_CHANGE);
|
||||||
|
|
||||||
const save = useCallback(() => {
|
const save = useCallback(() => {
|
||||||
@@ -79,6 +81,11 @@ function ManageImportListsEditModalContent(
|
|||||||
payload.qualityProfileId = qualityProfileId as number;
|
payload.qualityProfileId = qualityProfileId as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (minimumAvailability !== NO_CHANGE) {
|
||||||
|
hasChanges = true;
|
||||||
|
payload.minimumAvailability = minimumAvailability as string;
|
||||||
|
}
|
||||||
|
|
||||||
if (rootFolderPath !== NO_CHANGE) {
|
if (rootFolderPath !== NO_CHANGE) {
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
payload.rootFolderPath = rootFolderPath;
|
payload.rootFolderPath = rootFolderPath;
|
||||||
@@ -93,6 +100,7 @@ function ManageImportListsEditModalContent(
|
|||||||
enabled,
|
enabled,
|
||||||
enableAuto,
|
enableAuto,
|
||||||
qualityProfileId,
|
qualityProfileId,
|
||||||
|
minimumAvailability,
|
||||||
rootFolderPath,
|
rootFolderPath,
|
||||||
onSavePress,
|
onSavePress,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
@@ -110,6 +118,9 @@ function ManageImportListsEditModalContent(
|
|||||||
case 'qualityProfileId':
|
case 'qualityProfileId':
|
||||||
setQualityProfileId(value);
|
setQualityProfileId(value);
|
||||||
break;
|
break;
|
||||||
|
case 'minimumAvailability':
|
||||||
|
setMinimumAvailability(value);
|
||||||
|
break;
|
||||||
case 'rootFolderPath':
|
case 'rootFolderPath':
|
||||||
setRootFolderPath(value);
|
setRootFolderPath(value);
|
||||||
break;
|
break;
|
||||||
@@ -164,6 +175,19 @@ function ManageImportListsEditModalContent(
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.AVAILABILITY_SELECT}
|
||||||
|
name="minimumAvailability"
|
||||||
|
value={minimumAvailability}
|
||||||
|
includeNoChange={true}
|
||||||
|
includeNoChangeDisabled={false}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('RootFolder')}</FormLabel>
|
<FormLabel>{translate('RootFolder')}</FormLabel>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import ManageImportListsModalContent from './ManageImportListsModalContent';
|
import ManageImportListsModalContent from './ManageImportListsModalContent';
|
||||||
|
|
||||||
interface ManageImportListsModalProps {
|
interface ManageImportListsModalProps {
|
||||||
@@ -11,7 +12,7 @@ function ManageImportListsModal(props: ManageImportListsModalProps) {
|
|||||||
const { isOpen, onModalClose } = props;
|
const { isOpen, onModalClose } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
<Modal isOpen={isOpen} size={sizes.EXTRA_LARGE} onModalClose={onModalClose}>
|
||||||
<ManageImportListsModalContent onModalClose={onModalClose} />
|
<ManageImportListsModalContent onModalClose={onModalClose} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ const COLUMNS = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'minimumAvailability',
|
||||||
|
label: () => translate('MinimumAvailability'),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'rootFolderPath',
|
name: 'rootFolderPath',
|
||||||
label: () => translate('RootFolder'),
|
label: () => translate('RootFolder'),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
.tags,
|
.tags,
|
||||||
.enabled,
|
.enabled,
|
||||||
.enableAuto,
|
.enableAuto,
|
||||||
|
.minimumAvailability,
|
||||||
.qualityProfileId,
|
.qualityProfileId,
|
||||||
.rootFolderPath,
|
.rootFolderPath,
|
||||||
.implementation {
|
.implementation {
|
||||||
|
|||||||
+1
@@ -4,6 +4,7 @@ interface CssExports {
|
|||||||
'enableAuto': string;
|
'enableAuto': string;
|
||||||
'enabled': string;
|
'enabled': string;
|
||||||
'implementation': string;
|
'implementation': string;
|
||||||
|
'minimumAvailability': string;
|
||||||
'name': string;
|
'name': string;
|
||||||
'qualityProfileId': string;
|
'qualityProfileId': string;
|
||||||
'rootFolderPath': string;
|
'rootFolderPath': string;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TableRow from 'Components/Table/TableRow';
|
|||||||
import TagListConnector from 'Components/TagListConnector';
|
import TagListConnector from 'Components/TagListConnector';
|
||||||
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
||||||
import { SelectStateInputProps } from 'typings/props';
|
import { SelectStateInputProps } from 'typings/props';
|
||||||
|
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './ManageImportListsModalRow.css';
|
import styles from './ManageImportListsModalRow.css';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ interface ManageImportListsModalRowProps {
|
|||||||
name: string;
|
name: string;
|
||||||
rootFolderPath: string;
|
rootFolderPath: string;
|
||||||
qualityProfileId: number;
|
qualityProfileId: number;
|
||||||
|
minimumAvailability: string;
|
||||||
implementation: string;
|
implementation: string;
|
||||||
tags: number[];
|
tags: number[];
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -30,6 +32,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
|
|||||||
isSelected,
|
isSelected,
|
||||||
name,
|
name,
|
||||||
rootFolderPath,
|
rootFolderPath,
|
||||||
|
minimumAvailability,
|
||||||
qualityProfileId,
|
qualityProfileId,
|
||||||
implementation,
|
implementation,
|
||||||
enabled,
|
enabled,
|
||||||
@@ -69,6 +72,10 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
|
|||||||
{qualityProfile?.name ?? translate('None')}
|
{qualityProfile?.name ?? translate('None')}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.minimumAvailability}>
|
||||||
|
{translate(firstCharToUpper(minimumAvailability))}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.rootFolderPath}>
|
<TableRowCell className={styles.rootFolderPath}>
|
||||||
{rootFolderPath}
|
{rootFolderPath}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default {
|
|||||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||||
selectedSchema.name = payload.presetName ?? payload.implementationName;
|
selectedSchema.name = payload.presetName ?? payload.implementationName;
|
||||||
selectedSchema.implementationName = payload.implementationName;
|
selectedSchema.implementationName = payload.implementationName;
|
||||||
selectedSchema.minRefreshInterval = payload.minRefreshInterval;
|
selectedSchema.minRefreshInterval = selectedSchema.minRefreshInterval ?? payload.minRefreshInterval;
|
||||||
selectedSchema.minimumAvailability = 'released';
|
selectedSchema.minimumAvailability = 'released';
|
||||||
selectedSchema.rootFolderPath = '';
|
selectedSchema.rootFolderPath = '';
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ export const actionHandlers = handleThunks({
|
|||||||
|
|
||||||
promise.done((data) => {
|
promise.done((data) => {
|
||||||
const updatedItem = _.cloneDeep(data);
|
const updatedItem = _.cloneDeep(data);
|
||||||
|
updatedItem.internalId = updatedItem.id;
|
||||||
updatedItem.id = updatedItem.tmdbId;
|
updatedItem.id = updatedItem.tmdbId;
|
||||||
|
delete updatedItem.images;
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
updateItem({ section: 'movies', ...data }),
|
updateItem({ section: 'movies', ...data }),
|
||||||
updateItem({ section: 'addMovie', ...updatedItem }),
|
updateItem({ section: 'addMovie', ...updatedItem }),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment/moment';
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { batchActions } from 'redux-batched-actions';
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
|
||||||
@@ -219,6 +220,42 @@ export const defaultState = {
|
|||||||
const { ratings = {} } = item;
|
const { ratings = {} } = item;
|
||||||
|
|
||||||
return ratings.tmdb? ratings.tmdb.value : 0;
|
return ratings.tmdb? ratings.tmdb.value : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
inCinemas: function(item, direction) {
|
||||||
|
if (item.inCinemas) {
|
||||||
|
return moment(item.inCinemas).unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === sortDirections.DESCENDING) {
|
||||||
|
return -1 * Number.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number.MAX_VALUE;
|
||||||
|
},
|
||||||
|
|
||||||
|
physicalRelease: function(item, direction) {
|
||||||
|
if (item.physicalRelease) {
|
||||||
|
return moment(item.physicalRelease).unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === sortDirections.DESCENDING) {
|
||||||
|
return -1 * Number.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number.MAX_VALUE;
|
||||||
|
},
|
||||||
|
|
||||||
|
digitalRelease: function(item, direction) {
|
||||||
|
if (item.digitalRelease) {
|
||||||
|
return moment(item.digitalRelease).unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === sortDirections.DESCENDING) {
|
||||||
|
return -1 * Number.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number.MAX_VALUE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const defaultState = {
|
|||||||
columnLabel: () => translate('CustomFormatScore'),
|
columnLabel: () => translate('CustomFormatScore'),
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
name: icons.SCORE,
|
name: icons.SCORE,
|
||||||
title: 'Custom format score'
|
title: () => translate('CustomFormatScore')
|
||||||
}),
|
}),
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
function createMovieCollectionListSelector() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { tmdbId }) => tmdbId,
|
|
||||||
(state) => state.settings.importLists.items,
|
|
||||||
(tmdbId, importLists) => {
|
|
||||||
const importListIds = _.reduce(importLists, (acc, list) => {
|
|
||||||
if (list.implementation === 'TMDbCollectionImport') {
|
|
||||||
const collectionIdField = list.fields.find((field) => {
|
|
||||||
return field.name === 'collectionId';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (collectionIdField && parseInt(collectionIdField.value) === tmdbId) {
|
|
||||||
acc.push(list);
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (importListIds.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return importListIds[0];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createMovieCollectionListSelector;
|
|
||||||
@@ -21,15 +21,11 @@ function createMovieCreditListSelector() {
|
|||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let importListId = 0;
|
if (importListIds.length === 0) {
|
||||||
|
return undefined;
|
||||||
if (importListIds.length > 0) {
|
|
||||||
importListId = importListIds[0].id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return importListIds[0];
|
||||||
importListId
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { render } from 'react-dom';
|
|||||||
import createAppStore from 'Store/createAppStore';
|
import createAppStore from 'Store/createAppStore';
|
||||||
import App from './App/App';
|
import App from './App/App';
|
||||||
|
|
||||||
|
import 'Diag/ConsoleApi';
|
||||||
|
|
||||||
export async function bootstrap() {
|
export async function bootstrap() {
|
||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
const store = createAppStore(history);
|
const store = createAppStore(history);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface ImportList extends ModelBase {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
enableAuto: boolean;
|
enableAuto: boolean;
|
||||||
qualityProfileId: number;
|
qualityProfileId: number;
|
||||||
|
minimumAvailability: string;
|
||||||
rootFolderPath: string;
|
rootFolderPath: string;
|
||||||
name: string;
|
name: string;
|
||||||
fields: Field[];
|
fields: Field[];
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@juggle/resize-observer": "3.4.0",
|
"@juggle/resize-observer": "3.4.0",
|
||||||
"@microsoft/signalr": "6.0.21",
|
"@microsoft/signalr": "6.0.25",
|
||||||
"@sentry/browser": "7.51.2",
|
"@sentry/browser": "7.51.2",
|
||||||
"@sentry/integrations": "7.51.2",
|
"@sentry/integrations": "7.51.2",
|
||||||
"@types/node": "18.16.8",
|
"@types/node": "18.16.8",
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DryIoc.dll" Version="5.4.1" />
|
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NLog" Version="5.2.3" />
|
<PackageReference Include="NLog" Version="5.2.3" />
|
||||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.3" />
|
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.3" />
|
||||||
<PackageReference Include="Npgsql" Version="7.0.4" />
|
<PackageReference Include="Npgsql" Version="7.0.6" />
|
||||||
<PackageReference Include="Sentry" Version="3.23.1" />
|
<PackageReference Include="Sentry" Version="3.23.1" />
|
||||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageReference Include="System.Text.Json" Version="6.0.8" />
|
<PackageReference Include="System.Text.Json" Version="6.0.9" />
|
||||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
|||||||
[TestCase(HdrFormat.Hdr10Plus, "HDR10Plus")]
|
[TestCase(HdrFormat.Hdr10Plus, "HDR10Plus")]
|
||||||
[TestCase(HdrFormat.DolbyVision, "DV")]
|
[TestCase(HdrFormat.DolbyVision, "DV")]
|
||||||
[TestCase(HdrFormat.DolbyVisionHdr10, "DV HDR10")]
|
[TestCase(HdrFormat.DolbyVisionHdr10, "DV HDR10")]
|
||||||
|
[TestCase(HdrFormat.DolbyVisionHdr10Plus, "DV HDR10Plus")]
|
||||||
[TestCase(HdrFormat.DolbyVisionHlg, "DV HLG")]
|
[TestCase(HdrFormat.DolbyVisionHlg, "DV HLG")]
|
||||||
[TestCase(HdrFormat.DolbyVisionSdr, "DV SDR")]
|
[TestCase(HdrFormat.DolbyVisionSdr, "DV SDR")]
|
||||||
public void should_format_video_dynamic_range_type(HdrFormat format, string expectedVideoDynamicRangeType)
|
public void should_format_video_dynamic_range_type(HdrFormat format, string expectedVideoDynamicRangeType)
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
|||||||
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", null, HdrFormat.Hdr10Plus)]
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", null, HdrFormat.Hdr10Plus)]
|
||||||
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", null, HdrFormat.DolbyVision)]
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", null, HdrFormat.DolbyVision)]
|
||||||
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 1, HdrFormat.DolbyVisionHdr10)]
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 1, HdrFormat.DolbyVisionHdr10)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData,FFMpegCore.HdrDynamicMetadataSpmte2094", 1, HdrFormat.DolbyVisionHdr10Plus)]
|
||||||
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData,FFMpegCore.HdrDynamicMetadataSpmte2094", 6, HdrFormat.DolbyVisionHdr10Plus)]
|
||||||
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 2, HdrFormat.DolbyVisionSdr)]
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 2, HdrFormat.DolbyVisionSdr)]
|
||||||
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 4, HdrFormat.DolbyVisionHlg)]
|
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 4, HdrFormat.DolbyVisionHlg)]
|
||||||
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, int? doviConfigId, HdrFormat expected)
|
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, int? doviConfigId, HdrFormat expected)
|
||||||
|
|||||||
@@ -48,13 +48,14 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||||||
Subject.Definition = _traktDefinition;
|
Subject.Definition = _traktDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType)
|
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType, HdrFormat hdrFormat = HdrFormat.None)
|
||||||
{
|
{
|
||||||
_downloadMessage.MovieFile.MediaInfo = new MediaInfoModel
|
_downloadMessage.MovieFile.MediaInfo = new MediaInfoModel
|
||||||
{
|
{
|
||||||
AudioChannelPositions = audioChannels,
|
AudioChannelPositions = audioChannels,
|
||||||
AudioFormat = audioFormat,
|
AudioFormat = audioFormat,
|
||||||
ScanType = scanType
|
ScanType = scanType,
|
||||||
|
VideoHdrFormat = hdrFormat
|
||||||
};
|
};
|
||||||
|
|
||||||
_downloadMessage.MovieFile.Quality.Quality = quality;
|
_downloadMessage.MovieFile.Quality.Quality = quality;
|
||||||
@@ -72,7 +73,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_add_collection_movie_if_valid_mediainfo()
|
public void should_add_collection_movie_if_valid_mediainfo()
|
||||||
{
|
{
|
||||||
GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
|
GiventValidMediaInfo(Quality.Bluray2160p, "5.1", "DTS", "Progressive", HdrFormat.DolbyVisionHdr10);
|
||||||
|
|
||||||
Subject.OnDownload(_downloadMessage);
|
Subject.OnDownload(_downloadMessage);
|
||||||
|
|
||||||
@@ -80,15 +81,16 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||||||
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||||
t.Movies.First().Audio == "dts" &&
|
t.Movies.First().Audio == "dts" &&
|
||||||
t.Movies.First().AudioChannels == "5.1" &&
|
t.Movies.First().AudioChannels == "5.1" &&
|
||||||
t.Movies.First().Resolution == "hd_1080p" &&
|
t.Movies.First().Resolution == "uhd_4k" &&
|
||||||
t.Movies.First().MediaType == "bluray"),
|
t.Movies.First().MediaType == "bluray" &&
|
||||||
|
t.Movies.First().Hdr == "hdr10"),
|
||||||
It.IsAny<string>()), Times.Once());
|
It.IsAny<string>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_format_audio_channels_to_one_decimal_when_adding_collection_movie()
|
public void should_format_audio_channels_to_one_decimal_when_adding_collection_movie()
|
||||||
{
|
{
|
||||||
GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
|
GiventValidMediaInfo(Quality.Bluray2160p, "2.0", "DTS", "Progressive", HdrFormat.DolbyVisionHdr10);
|
||||||
|
|
||||||
Subject.OnDownload(_downloadMessage);
|
Subject.OnDownload(_downloadMessage);
|
||||||
|
|
||||||
@@ -96,8 +98,9 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||||||
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||||
t.Movies.First().Audio == "dts" &&
|
t.Movies.First().Audio == "dts" &&
|
||||||
t.Movies.First().AudioChannels == "2.0" &&
|
t.Movies.First().AudioChannels == "2.0" &&
|
||||||
t.Movies.First().Resolution == "hd_1080p" &&
|
t.Movies.First().Resolution == "uhd_4k" &&
|
||||||
t.Movies.First().MediaType == "bluray"),
|
t.Movies.First().MediaType == "bluray" &&
|
||||||
|
t.Movies.First().Hdr == "hdr10"),
|
||||||
It.IsAny<string>()), Times.Once());
|
It.IsAny<string>()), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Core.Movies;
|
using NzbDrone.Core.Movies;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
@@ -27,7 +28,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
|||||||
|
|
||||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||||
{
|
{
|
||||||
return movie.MovieMetadata.Value.Genres.Any(genre => Value.Contains(genre));
|
return movie?.MovieMetadata?.Value?.Genres.Any(genre => Value.ContainsIgnoreCase(genre)) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override NzbDroneValidationResult Validate()
|
public override NzbDroneValidationResult Validate()
|
||||||
|
|||||||
@@ -323,6 +323,20 @@ namespace NzbDrone.Core.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void MigrateConfigFile()
|
||||||
|
{
|
||||||
|
if (!File.Exists(_configFile))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If SSL is enabled and a cert hash is still in the config file disable SSL
|
||||||
|
if (EnableSsl && GetValue("SslCertHash", null).IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
SetValue("EnableSsl", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DeleteOldValues()
|
private void DeleteOldValues()
|
||||||
{
|
{
|
||||||
var xDoc = LoadConfigFile();
|
var xDoc = LoadConfigFile();
|
||||||
@@ -394,6 +408,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
|
|
||||||
public void HandleAsync(ApplicationStartedEvent message)
|
public void HandleAsync(ApplicationStartedEvent message)
|
||||||
{
|
{
|
||||||
|
MigrateConfigFile();
|
||||||
EnsureDefaultConfigFile();
|
EnsureDefaultConfigFile();
|
||||||
DeleteOldValues();
|
DeleteOldValues();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
public interface IConnectionStringFactory
|
public interface IConnectionStringFactory
|
||||||
{
|
{
|
||||||
string MainDbConnectionString { get; }
|
DatabaseConnectionInfo MainDbConnection { get; }
|
||||||
string LogDbConnectionString { get; }
|
DatabaseConnectionInfo LogDbConnection { get; }
|
||||||
string GetDatabasePath(string connectionString);
|
string GetDatabasePath(string connectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,15 +22,15 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
_configFileProvider = configFileProvider;
|
_configFileProvider = configFileProvider;
|
||||||
|
|
||||||
MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
|
MainDbConnection = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
|
||||||
GetConnectionString(appFolderInfo.GetDatabase());
|
GetConnectionString(appFolderInfo.GetDatabase());
|
||||||
|
|
||||||
LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
|
LogDbConnection = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
|
||||||
GetConnectionString(appFolderInfo.GetLogDatabase());
|
GetConnectionString(appFolderInfo.GetLogDatabase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MainDbConnectionString { get; private set; }
|
public DatabaseConnectionInfo MainDbConnection { get; private set; }
|
||||||
public string LogDbConnectionString { get; private set; }
|
public DatabaseConnectionInfo LogDbConnection { get; private set; }
|
||||||
|
|
||||||
public string GetDatabasePath(string connectionString)
|
public string GetDatabasePath(string connectionString)
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
return connectionBuilder.DataSource;
|
return connectionBuilder.DataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetConnectionString(string dbPath)
|
private static DatabaseConnectionInfo GetConnectionString(string dbPath)
|
||||||
{
|
{
|
||||||
var connectionBuilder = new SQLiteConnectionStringBuilder
|
var connectionBuilder = new SQLiteConnectionStringBuilder
|
||||||
{
|
{
|
||||||
@@ -57,21 +57,22 @@ namespace NzbDrone.Core.Datastore
|
|||||||
connectionBuilder.Add("Full FSync", true);
|
connectionBuilder.Add("Full FSync", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionBuilder.ConnectionString;
|
return new DatabaseConnectionInfo(DatabaseType.SQLite, connectionBuilder.ConnectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPostgresConnectionString(string dbName)
|
private DatabaseConnectionInfo GetPostgresConnectionString(string dbName)
|
||||||
{
|
{
|
||||||
var connectionBuilder = new NpgsqlConnectionStringBuilder();
|
var connectionBuilder = new NpgsqlConnectionStringBuilder
|
||||||
|
{
|
||||||
|
Database = dbName,
|
||||||
|
Host = _configFileProvider.PostgresHost,
|
||||||
|
Username = _configFileProvider.PostgresUser,
|
||||||
|
Password = _configFileProvider.PostgresPassword,
|
||||||
|
Port = _configFileProvider.PostgresPort,
|
||||||
|
Enlist = false
|
||||||
|
};
|
||||||
|
|
||||||
connectionBuilder.Database = dbName;
|
return new DatabaseConnectionInfo(DatabaseType.PostgreSQL, connectionBuilder.ConnectionString);
|
||||||
connectionBuilder.Host = _configFileProvider.PostgresHost;
|
|
||||||
connectionBuilder.Username = _configFileProvider.PostgresUser;
|
|
||||||
connectionBuilder.Password = _configFileProvider.PostgresPassword;
|
|
||||||
connectionBuilder.Port = _configFileProvider.PostgresPort;
|
|
||||||
connectionBuilder.Enlist = false;
|
|
||||||
|
|
||||||
return connectionBuilder.ConnectionString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace NzbDrone.Core.Datastore
|
||||||
|
{
|
||||||
|
public class DatabaseConnectionInfo
|
||||||
|
{
|
||||||
|
public DatabaseConnectionInfo(DatabaseType databaseType, string connectionString)
|
||||||
|
{
|
||||||
|
DatabaseType = databaseType;
|
||||||
|
ConnectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType { get; internal set; }
|
||||||
|
public string ConnectionString { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@@ -60,22 +61,22 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public IDatabase Create(MigrationContext migrationContext)
|
public IDatabase Create(MigrationContext migrationContext)
|
||||||
{
|
{
|
||||||
string connectionString;
|
DatabaseConnectionInfo connectionInfo;
|
||||||
|
|
||||||
switch (migrationContext.MigrationType)
|
switch (migrationContext.MigrationType)
|
||||||
{
|
{
|
||||||
case MigrationType.Main:
|
case MigrationType.Main:
|
||||||
{
|
{
|
||||||
connectionString = _connectionStringFactory.MainDbConnectionString;
|
connectionInfo = _connectionStringFactory.MainDbConnection;
|
||||||
CreateMain(connectionString, migrationContext);
|
CreateMain(connectionInfo.ConnectionString, migrationContext, connectionInfo.DatabaseType);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MigrationType.Log:
|
case MigrationType.Log:
|
||||||
{
|
{
|
||||||
connectionString = _connectionStringFactory.LogDbConnectionString;
|
connectionInfo = _connectionStringFactory.LogDbConnection;
|
||||||
CreateLog(connectionString, migrationContext);
|
CreateLog(connectionInfo.ConnectionString, migrationContext, connectionInfo.DatabaseType);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -90,14 +91,14 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
DbConnection conn;
|
DbConnection conn;
|
||||||
|
|
||||||
if (connectionString.Contains(".db"))
|
if (connectionInfo.DatabaseType == DatabaseType.SQLite)
|
||||||
{
|
{
|
||||||
conn = SQLiteFactory.Instance.CreateConnection();
|
conn = SQLiteFactory.Instance.CreateConnection();
|
||||||
conn.ConnectionString = connectionString;
|
conn.ConnectionString = connectionInfo.ConnectionString;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
conn = new NpgsqlConnection(connectionString);
|
conn = new NpgsqlConnection(connectionInfo.ConnectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.Open();
|
conn.Open();
|
||||||
@@ -107,12 +108,12 @@ namespace NzbDrone.Core.Datastore
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateMain(string connectionString, MigrationContext migrationContext)
|
private void CreateMain(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_restoreDatabaseService.Restore();
|
_restoreDatabaseService.Restore();
|
||||||
_migrationController.Migrate(connectionString, migrationContext);
|
_migrationController.Migrate(connectionString, migrationContext, databaseType);
|
||||||
}
|
}
|
||||||
catch (SQLiteException e)
|
catch (SQLiteException e)
|
||||||
{
|
{
|
||||||
@@ -135,15 +136,17 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
|
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
|
||||||
|
|
||||||
|
Thread.Sleep(5000);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_migrationController.Migrate(connectionString, migrationContext);
|
_migrationController.Migrate(connectionString, migrationContext, databaseType);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (--retryCount > 0)
|
if (--retryCount > 0)
|
||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep(5000);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +165,11 @@ namespace NzbDrone.Core.Datastore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateLog(string connectionString, MigrationContext migrationContext)
|
private void CreateLog(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_migrationController.Migrate(connectionString, migrationContext);
|
_migrationController.Migrate(connectionString, migrationContext, databaseType);
|
||||||
}
|
}
|
||||||
catch (SQLiteException e)
|
catch (SQLiteException e)
|
||||||
{
|
{
|
||||||
@@ -186,7 +189,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
Logger.Error("Unable to recreate logging database automatically. It will need to be removed manually.");
|
Logger.Error("Unable to recreate logging database automatically. It will need to be removed manually.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_migrationController.Migrate(connectionString, migrationContext);
|
_migrationController.Migrate(connectionString, migrationContext, databaseType);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||||||
{
|
{
|
||||||
public interface IMigrationController
|
public interface IMigrationController
|
||||||
{
|
{
|
||||||
void Migrate(string connectionString, MigrationContext migrationContext);
|
void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MigrationController : IMigrationController
|
public class MigrationController : IMigrationController
|
||||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||||||
_migrationLoggerProvider = migrationLoggerProvider;
|
_migrationLoggerProvider = migrationLoggerProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Migrate(string connectionString, MigrationContext migrationContext)
|
public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||||||
|
|
||||||
ServiceProvider serviceProvider;
|
ServiceProvider serviceProvider;
|
||||||
|
|
||||||
var db = connectionString.Contains(".db") ? "sqlite" : "postgres";
|
var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres";
|
||||||
|
|
||||||
serviceProvider = new ServiceCollection()
|
serviceProvider = new ServiceCollection()
|
||||||
.AddLogging(b => b.AddNLog())
|
.AddLogging(b => b.AddNLog())
|
||||||
|
|||||||
@@ -78,7 +78,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Debug("New item has a better custom format score");
|
_logger.Debug("New item's custom formats [{0}] ({1}) improve on [{2}] ({3}), accepting",
|
||||||
|
newCustomFormats.ConcatToString(),
|
||||||
|
newFormatScore,
|
||||||
|
currentCustomFormats.ConcatToString(),
|
||||||
|
currentFormatScore);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Dapper;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
public class CleanupOrphanedMovies : IHousekeepingTask
|
||||||
|
{
|
||||||
|
private readonly IMainDatabase _database;
|
||||||
|
|
||||||
|
public CleanupOrphanedMovies(IMainDatabase database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clean()
|
||||||
|
{
|
||||||
|
using var mapper = _database.OpenConnection();
|
||||||
|
mapper.Execute(@"DELETE FROM ""Movies""
|
||||||
|
WHERE ""Id"" IN (
|
||||||
|
SELECT ""Movies"".""Id"" FROM ""Movies""
|
||||||
|
LEFT OUTER JOIN ""MovieMetadata"" ON ""Movies"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
|
||||||
|
WHERE ""MovieMetadata"".""Id"" IS NULL)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ namespace NzbDrone.Core.ImportLists.RSSImport
|
|||||||
EnableAuto = true,
|
EnableAuto = true,
|
||||||
QualityProfileId = 1,
|
QualityProfileId = 1,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
|
MinRefreshInterval = MinRefreshInterval,
|
||||||
Settings = new RSSImportSettings { Link = "https://rss.imdb.com/list/YOURLISTID" },
|
Settings = new RSSImportSettings { Link = "https://rss.imdb.com/list/YOURLISTID" },
|
||||||
};
|
};
|
||||||
yield return new ImportListDefinition
|
yield return new ImportListDefinition
|
||||||
@@ -47,6 +48,7 @@ namespace NzbDrone.Core.ImportLists.RSSImport
|
|||||||
EnableAuto = true,
|
EnableAuto = true,
|
||||||
QualityProfileId = 1,
|
QualityProfileId = 1,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
|
MinRefreshInterval = MinRefreshInterval,
|
||||||
Settings = new RSSImportSettings { Link = "https://rss.imdb.com/user/IMDBUSERID/watchlist" },
|
Settings = new RSSImportSettings { Link = "https://rss.imdb.com/user/IMDBUSERID/watchlist" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
|
|||||||
EnableAuto = true,
|
EnableAuto = true,
|
||||||
QualityProfileId = 1,
|
QualityProfileId = 1,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
|
MinRefreshInterval = MinRefreshInterval,
|
||||||
Settings = new IMDbListSettings { ListId = "top250" },
|
Settings = new IMDbListSettings { ListId = "top250" },
|
||||||
};
|
};
|
||||||
yield return new ImportListDefinition
|
yield return new ImportListDefinition
|
||||||
@@ -56,6 +57,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
|
|||||||
EnableAuto = true,
|
EnableAuto = true,
|
||||||
QualityProfileId = 1,
|
QualityProfileId = 1,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
|
MinRefreshInterval = MinRefreshInterval,
|
||||||
Settings = new IMDbListSettings { ListId = "popular" },
|
Settings = new IMDbListSettings { ListId = "popular" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
@@ -28,15 +29,15 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||||||
BaseUrl = "https://filelist.io";
|
BaseUrl = "https://filelist.io";
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
|
|
||||||
Categories = new int[]
|
Categories = new[]
|
||||||
{
|
{
|
||||||
(int)FileListCategories.Movie_HD,
|
(int)FileListCategories.Movie_HD,
|
||||||
(int)FileListCategories.Movie_SD,
|
(int)FileListCategories.Movie_SD,
|
||||||
(int)FileListCategories.Movie_4K
|
(int)FileListCategories.Movie_4K
|
||||||
};
|
};
|
||||||
|
|
||||||
MultiLanguages = new List<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
RequiredFlags = new List<int>();
|
RequiredFlags = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
|
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
|
||||||
@@ -57,7 +58,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||||||
[FieldDefinition(5, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
|
[FieldDefinition(5, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
|
||||||
public int MinimumSeeders { get; set; }
|
public int MinimumSeeders { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Type = FieldType.TagSelect, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
|
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
|
||||||
public IEnumerable<int> RequiredFlags { get; set; }
|
public IEnumerable<int> RequiredFlags { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7)]
|
[FieldDefinition(7)]
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||||||
Categories = new[] { (int)HdBitsCategory.Movie };
|
Categories = new[] { (int)HdBitsCategory.Movie };
|
||||||
Codecs = Array.Empty<int>();
|
Codecs = Array.Empty<int>();
|
||||||
Mediums = Array.Empty<int>();
|
Mediums = Array.Empty<int>();
|
||||||
MultiLanguages = new List<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
RequiredFlags = new List<int>();
|
RequiredFlags = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]
|
[FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]
|
||||||
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||||||
[FieldDefinition(7, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
|
[FieldDefinition(7, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
|
||||||
public int MinimumSeeders { get; set; }
|
public int MinimumSeeders { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(8, Type = FieldType.TagSelect, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
|
[FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
|
||||||
public IEnumerable<int> RequiredFlags { get; set; }
|
public IEnumerable<int> RequiredFlags { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(9)]
|
[FieldDefinition(9)]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
@@ -33,8 +34,8 @@ namespace NzbDrone.Core.Indexers.IPTorrents
|
|||||||
{
|
{
|
||||||
BaseUrl = string.Empty;
|
BaseUrl = string.Empty;
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
MultiLanguages = new List<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
RequiredFlags = new List<int>();
|
RequiredFlags = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Feed URL", HelpText = "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)")]
|
[FieldDefinition(0, Label = "Feed URL", HelpText = "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)")]
|
||||||
@@ -46,7 +47,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents
|
|||||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
|
[FieldDefinition(2, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
|
||||||
public int MinimumSeeders { get; set; }
|
public int MinimumSeeders { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(3, Type = FieldType.TagSelect, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
|
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
|
||||||
public IEnumerable<int> RequiredFlags { get; set; }
|
public IEnumerable<int> RequiredFlags { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(4)]
|
[FieldDefinition(4)]
|
||||||
|
|||||||
@@ -48,13 +48,11 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||||||
{
|
{
|
||||||
yield return GetDefinition("DOGnzb", GetSettings("https://api.dognzb.cr"));
|
yield return GetDefinition("DOGnzb", GetSettings("https://api.dognzb.cr"));
|
||||||
yield return GetDefinition("DrunkenSlug", GetSettings("https://drunkenslug.com"));
|
yield return GetDefinition("DrunkenSlug", GetSettings("https://drunkenslug.com"));
|
||||||
yield return GetDefinition("Nzb-Tortuga", GetSettings("https://www.nzb-tortuga.com"));
|
|
||||||
yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su"));
|
yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su"));
|
||||||
yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat"));
|
yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat"));
|
||||||
yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws", categories: new[] { 2030, 2040, 2045, 2050, 2060, 2070, 2080, 2090 }));
|
yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws", categories: new[] { 2030, 2040, 2045, 2050, 2060, 2070 }));
|
||||||
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));
|
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));
|
||||||
yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net"));
|
yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net"));
|
||||||
yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com"));
|
|
||||||
yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com"));
|
yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com"));
|
||||||
yield return GetDefinition("Tabula Rasa", GetSettings("https://www.tabula-rasa.pw", apiPath: @"/api/v1/api"));
|
yield return GetDefinition("Tabula Rasa", GetSettings("https://www.tabula-rasa.pw", apiPath: @"/api/v1/api"));
|
||||||
yield return GetDefinition("Usenet Crawler", GetSettings("https://www.usenet-crawler.com"));
|
yield return GetDefinition("Usenet Crawler", GetSettings("https://www.usenet-crawler.com"));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user