Cleanup unused frontend components

pull/47/head
Qstick 3 years ago
parent 6dc475cf53
commit 2b6b17707d

2
.gitattributes vendored

@ -3,7 +3,7 @@
# Explicitly set bash scripts to have unix endings
*.sh text eol=lf
macOS/Prowlarr text eol=lf
distribution/osx/Prowlarr text eol=lf
# Custom for Visual Studio
*.cs diff=csharp

@ -183,8 +183,8 @@ stages:
- bash: ./build.sh --packages
displayName: Create Packages
- bash: |
setup/inno/ISCC.exe setup/prowlarr.iss //DFramework=net5.0
cp setup/output/Prowlarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x64-installer.exe
distribution/windows/setup/inno/ISCC.exe distribution/windows/setup/prowlarr.iss //DFramework=net5.0
cp distribution/windows/setup/output/Prowlarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x64-installer.exe
displayName: Create .NET Core Windows installer
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller'

@ -21,7 +21,7 @@ UpdateVersionNumber()
echo "Updating Version Info"
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$PROWLARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$PROWLARRVERSION<\/string>/g" macOS/Prowlarr.app/Contents/Info.plist
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$PROWLARRVERSION<\/string>/g" distribution/osx/Prowlarr.app/Contents/Info.plist
fi
}
@ -163,7 +163,7 @@ PackageMacOSApp()
rm -rf $folder
mkdir -p $folder
cp -r macOS/Prowlarr.app $folder
cp -r distribution/osx/Prowlarr.app $folder
mkdir -p $folder/Prowlarr.app/Contents/MacOS
echo "Copying Binaries"

5
debian/changelog vendored

@ -1,5 +0,0 @@
nzbdrone {version} {branch}; urgency=low
* Automatic Release.
-- NzbDrone <contact@nzbdrone.com> Mon, 26 Aug 2013 00:00:00 -0700

1
debian/compat vendored

@ -1 +0,0 @@
8

12
debian/control vendored

@ -1,12 +0,0 @@
Section: web
Priority: optional
Maintainer: Sonarr <contact@nzbdrone.com>
Source: nzbdrone
Homepage: https://sonarr.tv
Vcs-Git: git@github.com:Sonarr/Sonarr.git
Vcs-Browser: https://github.com/Sonarr/Sonarr
Package: nzbdrone
Architecture: all
Depends: libmono-cil-dev (>= 3.2), sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
Description: Sonarr is an internet PVR

24
debian/copyright vendored

@ -1,24 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: nzbdrone
Source: https://github.com/Sonarr/Sonarr
Files: *
Copyright: 2010-2016 Sonarr <hello@sonarr.tv>
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".

1
debian/install vendored

@ -1 +0,0 @@
nzbdrone_bin/* opt/NzbDrone

13
debian/rules vendored

@ -1,13 +0,0 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
%:
dh $@

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

@ -49,8 +49,8 @@ Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
[Files]
Source: "..\_artifacts\windows\{#Framework}\Prowlarr\Prowlarr.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\_artifacts\windows\{#Framework}\Prowlarr\*"; Excludes: "Prowlarr.Update"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\..\..\_artifacts\windows\{#Framework}\Prowlarr\Prowlarr.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\..\_artifacts\windows\{#Framework}\Prowlarr\*"; Excludes: "Prowlarr.Update"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]

@ -7,7 +7,6 @@ import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
import MovieStatusFilterBuilderRowValue from './MovieStatusFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
import styles from './FilterBuilderRow.css';
@ -60,9 +59,6 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.PROTOCOL:
return ProtocolFilterBuilderRowValue;
case filterBuilderValueTypes.MOVIE_STATUS:
return MovieStatusFilterBuilderRowValue;
case filterBuilderValueTypes.TAG:
return TagFilterBuilderRowValueConnector;

@ -1,21 +0,0 @@
import React from 'react';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const protocols = [
{ id: 'tba', name: 'TBA' },
{ id: 'announced', name: 'Announced' },
{ id: 'inCinemas', name: 'In Cinemas' },
{ id: 'released', name: 'Released' },
{ id: 'deleted', name: 'Deleted' }
];
function MovieStatusFilterBuilderRowValue(props) {
return (
<FilterBuilderRowValue
tagList={protocols}
{...props}
/>
);
}
export default MovieStatusFilterBuilderRowValue;

@ -1,4 +0,0 @@
.heart {
margin-right: 5px;
color: $themeRed;
}

@ -1,34 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import styles from './HeartRating.css';
function HeartRating({ rating, iconSize, hideHeart }) {
return (
<span>
{
!hideHeart &&
<Icon
className={styles.heart}
name={icons.HEART}
size={iconSize}
/>
}
{rating * 10}%
</span>
);
}
HeartRating.propTypes = {
rating: PropTypes.number.isRequired,
iconSize: PropTypes.number.isRequired,
hideHeart: PropTypes.bool
};
HeartRating.defaultProps = {
iconSize: 14
};
export default HeartRating;

@ -1,3 +0,0 @@
.lists {
flex: 1 0 auto;
}

@ -1,43 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import { kinds, sizes } from 'Helpers/Props';
import Label from './Label';
import styles from './ImportListList.css';
function ImportListList({ lists, importListList }) {
return (
<div className={styles.lists}>
{
lists.map((t) => {
const list = _.find(importListList, { id: t });
if (!list) {
return null;
}
return (
<Label
key={list.id}
kind={kinds.INFO}
size={sizes.MEDIUM}
>
{list.name}
</Label>
);
})
}
</div>
);
}
ImportListList.propTypes = {
lists: PropTypes.arrayOf(PropTypes.number).isRequired,
importListList: PropTypes.arrayOf(PropTypes.object).isRequired
};
ImportListList.defaultProps = {
lists: []
};
export default ImportListList;

@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createImportListSelector from 'Store/Selectors/createImportListSelector';
import ImportListList from './ImportListList';
function createMapStateToProps() {
return createSelector(
createImportListSelector(),
(importListList) => {
return {
importListList
};
}
);
}
export default connect(createMapStateToProps)(ImportListList);

@ -1,181 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
const FPS = 20;
const STEP = 1;
const TIMEOUT = 1 / FPS * 1000;
class Marquee extends Component {
static propTypes = {
text: PropTypes.string,
title: PropTypes.string,
hoverToStop: PropTypes.bool,
loop: PropTypes.bool,
className: PropTypes.string
};
static defaultProps = {
text: '',
title: '',
hoverToStop: true,
loop: false
};
state = {
animatedWidth: 0,
overflowWidth: 0,
direction: 0
};
componentDidMount() {
this.measureText();
if (this.props.hoverToStop) {
this.startAnimation();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.text.length !== nextProps.text.length) {
clearTimeout(this.marqueeTimer);
this.setState({ animatedWidth: 0, direction: 0 });
}
}
componentDidUpdate() {
this.measureText();
if (this.props.hoverToStop) {
this.startAnimation();
}
}
componentWillUnmount() {
clearTimeout(this.marqueeTimer);
}
onHandleMouseEnter = () => {
if (this.props.hoverToStop) {
clearTimeout(this.marqueeTimer);
} else if (this.state.overflowWidth > 0) {
this.startAnimation();
}
}
onHandleMouseLeave = () => {
if (this.props.hoverToStop && this.state.overflowWidth > 0) {
this.startAnimation();
} else {
clearTimeout(this.marqueeTimer);
this.setState({ animatedWidth: 0 });
}
}
startAnimation = () => {
clearTimeout(this.marqueeTimer);
const isLeading = this.state.animatedWidth === 0;
const timeout = isLeading ? 0 : TIMEOUT;
const animate = () => {
const { overflowWidth } = this.state;
let animatedWidth = this.state.animatedWidth;
let direction = this.state.direction;
if (direction === 0) {
animatedWidth = this.state.animatedWidth + STEP;
} else {
animatedWidth = this.state.animatedWidth - STEP;
}
const isRoundOver = animatedWidth < 0;
const endOfText = animatedWidth > overflowWidth;
if (endOfText) {
direction = direction === 1;
}
if (isRoundOver) {
if (this.props.loop) {
direction = direction === 0;
} else {
return;
}
}
this.setState({ animatedWidth, direction });
this.marqueeTimer = setTimeout(animate, TIMEOUT);
};
this.marqueeTimer = setTimeout(animate, timeout);
}
measureText = () => {
const container = this.container;
const node = this.text;
if (container && node) {
const containerWidth = container.offsetWidth;
const textWidth = node.offsetWidth;
const overflowWidth = textWidth - containerWidth;
if (overflowWidth !== this.state.overflowWidth) {
this.setState({ overflowWidth });
}
}
}
render() {
const style = {
position: 'relative',
right: this.state.animatedWidth,
whiteSpace: 'nowrap'
};
if (this.state.overflowWidth < 0) {
return (
<div
ref={(el) => {
this.container = el;
}}
className={`ui-marquee ${this.props.className}`}
style={{ overflow: 'hidden' }}
>
<span
ref={(el) => {
this.text = el;
}}
style={style}
title={(this.props.title && (this.props.text !== this.props.title)) ? `Original Title: ${this.props.title}` : this.props.text}
>
{this.props.text}
</span>
</div>
);
}
return (
<div
ref={(el) => {
this.container = el;
}}
className={`ui-marquee ${this.props.className}`.trim()}
style={{ overflow: 'hidden' }}
onMouseEnter={this.onHandleMouseEnter}
onMouseLeave={this.onHandleMouseLeave}
>
<span
ref={(el) => {
this.text = el;
}}
style={style}
title={(this.props.title && (this.props.text !== this.props.title)) ? `Original Title: ${this.props.title}` : this.props.text}
>
{this.props.text}
</span>
</div>
);
}
}
export default Marquee;

@ -1,11 +0,0 @@
.toggleButton {
composes: button from '~Components/Link/IconButton.css';
padding: 0;
font-size: inherit;
}
.isDisabled {
color: $disabledColor;
cursor: not-allowed;
}

@ -1,79 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import { icons } from 'Helpers/Props';
import styles from './MonitorToggleButton.css';
function getTooltip(monitored, isDisabled) {
if (isDisabled) {
return 'Cannot toggle monitored state when movie is unmonitored';
}
if (monitored) {
return 'Monitored, click to unmonitor';
}
return 'Unmonitored, click to monitor';
}
class MonitorToggleButton extends Component {
//
// Listeners
onPress = (event) => {
const shiftKey = event.nativeEvent.shiftKey;
this.props.onPress(!this.props.monitored, { shiftKey });
}
//
// Render
render() {
const {
className,
monitored,
isDisabled,
isSaving,
size,
...otherProps
} = this.props;
const iconName = monitored ? icons.MONITORED : icons.UNMONITORED;
return (
<SpinnerIconButton
className={classNames(
className,
isDisabled && styles.isDisabled
)}
name={iconName}
size={size}
title={getTooltip(monitored, isDisabled)}
isDisabled={isDisabled}
isSpinning={isSaving}
{...otherProps}
onPress={this.onPress}
/>
);
}
}
MonitorToggleButton.propTypes = {
className: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
size: PropTypes.number,
isDisabled: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,
onPress: PropTypes.func.isRequired
};
MonitorToggleButton.defaultProps = {
className: styles.toggleButton,
isDisabled: false,
isSaving: false
};
export default MonitorToggleButton;

@ -6,12 +6,12 @@ import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import MovieSearchResult from './MovieSearchResult';
import styles from './MovieSearchInput.css';
import IndexerSearchResult from './IndexerSearchResult';
import styles from './IndexerSearchInput.css';
const ADD_NEW_TYPE = 'addNew';
class MovieSearchInput extends Component {
class IndexerSearchInput extends Component {
//
// Lifecycle
@ -86,7 +86,7 @@ class MovieSearchInput extends Component {
}
return (
<MovieSearchResult
<IndexerSearchResult
{...item.item}
match={item.matches[0]}
/>
@ -239,9 +239,9 @@ class MovieSearchInput extends Component {
}
}
MovieSearchInput.propTypes = {
IndexerSearchInput.propTypes = {
onGoToAddNewMovie: PropTypes.func.isRequired,
bindShortcut: PropTypes.func.isRequired
};
export default keyboardShortcuts(MovieSearchInput);
export default keyboardShortcuts(IndexerSearchInput);

@ -5,14 +5,14 @@ import { setSearchDefault } from 'Store/Actions/releaseActions';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import MovieSearchInput from './MovieSearchInput';
import IndexerSearchInput from './IndexerSearchInput';
function createCleanMovieSelector() {
return createSelector(
createAllIndexersSelector(),
createTagsSelector(),
(allMovies, allTags) => {
return allMovies.map((movie) => {
(allIndexers, allTags) => {
return allIndexers.map((movie) => {
const {
name,
titleSlug,
@ -66,4 +66,4 @@ function createMapDispatchToProps(dispatch, props) {
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieSearchInput);
export default connect(createMapStateToProps, createMapDispatchToProps)(IndexerSearchInput);

@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import { kinds } from 'Helpers/Props';
import styles from './MovieSearchResult.css';
import styles from './IndexerSearchResult.css';
function MovieSearchResult(props) {
function IndexerSearchResult(props) {
const {
match,
title,
@ -54,7 +54,7 @@ function MovieSearchResult(props) {
);
}
MovieSearchResult.propTypes = {
IndexerSearchResult.propTypes = {
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -62,4 +62,4 @@ MovieSearchResult.propTypes = {
match: PropTypes.object.isRequired
};
export default MovieSearchResult;
export default IndexerSearchResult;

@ -5,8 +5,8 @@ import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import IndexerSearchInputConnector from './IndexerSearchInputConnector';
import KeyboardShortcutsModal from './KeyboardShortcutsModal';
import MovieSearchInputConnector from './MovieSearchInputConnector';
import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector';
import styles from './PageHeader.css';
@ -68,7 +68,7 @@ class PageHeader extends Component {
/>
</div>
<MovieSearchInputConnector />
<IndexerSearchInputConnector />
<div className={styles.right}>
<IconButton

@ -37,16 +37,6 @@ export const shortcuts = {
SCROLL_BOTTOM: {
key: 'mod+end',
name: translate('MovieIndexScrollBottom')
},
DETAILS_NEXT: {
key: '→',
name: translate('MovieDetailsNextMovie')
},
DETAILS_PREVIOUS: {
key: '←',
name: translate('MovieDetailsPreviousMovie')
}
};

@ -4,6 +4,7 @@ export const CAPTCHA = 'captcha';
export const CARDIGANNCAPTCHA = 'cardigannCaptcha';
export const CHECK = 'check';
export const DEVICE = 'device';
export const KEY_VALUE_LIST = 'keyValueList';
export const INFO = 'info';
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
export const NUMBER = 'number';
@ -12,6 +13,7 @@ export const PASSWORD = 'password';
export const PATH = 'path';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
export const TEXT = 'text';
export const TEXT_AREA = 'textArea';
@ -25,6 +27,7 @@ export const all = [
CARDIGANNCAPTCHA,
CHECK,
DEVICE,
KEY_VALUE_LIST,
INFO,
MOVIE_MONITORED_SELECT,
NUMBER,
@ -33,6 +36,7 @@ export const all = [
PATH,
INDEXER_FLAGS_SELECT,
SELECT,
DYNAMIC_SELECT,
TAG,
TEXT,
TEXT_AREA,

@ -1,31 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import OrganizeMovieModalContentConnector from './OrganizeMovieModalContentConnector';
function OrganizeMovieModal(props) {
const {
isOpen,
onModalClose,
...otherProps
} = props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<OrganizeMovieModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
OrganizeMovieModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default OrganizeMovieModal;

@ -1,8 +0,0 @@
.renameIcon {
margin-left: 5px;
}
.message {
margin-top: 20px;
margin-bottom: 10px;
}

@ -1,75 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
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 { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './OrganizeMovieModalContent.css';
function OrganizeMovieModalContent(props) {
const {
movieTitles,
onModalClose,
onOrganizeMoviePress
} = props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Organize Selected Movies
</ModalHeader>
<ModalBody>
<Alert>
Tip: To preview a rename... select "Cancel" then click any movie title and use the
<Icon
className={styles.renameIcon}
name={icons.ORGANIZE}
/>
</Alert>
<div className={styles.message}>
Are you sure you want to organize all files in the {movieTitles.length} selected movie(s)?
</div>
<ul>
{
movieTitles.map((title) => {
return (
<li key={title}>
{title}
</li>
);
})
}
</ul>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Cancel')}
</Button>
<Button
kind={kinds.DANGER}
onPress={onOrganizeMoviePress}
>
Organize
</Button>
</ModalFooter>
</ModalContent>
);
}
OrganizeMovieModalContent.propTypes = {
movieTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
onModalClose: PropTypes.func.isRequired,
onOrganizeMoviePress: PropTypes.func.isRequired
};
export default OrganizeMovieModalContent;

@ -1,67 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import { executeCommand } from 'Store/Actions/commandActions';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import OrganizeMovieModalContent from './OrganizeMovieModalContent';
function createMapStateToProps() {
return createSelector(
(state, { movieIds }) => movieIds,
createAllMoviesSelector(),
(movieIds, allMovies) => {
const movies = _.intersectionWith(allMovies, movieIds, (s, id) => {
return s.id === id;
});
const sortedMovies = _.orderBy(movies, 'sortTitle');
const movieTitles = _.map(sortedMovies, 'title');
return {
movieTitles
};
}
);
}
const mapDispatchToProps = {
executeCommand
};
class OrganizeMovieModalContentConnector extends Component {
//
// Listeners
onOrganizeMoviePress = () => {
this.props.executeCommand({
name: commandNames.RENAME_MOVIE,
movieIds: this.props.movieIds
});
this.props.onModalClose(true);
}
//
// Render
render(props) {
return (
<OrganizeMovieModalContent
{...this.props}
onOrganizeMoviePress={this.onOrganizeMoviePress}
/>
);
}
}
OrganizeMovieModalContentConnector.propTypes = {
movieIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onModalClose: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeMovieModalContentConnector);

@ -11,10 +11,10 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import { align, icons, sortDirections } from 'Helpers/Props';
import AddIndexerModal from 'Indexer/Add/AddIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import IndexerEditorFooter from 'Indexer/Editor/IndexerEditorFooter.js';
import NoIndexer from 'Indexer/NoIndexer';
import AddIndexerModal from 'Settings/Indexers/Indexers/AddIndexerModal';
import EditIndexerModalConnector from 'Settings/Indexers/Indexers/EditIndexerModalConnector';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
@ -23,14 +23,14 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import IndexerIndexFooterConnector from './IndexerIndexFooterConnector';
import MovieIndexFilterMenu from './Menus/MovieIndexFilterMenu';
import MovieIndexSortMenu from './Menus/MovieIndexSortMenu';
import MovieIndexTableConnector from './Table/MovieIndexTableConnector';
import MovieIndexTableOptionsConnector from './Table/MovieIndexTableOptionsConnector';
import IndexerIndexFilterMenu from './Menus/IndexerIndexFilterMenu';
import IndexerIndexSortMenu from './Menus/IndexerIndexSortMenu';
import IndexerIndexTableConnector from './Table/IndexerIndexTableConnector';
import IndexerIndexTableOptionsConnector from './Table/IndexerIndexTableOptionsConnector';
import styles from './IndexerIndex.css';
function getViewComponent() {
return MovieIndexTableConnector;
return IndexerIndexTableConnector;
}
class IndexerIndex extends Component {
@ -354,7 +354,7 @@ class IndexerIndex extends Component {
<TableOptionsModalWrapper
{...otherProps}
columns={columns}
optionsComponent={MovieIndexTableOptionsConnector}
optionsComponent={IndexerIndexTableOptionsConnector}
>
<PageToolbarButton
label={translate('Options')}
@ -365,14 +365,14 @@ class IndexerIndex extends Component {
<PageToolbarSeparator />
<MovieIndexSortMenu
<IndexerIndexSortMenu
sortKey={sortKey}
sortDirection={sortDirection}
isDisabled={hasNoIndexer}
onSortSelect={onSortSelect}
/>
<MovieIndexFilterMenu
<IndexerIndexFilterMenu
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}

@ -5,7 +5,7 @@ import { setMovieFilter } from 'Store/Actions/indexerIndexActions';
function createMapStateToProps() {
return createSelector(
(state) => state.movies.items,
(state) => state.indexers.items,
(state) => state.indexerIndex.filterBuilderProps,
(sectionItems, filterBuilderProps) => {
return {

@ -53,7 +53,7 @@ const mapDispatchToProps = {
dispatchExecuteCommand: executeCommand
};
class MovieIndexItemConnector extends Component {
class IndexerIndexItemConnector extends Component {
//
// Render
@ -78,10 +78,10 @@ class MovieIndexItemConnector extends Component {
}
}
MovieIndexItemConnector.propTypes = {
IndexerIndexItemConnector.propTypes = {
id: PropTypes.number,
component: PropTypes.elementType.isRequired,
dispatchExecuteCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MovieIndexItemConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(IndexerIndexItemConnector);

@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react';
import FilterMenu from 'Components/Menu/FilterMenu';
import { align } from 'Helpers/Props';
import MovieIndexFilterModalConnector from 'Indexer/Index/MovieIndexFilterModalConnector';
import IndexerIndexFilterModalConnector from 'Indexer/Index/IndexerIndexFilterModalConnector';
function MovieIndexFilterMenu(props) {
function IndexerIndexFilterMenu(props) {
const {
selectedFilterKey,
filters,
@ -20,13 +20,13 @@ function MovieIndexFilterMenu(props) {
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={MovieIndexFilterModalConnector}
filterModalConnectorComponent={IndexerIndexFilterModalConnector}
onFilterSelect={onFilterSelect}
/>
);
}
MovieIndexFilterMenu.propTypes = {
IndexerIndexFilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -34,8 +34,8 @@ MovieIndexFilterMenu.propTypes = {
onFilterSelect: PropTypes.func.isRequired
};
MovieIndexFilterMenu.defaultProps = {
IndexerIndexFilterMenu.defaultProps = {
showCustomFilters: false
};
export default MovieIndexFilterMenu;
export default IndexerIndexFilterMenu;

@ -6,7 +6,7 @@ import SortMenuItem from 'Components/Menu/SortMenuItem';
import { align, sortDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function MovieIndexSortMenu(props) {
function IndexerIndexSortMenu(props) {
const {
sortKey,
sortDirection,
@ -78,11 +78,11 @@ function MovieIndexSortMenu(props) {
);
}
MovieIndexSortMenu.propTypes = {
IndexerIndexSortMenu.propTypes = {
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
isDisabled: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired
};
export default MovieIndexSortMenu;
export default IndexerIndexSortMenu;

@ -1,52 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Menu from 'Components/Menu/Menu';
import MenuContent from 'Components/Menu/MenuContent';
import SearchMenuItem from 'Components/Menu/SearchMenuItem';
import ToolbarMenuButton from 'Components/Menu/ToolbarMenuButton';
import { align, icons } from 'Helpers/Props';
class MovieIndexSearchMenu extends Component {
render() {
const {
isDisabled,
onSearchPress
} = this.props;
return (
<Menu
isDisabled={isDisabled}
alignMenu={align.RIGHT}
>
<ToolbarMenuButton
iconName={icons.SEARCH}
text="Search"
isDisabled={isDisabled}
/>
<MenuContent>
<SearchMenuItem
name="missingMoviesSearch"
onPress={onSearchPress}
>
Search Missing
</SearchMenuItem>
<SearchMenuItem
name="cutoffUnmetMoviesSearch"
onPress={onSearchPress}
>
Search Cutoff Unmet
</SearchMenuItem>
</MenuContent>
</Menu>
);
}
}
MovieIndexSearchMenu.propTypes = {
isDisabled: PropTypes.bool.isRequired,
onSearchPress: PropTypes.func.isRequired
};
export default MovieIndexSearchMenu;

@ -8,7 +8,7 @@ import DeleteMovieModal from 'Indexer/Delete/DeleteMovieModal';
import EditMovieModalConnector from 'Indexer/Edit/EditMovieModalConnector';
import translate from 'Utilities/String/translate';
class MovieIndexActionsCell extends Component {
class IndexerIndexActionsCell extends Component {
//
// Lifecycle
@ -94,10 +94,10 @@ class MovieIndexActionsCell extends Component {
}
}
MovieIndexActionsCell.propTypes = {
IndexerIndexActionsCell.propTypes = {
id: PropTypes.number.isRequired,
isRefreshingMovie: PropTypes.bool.isRequired,
onRefreshMoviePress: PropTypes.func.isRequired
};
export default MovieIndexActionsCell;
export default IndexerIndexActionsCell;

@ -6,10 +6,10 @@ import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
import { icons } from 'Helpers/Props';
import MovieIndexTableOptionsConnector from './MovieIndexTableOptionsConnector';
import styles from './MovieIndexHeader.css';
import IndexerIndexTableOptionsConnector from './IndexerIndexTableOptionsConnector';
import styles from './IndexerIndexHeader.css';
class MovieIndexHeader extends Component {
class IndexerIndexHeader extends Component {
//
// Lifecycle
@ -111,7 +111,7 @@ class MovieIndexHeader extends Component {
<TableOptionsModal
isOpen={this.state.isTableOptionsModalOpen}
columns={columns}
optionsComponent={MovieIndexTableOptionsConnector}
optionsComponent={IndexerIndexTableOptionsConnector}
onTableOptionChange={onTableOptionChange}
onModalClose={this.onTableOptionsModalClose}
/>
@ -120,7 +120,7 @@ class MovieIndexHeader extends Component {
}
}
MovieIndexHeader.propTypes = {
IndexerIndexHeader.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onTableOptionChange: PropTypes.func.isRequired,
allSelected: PropTypes.bool.isRequired,
@ -129,4 +129,4 @@ MovieIndexHeader.propTypes = {
isMovieEditorActive: PropTypes.bool.isRequired
};
export default MovieIndexHeader;
export default IndexerIndexHeader;

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { setMovieTableOption } from 'Store/Actions/indexerIndexActions';
import MovieIndexHeader from './MovieIndexHeader';
import IndexerIndexHeader from './IndexerIndexHeader';
function createMapDispatchToProps(dispatch, props) {
return {
@ -10,4 +10,4 @@ function createMapDispatchToProps(dispatch, props) {
};
}
export default connect(undefined, createMapDispatchToProps)(MovieIndexHeader);
export default connect(undefined, createMapDispatchToProps)(IndexerIndexHeader);

@ -8,7 +8,7 @@ import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCel
import TagListConnector from 'Components/TagListConnector';
import { icons } from 'Helpers/Props';
import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
import EditIndexerModalConnector from 'Settings/Indexers/Indexers/EditIndexerModalConnector';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import CapabilitiesLabel from './CapabilitiesLabel';

@ -3,13 +3,13 @@ import React, { Component } from 'react';
import VirtualTable from 'Components/Table/VirtualTable';
import VirtualTableRow from 'Components/Table/VirtualTableRow';
import { sortDirections } from 'Helpers/Props';
import MovieIndexItemConnector from 'Indexer/Index/MovieIndexItemConnector';
import IndexerIndexItemConnector from 'Indexer/Index/IndexerIndexItemConnector';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import IndexerIndexHeaderConnector from './IndexerIndexHeaderConnector';
import IndexerIndexRow from './IndexerIndexRow';
import MovieIndexHeaderConnector from './MovieIndexHeaderConnector';
import styles from './MovieIndexTable.css';
import styles from './IndexerIndexTable.css';
class MovieIndexTable extends Component {
class IndexerIndexTable extends Component {
//
// Lifecycle
@ -49,8 +49,7 @@ class MovieIndexTable extends Component {
columns,
selectedState,
onSelectedChange,
isMovieEditorActive,
movieRuntimeFormat
isMovieEditorActive
} = this.props;
const movie = items[rowIndex];
@ -60,7 +59,7 @@ class MovieIndexTable extends Component {
key={key}
style={style}
>
<MovieIndexItemConnector
<IndexerIndexItemConnector
key={movie.id}
component={IndexerIndexRow}
columns={columns}
@ -68,7 +67,6 @@ class MovieIndexTable extends Component {
isSelected={selectedState[movie.id]}
onSelectedChange={onSelectedChange}
isMovieEditorActive={isMovieEditorActive}
movieRuntimeFormat={movieRuntimeFormat}
/>
</VirtualTableRow>
);
@ -104,7 +102,7 @@ class MovieIndexTable extends Component {
overscanRowCount={2}
rowRenderer={this.rowRenderer}
header={
<MovieIndexHeaderConnector
<IndexerIndexHeaderConnector
columns={columns}
sortKey={sortKey}
sortDirection={sortDirection}
@ -122,7 +120,7 @@ class MovieIndexTable extends Component {
}
}
MovieIndexTable.propTypes = {
IndexerIndexTable.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string,
@ -136,8 +134,7 @@ MovieIndexTable.propTypes = {
selectedState: PropTypes.object.isRequired,
onSelectedChange: PropTypes.func.isRequired,
onSelectAllChange: PropTypes.func.isRequired,
isMovieEditorActive: PropTypes.bool.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired
isMovieEditorActive: PropTypes.bool.isRequired
};
export default MovieIndexTable;
export default IndexerIndexTable;

@ -1,20 +1,18 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setMovieSort } from 'Store/Actions/indexerIndexActions';
import MovieIndexTable from './MovieIndexTable';
import IndexerIndexTable from './IndexerIndexTable';
function createMapStateToProps() {
return createSelector(
(state) => state.app.dimensions,
(state) => state.indexerIndex.tableOptions,
(state) => state.indexerIndex.columns,
(state) => state.settings.ui.item.movieRuntimeFormat,
(dimensions, tableOptions, columns, movieRuntimeFormat) => {
(dimensions, tableOptions, columns) => {
return {
isSmallScreen: dimensions.isSmallScreen,
showBanners: tableOptions.showBanners,
columns,
movieRuntimeFormat
columns
};
}
);
@ -28,4 +26,4 @@ function createMapDispatchToProps(dispatch, props) {
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieIndexTable);
export default connect(createMapStateToProps, createMapDispatchToProps)(IndexerIndexTable);

@ -6,7 +6,7 @@ import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class MovieIndexTableOptions extends Component {
class IndexerIndexTableOptions extends Component {
//
// Lifecycle
@ -69,9 +69,9 @@ class MovieIndexTableOptions extends Component {
}
}
MovieIndexTableOptions.propTypes = {
IndexerIndexTableOptions.propTypes = {
showSearchAction: PropTypes.bool.isRequired,
onTableOptionChange: PropTypes.func.isRequired
};
export default MovieIndexTableOptions;
export default IndexerIndexTableOptions;

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import MovieIndexTableOptions from './MovieIndexTableOptions';
import IndexerIndexTableOptions from './IndexerIndexTableOptions';
function createMapStateToProps() {
return createSelector(
@ -11,4 +11,4 @@ function createMapStateToProps() {
);
}
export default connect(createMapStateToProps)(MovieIndexTableOptions);
export default connect(createMapStateToProps)(IndexerIndexTableOptions);

@ -1,70 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import Popover from 'Components/Tooltip/Popover';
import { kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function MovieLanguage(props) {
const {
className,
languages,
isCutoffNotMet
} = props;
if (!languages) {
return null;
}
if (languages.length === 1) {
return (
<Label
className={className}
kind={isCutoffNotMet ? kinds.INVERSE : kinds.DEFAULT}
>
{languages[0].name}
</Label>
);
}
return (
<Popover
className={className}
anchor={
<Label
className={className}
kind={isCutoffNotMet ? kinds.INVERSE : kinds.DEFAULT}
>
Multi-Language
</Label>
}
title={translate('Languages')}
body={
<ul>
{
languages.map((language) => {
return (
<li key={language.id}>
{language.name}
</li>
);
})
}
</ul>
}
position={tooltipPositions.LEFT}
/>
);
}
MovieLanguage.propTypes = {
className: PropTypes.string,
languages: PropTypes.arrayOf(PropTypes.object),
isCutoffNotMet: PropTypes.bool
};
MovieLanguage.defaultProps = {
isCutoffNotMet: true
};
export default MovieLanguage;

@ -1,77 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import { kinds } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) {
const revision = quality.revision;
if (revision.real && revision.real > 0) {
title += ' [REAL]';
}
if (revision.version && revision.version > 1) {
title += ' [PROPER]';
}
if (size) {
title += ` - ${formatBytes(size)}`;
}
if (!isMonitored) {
title += ' [Not Monitored]';
} else if (isCutoffNotMet) {
title += ' [Cutoff Not Met]';
}
return title;
}
function MovieQuality(props) {
const {
className,
title,
quality,
size,
isMonitored,
isCutoffNotMet
} = props;
let kind = kinds.DEFAULT;
if (!isMonitored) {
kind = kinds.DISABLED;
} else if (isCutoffNotMet) {
kind = kinds.INVERSE;
}
if (!quality) {
return null;
}
return (
<Label
className={className}
kind={kind}
title={getTooltip(title, quality, size, isMonitored, isCutoffNotMet)}
>
{quality.quality.name}
</Label>
);
}
MovieQuality.propTypes = {
className: PropTypes.string,
title: PropTypes.string,
quality: PropTypes.object.isRequired,
size: PropTypes.number,
isMonitored: PropTypes.bool,
isCutoffNotMet: PropTypes.bool
};
MovieQuality.defaultProps = {
title: '',
isMonitored: true
};
export default MovieQuality;

@ -1,32 +0,0 @@
import { icons } from 'Helpers/Props';
export function getMovieStatusDetails(status) {
let statusDetails = {
icon: icons.ANNOUNCED,
title: 'Announced',
message: 'Movie is announced'
};
if (status === 'deleted') {
statusDetails = {
icon: icons.MOVIE_DELETED,
title: 'Deleted',
message: 'Movie was deleted from TMDb'
};
} else if (status === 'inCinemas') {
statusDetails = {
icon: icons.IN_CINEMAS,
title: 'In Cinemas',
message: 'Movie is in Cinemas'
};
} else if (status === 'released') {
statusDetails = {
icon: icons.MOVIE_FILE,
title: 'Released',
message: 'Movie is released'
};
}
return statusDetails;
}

@ -1,31 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import Link from 'Components/Link/Link';
class MovieTitleLink extends PureComponent {
render() {
const {
titleSlug,
title
} = this.props;
const link = `/movie/${titleSlug}`;
return (
<Link
to={link}
title={title}
>
{title}
</Link>
);
}
}
MovieTitleLink.propTypes = {
titleSlug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
};
export default MovieTitleLink;

@ -11,9 +11,9 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import { align, icons, sortDirections } from 'Helpers/Props';
import AddIndexerModal from 'Indexer/Add/AddIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import NoIndexer from 'Indexer/NoIndexer';
import AddIndexerModal from 'Settings/Indexers/Indexers/AddIndexerModal';
import EditIndexerModalConnector from 'Settings/Indexers/Indexers/EditIndexerModalConnector';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';

@ -1,30 +0,0 @@
.indexer {
composes: card from '~Components/Card.css';
width: 290px;
}
.nameContainer {
display: flex;
justify-content: space-between;
}
.name {
@add-mixin truncate;
margin-bottom: 20px;
font-weight: 300;
font-size: 24px;
}
.cloneButton {
composes: button from '~Components/Link/IconButton.css';
height: 36px;
}
.enabled {
display: flex;
flex-wrap: wrap;
margin-top: 5px;
}

@ -1,152 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditIndexerModalConnector from './EditIndexerModalConnector';
import styles from './Indexer.css';
class Indexer extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditIndexerModalOpen: false,
isDeleteIndexerModalOpen: false
};
}
//
// Listeners
onEditIndexerPress = () => {
this.setState({ isEditIndexerModalOpen: true });
}
onEditIndexerModalClose = () => {
this.setState({ isEditIndexerModalOpen: false });
}
onDeleteIndexerPress = () => {
this.setState({
isEditIndexerModalOpen: false,
isDeleteIndexerModalOpen: true
});
}
onDeleteIndexerModalClose= () => {
this.setState({ isDeleteIndexerModalOpen: false });
}
onConfirmDeleteIndexer = () => {
this.props.onConfirmDeleteIndexer(this.props.id);
}
onCloneIndexerPress = () => {
const {
id,
onCloneIndexerPress
} = this.props;
onCloneIndexerPress(id);
}
//
// Render
render() {
const {
id,
name,
enable,
supportsRss,
priority,
showPriority
} = this.props;
return (
<Card
className={styles.indexer}
overlayContent={true}
onPress={this.onEditIndexerPress}
>
<div className={styles.nameContainer}>
<div className={styles.name}>
{name}
</div>
<IconButton
className={styles.cloneButton}
title={translate('CloneIndexer')}
name={icons.CLONE}
onPress={this.onCloneIndexerPress}
/>
</div>
<div className={styles.enabled}>
{
supportsRss && enable &&
<Label kind={kinds.SUCCESS}>
RSS
</Label>
}
{
showPriority &&
<Label kind={kinds.DEFAULT}>
{translate('Priority')}: {priority}
</Label>
}
{
!enable &&
<Label
kind={kinds.DISABLED}
outline={true}
>
{translate('Disabled')}
</Label>
}
</div>
<EditIndexerModalConnector
id={id}
isOpen={this.state.isEditIndexerModalOpen}
onModalClose={this.onEditIndexerModalClose}
onDeleteIndexerPress={this.onDeleteIndexerPress}
/>
<ConfirmModal
isOpen={this.state.isDeleteIndexerModalOpen}
kind={kinds.DANGER}
title={translate('DeleteIndexer')}
message={translate('DeleteIndexerMessageText', [name])}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteIndexer}
onCancel={this.onDeleteIndexerModalClose}
/>
</Card>
);
}
}
Indexer.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
supportsRss: PropTypes.bool.isRequired,
supportsSearch: PropTypes.bool.isRequired,
onCloneIndexerPress: PropTypes.func.isRequired,
onConfirmDeleteIndexer: PropTypes.func.isRequired,
priority: PropTypes.number.isRequired,
showPriority: PropTypes.bool.isRequired
};
export default Indexer;

@ -1,20 +0,0 @@
.indexers {
display: flex;
flex-wrap: wrap;
}
.addIndexer {
composes: indexer from '~./Indexer.css';
background-color: $cardAlternateBackgroundColor;
color: $gray;
text-align: center;
}
.center {
display: inline-block;
padding: 5px 20px 0;
border: 1px solid $borderColor;
border-radius: 4px;
background-color: $white;
}

@ -1,126 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Card from 'Components/Card';
import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddIndexerModal from './AddIndexerModal';
import EditIndexerModalConnector from './EditIndexerModalConnector';
import Indexer from './Indexer';
import styles from './Indexers.css';
class Indexers extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isAddIndexerModalOpen: false,
isEditIndexerModalOpen: false
};
}
//
// Listeners
onAddIndexerPress = () => {
this.setState({ isAddIndexerModalOpen: true });
}
onCloneIndexerPress = (id) => {
this.props.dispatchCloneIndexer({ id });
this.setState({ isEditIndexerModalOpen: true });
}
onAddIndexerModalClose = ({ indexerSelected = false } = {}) => {
this.setState({
isAddIndexerModalOpen: false,
isEditIndexerModalOpen: indexerSelected
});
}
onEditIndexerModalClose = () => {
this.setState({ isEditIndexerModalOpen: false });
}
//
// Render
render() {
const {
items,
dispatchCloneIndexer,
onConfirmDeleteIndexer,
...otherProps
} = this.props;
const {
isAddIndexerModalOpen,
isEditIndexerModalOpen
} = this.state;
const showPriority = items.some((index) => index.priority !== 25);
return (
<FieldSet legend={translate('Indexers')}>
<PageSectionContent
errorMessage={translate('UnableToLoadIndexers')}
{...otherProps}
>
<div className={styles.indexers}>
{
items.map((item) => {
return (
<Indexer
key={item.id}
{...item}
showPriority={showPriority}
onCloneIndexerPress={this.onCloneIndexerPress}
onConfirmDeleteIndexer={onConfirmDeleteIndexer}
/>
);
})
}
<Card
className={styles.addIndexer}
onPress={this.onAddIndexerPress}
>
<div className={styles.center}>
<Icon
name={icons.ADD}
size={45}
/>
</div>
</Card>
</div>
<AddIndexerModal
isOpen={isAddIndexerModalOpen}
onModalClose={this.onAddIndexerModalClose}
/>
<EditIndexerModalConnector
isOpen={isEditIndexerModalOpen}
onModalClose={this.onEditIndexerModalClose}
/>
</PageSectionContent>
</FieldSet>
);
}
}
Indexers.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
dispatchCloneIndexer: PropTypes.func.isRequired,
onConfirmDeleteIndexer: PropTypes.func.isRequired
};
export default Indexers;

@ -1,58 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/indexerActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByName from 'Utilities/Array/sortByName';
import Indexers from './Indexers';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('indexers', sortByName),
(indexers) => indexers
);
}
const mapDispatchToProps = {
dispatchFetchIndexers: fetchIndexers,
dispatchDeleteIndexer: deleteIndexer,
dispatchCloneIndexer: cloneIndexer
};
class IndexersConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchIndexers();
}
//
// Listeners
onConfirmDeleteIndexer = (id) => {
this.props.dispatchDeleteIndexer({ id });
}
//
// Render
render() {
return (
<Indexers
{...this.props}
onConfirmDeleteIndexer={this.onConfirmDeleteIndexer}
/>
);
}
}
IndexersConnector.propTypes = {
dispatchFetchIndexers: PropTypes.func.isRequired,
dispatchDeleteIndexer: PropTypes.func.isRequired,
dispatchCloneIndexer: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(IndexersConnector);

@ -1,93 +0,0 @@
.qualityDefinition {
display: flex;
align-content: stretch;
margin: 5px 0;
padding-top: 5px;
height: 45px;
border-top: 1px solid $borderColor;
}
.quality,
.title {
flex: 0 1 250px;
padding-right: 20px;
line-height: 40px;
}
.sizeLimit {
flex: 0 1 500px;
padding-right: 30px;
}
.slider {
width: 100%;
height: 20px;
}
.bar {
top: 9px;
margin: 0 5px;
height: 3px;
background-color: $sliderAccentColor;
box-shadow: 0 0 0 #000;
&:nth-child(3n+1) {
background-color: #ddd;
}
}
.handle {
top: 1px;
z-index: 0 !important;
width: 18px;
height: 18px;
border: 3px solid $sliderAccentColor;
border-radius: 50%;
background-color: $white;
text-align: center;
cursor: pointer;
}
.sizes {
display: flex;
justify-content: space-between;
}
.megabytesPerMinute {
display: flex;
justify-content: space-between;
flex: 0 0 400px;
}
.sizeInput {
composes: input from '~Components/Form/TextInput.css';
display: inline-block;
margin-left: 5px;
padding: 6px;
width: 75px;
}
@media only screen and (max-width: $breakpointSmall) {
.qualityDefinition {
flex-wrap: wrap;
height: auto;
&:first-child {
border-top: none;
}
}
.qualityDefinition:first-child {
border-top: none;
}
.quality {
font-weight: bold;
line-height: inherit;
}
.sizeLimit {
margin-top: 10px;
}
}

@ -1,308 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactSlider from 'react-slider';
import NumberInput from 'Components/Form/NumberInput';
import TextInput from 'Components/Form/TextInput';
import Label from 'Components/Label';
import Popover from 'Components/Tooltip/Popover';
import { kinds, tooltipPositions } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
import roundNumber from 'Utilities/Number/roundNumber';
import translate from 'Utilities/String/translate';
import QualityDefinitionLimits from './QualityDefinitionLimits';
import styles from './QualityDefinition.css';
const MIN = 0;
const MAX = 400;
const slider = {
min: MIN,
max: roundNumber(Math.pow(MAX, 1 / 1.1)),
step: 0.1
};
function getValue(inputValue) {
if (inputValue < MIN) {
return MIN;
}
if (inputValue > MAX) {
return MAX;
}
return roundNumber(inputValue);
}
function getSliderValue(value, defaultValue) {
const sliderValue = value ? Math.pow(value, 1 / 1.1) : defaultValue;
return roundNumber(sliderValue);
}
class QualityDefinition extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
sliderMinSize: getSliderValue(props.minSize, slider.min),
sliderMaxSize: getSliderValue(props.maxSize, slider.max),
sliderPreferredSize: getSliderValue(props.preferredSize, (slider.max - 3))
};
}
//
// Listeners
onSliderChange = ([sliderMinSize, sliderPreferredSize, sliderMaxSize]) => {
this.setState({
sliderMinSize,
sliderMaxSize,
sliderPreferredSize
});
this.props.onSizeChange({
minSize: roundNumber(Math.pow(sliderMinSize, 1.1)),
preferredSize: sliderPreferredSize === (slider.max - 3) ? null : roundNumber(Math.pow(sliderPreferredSize, 1.1)),
maxSize: sliderMaxSize === slider.max ? null : roundNumber(Math.pow(sliderMaxSize, 1.1))
});
}
onAfterSliderChange = () => {
const {
minSize,
maxSize,
preferredSize
} = this.props;
this.setState({
sliderMiSize: getSliderValue(minSize, slider.min),
sliderMaxSize: getSliderValue(maxSize, slider.max),
sliderPreferredSize: getSliderValue(preferredSize, (slider.max - 3)) // fix
});
}
onMinSizeChange = ({ value }) => {
const minSize = getValue(value);
this.setState({
sliderMinSize: getSliderValue(minSize, slider.min)
});
this.props.onSizeChange({
minSize,
maxSize: this.props.maxSize,
preferredSize: this.props.preferredSize
});
}
onPreferredSizeChange = ({ value }) => {
const preferredSize = value === (MAX - 3) ? null : getValue(value);
this.setState({
sliderPreferredSize: getSliderValue(preferredSize, slider.preferred)
});
this.props.onSizeChange({
minSize: this.props.minSize,
maxSize: this.props.maxSize,
preferredSize
});
}
onMaxSizeChange = ({ value }) => {
const maxSize = value === MAX ? null : getValue(value);
this.setState({
sliderMaxSize: getSliderValue(maxSize, slider.max)
});
this.props.onSizeChange({
minSize: this.props.minSize,
maxSize,
preferredSize: this.props.preferredSize
});
}
//
// Render
render() {
const {
id,
quality,
title,
minSize,
maxSize,
preferredSize,
advancedSettings,
onTitleChange
} = this.props;
const {
sliderMinSize,
sliderMaxSize,
sliderPreferredSize
} = this.state;
const minBytes = minSize * 1024 * 1024;
const minSixty = `${formatBytes(minBytes * 60)}/h`;
const preferredBytes = preferredSize * 1024 * 1024;
const preferredSixty = preferredBytes ? `${formatBytes(preferredBytes * 60)}/h` : 'Unlimited';
const maxBytes = maxSize && maxSize * 1024 * 1024;
const maxSixty = maxBytes ? `${formatBytes(maxBytes * 60)}/h` : 'Unlimited';
return (
<div className={styles.qualityDefinition}>
<div className={styles.quality}>
{quality.name}
</div>
<div className={styles.title}>
<TextInput
name={`${id}.${title}`}
value={title}
onChange={onTitleChange}
/>
</div>
<div className={styles.sizeLimit}>
<ReactSlider
min={slider.min}
max={slider.max}
step={slider.step}
minDistance={3}
value={[sliderMinSize, sliderPreferredSize, sliderMaxSize]}
withTracks={true}
allowCross={false}
snapDragDisabled={true}
className={styles.slider}
trackClassName={styles.bar}
thumbClassName={styles.handle}
onChange={this.onSliderChange}
onAfterChange={this.onAfterSliderChange}
/>
<div className={styles.sizes}>
<div>
<Popover
anchor={
<Label kind={kinds.INFO}>{minSixty}</Label>
}
title={translate('MinimumLimits')}
body={
<QualityDefinitionLimits
bytes={minBytes}
message={translate('NoMinimumForAnyRuntime')}
/>
}
position={tooltipPositions.BOTTOM}
/>
</div>
<div>
<Popover
anchor={
<Label kind={kinds.SUCCESS}>{preferredSixty}</Label>
}
title={translate('PreferredSize')}
body={
<QualityDefinitionLimits
bytes={preferredBytes}
message={translate('NoLimitForAnyRuntime')}
/>
}
position={tooltipPositions.BOTTOM}
/>
</div>
<div>
<Popover
anchor={
<Label kind={kinds.WARNING}>{maxSixty}</Label>
}
title={translate('MaximumLimits')}
body={
<QualityDefinitionLimits
bytes={maxBytes}
message={translate('NoLimitForAnyRuntime')}
/>
}
position={tooltipPositions.BOTTOM}
/>
</div>
</div>
</div>
{
advancedSettings &&
<div className={styles.megabytesPerMinute}>
<div>
Min
<NumberInput
className={styles.sizeInput}
name={`${id}.min`}
value={minSize || MIN}
min={MIN}
max={preferredSize ? preferredSize - 5 : MAX - 5}
step={0.1}
isFloat={true}
onChange={this.onMinSizeChange}
/>
</div>
<div>
Preferred
<NumberInput
className={styles.sizeInput}
name={`${id}.min`}
value={preferredSize || MAX - 5}
min={MIN}
max={maxSize ? maxSize - 5 : MAX - 5}
step={0.1}
isFloat={true}
onChange={this.onPreferredSizeChange}
/>
</div>
<div>
Max
<NumberInput
className={styles.sizeInput}
name={`${id}.min`}
value={maxSize || MAX}
min={minSize + 5}
max={MAX}
step={0.1}
isFloat={true}
onChange={this.onMaxSizeChange}
/>
</div>
</div>
}
</div>
);
}
}
QualityDefinition.propTypes = {
id: PropTypes.number.isRequired,
quality: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
minSize: PropTypes.number,
maxSize: PropTypes.number,
preferredSize: PropTypes.number,
advancedSettings: PropTypes.bool.isRequired,
onTitleChange: PropTypes.func.isRequired,
onSizeChange: PropTypes.func.isRequired
};
export default QualityDefinition;

@ -1,70 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
import QualityDefinition from './QualityDefinition';
const mapDispatchToProps = {
setQualityDefinitionValue,
clearPendingChanges
};
class QualityDefinitionConnector extends Component {
componentWillUnmount() {
this.props.clearPendingChanges({ section: 'settings.qualityDefinitions' });
}
//
// Listeners
onTitleChange = ({ value }) => {
this.props.setQualityDefinitionValue({ id: this.props.id, name: 'title', value });
}
onSizeChange = ({ minSize, maxSize, preferredSize }) => {
const {
id,
minSize: currentMinSize,
maxSize: currentMaxSize,
preferredSize: currentPreferredSize
} = this.props;
if (minSize !== currentMinSize) {
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
}
if (maxSize !== currentMaxSize) {
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
}
if (preferredSize !== currentPreferredSize) {
this.props.setQualityDefinitionValue({ id, name: 'preferredSize', value: preferredSize });
}
}
//
// Render
render() {
return (
<QualityDefinition
{...this.props}
onTitleChange={this.onTitleChange}
onSizeChange={this.onSizeChange}
/>
);
}
}
QualityDefinitionConnector.propTypes = {
id: PropTypes.number.isRequired,
minSize: PropTypes.number,
maxSize: PropTypes.number,
preferredSize: PropTypes.number,
setQualityDefinitionValue: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(null, mapDispatchToProps)(QualityDefinitionConnector);

@ -1,40 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
function QualityDefinitionLimits(props) {
const {
bytes,
message
} = props;
if (!bytes) {
return <div>{message}</div>;
}
const sixty = formatBytes(bytes * 60);
const ninety = formatBytes(bytes * 90);
const hundredTwenty = formatBytes(bytes * 120);
return (
<div>
<div>
{translate('MinutesSixty', [sixty])}
</div>
<div>
{translate('MinutesNinety', [ninety])}
</div>
<div>
{translate('MinutesHundredTwenty', [hundredTwenty])}
</div>
</div>
);
}
QualityDefinitionLimits.propTypes = {
bytes: PropTypes.number,
message: PropTypes.string.isRequired
};
export default QualityDefinitionLimits;

@ -1,41 +0,0 @@
.header {
display: flex;
font-weight: bold;
}
.quality,
.title {
flex: 0 1 250px;
}
.sizeLimit {
flex: 0 1 500px;
}
.megabytesPerMinute {
flex: 0 0 250px;
}
.sizeLimitHelpTextContainer {
display: flex;
justify-content: flex-end;
margin-top: 20px;
max-width: 1000px;
}
.sizeLimitHelpText {
max-width: 500px;
color: $helpTextColor;
}
@media only screen and (max-width: $breakpointSmall) {
.header {
display: none;
}
.definitions {
&:first-child {
border-top: none;
}
}
}

@ -1,74 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import translate from 'Utilities/String/translate';
import QualityDefinitionConnector from './QualityDefinitionConnector';
import styles from './QualityDefinitions.css';
class QualityDefinitions extends Component {
//
// Render
render() {
const {
items,
advancedSettings,
...otherProps
} = this.props;
return (
<FieldSet legend={translate('QualityDefinitions')}>
<PageSectionContent
errorMessage={translate('UnableToLoadQualityDefinitions')}
{...otherProps}
>
<div className={styles.header}>
<div className={styles.quality}>Quality</div>
<div className={styles.title}>Title</div>
<div className={styles.sizeLimit}>Size Limit</div>
{
advancedSettings ?
<div className={styles.megabytesPerMinute}>
Megabytes Per Minute
</div> :
null
}
</div>
<div className={styles.definitions}>
{
items.map((item) => {
return (
<QualityDefinitionConnector
key={item.id}
{...item}
advancedSettings={advancedSettings}
/>
);
})
}
</div>
<div className={styles.sizeLimitHelpTextContainer}>
<div className={styles.sizeLimitHelpText}>
Limits are automatically adjusted for the movie runtime.
</div>
</div>
</PageSectionContent>
</FieldSet>
);
}
}
QualityDefinitions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
defaultProfile: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
advancedSettings: PropTypes.bool.isRequired
};
export default QualityDefinitions;

@ -1,92 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchQualityDefinitions, saveQualityDefinitions } from 'Store/Actions/settingsActions';
import QualityDefinitions from './QualityDefinitions';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.qualityDefinitions,
(state) => state.settings.advancedSettings,
(qualityDefinitions, advancedSettings) => {
const items = qualityDefinitions.items.map((item) => {
const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {};
return Object.assign({}, item, pendingChanges);
});
return {
...qualityDefinitions,
items,
hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges),
advancedSettings
};
}
);
}
const mapDispatchToProps = {
dispatchFetchQualityDefinitions: fetchQualityDefinitions,
dispatchSaveQualityDefinitions: saveQualityDefinitions
};
class QualityDefinitionsConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchQualityDefinitions();
const {
dispatchFetchQualityDefinitions,
dispatchSaveQualityDefinitions,
onChildMounted
} = this.props;
dispatchFetchQualityDefinitions();
onChildMounted(dispatchSaveQualityDefinitions);
}
componentDidUpdate(prevProps) {
const {
hasPendingChanges,
isSaving,
onChildStateChange
} = this.props;
if (
prevProps.isSaving !== isSaving ||
prevProps.hasPendingChanges !== hasPendingChanges
) {
onChildStateChange({
isSaving,
hasPendingChanges
});
}
}
//
// Render
render() {
return (
<QualityDefinitions
{...this.props}
/>
);
}
}
QualityDefinitionsConnector.propTypes = {
isSaving: PropTypes.bool.isRequired,
hasPendingChanges: PropTypes.bool.isRequired,
dispatchFetchQualityDefinitions: PropTypes.func.isRequired,
dispatchSaveQualityDefinitions: PropTypes.func.isRequired,
onChildMounted: PropTypes.func.isRequired,
onChildStateChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps, null)(QualityDefinitionsConnector);

@ -1,69 +0,0 @@
import React, { Component } from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import QualityDefinitionsConnector from './Definition/QualityDefinitionsConnector';
class Quality extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._saveCallback = null;
this.state = {
isSaving: false,
hasPendingChanges: false
};
}
//
// Listeners
onChildMounted = (saveCallback) => {
this._saveCallback = saveCallback;
}
onChildStateChange = (payload) => {
this.setState(payload);
}
onSavePress = () => {
if (this._saveCallback) {
this._saveCallback();
}
}
//
// Render
render() {
const {
isSaving,
hasPendingChanges
} = this.state;
return (
<PageContent title={translate('QualitySettings')}>
<SettingsToolbarConnector
isSaving={isSaving}
hasPendingChanges={hasPendingChanges}
onSavePress={this.onSavePress}
/>
<PageContentBody>
<QualityDefinitionsConnector
onChildMounted={this.onChildMounted}
onChildStateChange={this.onChildStateChange}
/>
</PageContentBody>
</PageContent>
);
}
}
export default Quality;

@ -7,7 +7,6 @@ import * as indexers from './indexerActions';
import * as indexerIndex from './indexerIndexActions';
import * as indexerStats from './indexerStatsActions';
import * as indexerStatus from './indexerStatusActions';
import * as movies from './movieActions';
import * as oAuth from './oAuthActions';
import * as paths from './pathActions';
import * as providerOptions from './providerOptionActions';
@ -26,7 +25,6 @@ export default [
paths,
providerOptions,
releases,
movies,
indexers,
indexerIndex,
indexerStats,

@ -9,8 +9,10 @@ import createTestProviderHandler, { createCancelTestProviderHandler } from 'Stor
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk, handleThunks } from 'Store/thunks';
import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import translate from 'Utilities/String/translate';
import createHandleActions from './Creators/createHandleActions';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
@ -45,6 +47,22 @@ export const defaultState = {
}
};
export const filters = [
{
key: 'all',
label: translate('All'),
filters: []
}
];
export const filterPredicates = {
added: function(item, filterValue, type) {
return dateFilterPredicate(item.added, filterValue, type);
}
};
export const sortPredicates = {};
//
// Actions Types

@ -9,7 +9,7 @@ import createHandleActions from './Creators/createHandleActions';
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
import { filterPredicates, filters, sortPredicates } from './movieActions';
import { filterPredicates, filters, sortPredicates } from './indexerActions';
//
// Variables

@ -1,193 +0,0 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
// import { batchActions } from 'redux-batched-actions';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
import translate from 'Utilities/String/translate';
import { updateItem } from './baseActions';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer';
//
// Variables
export const section = 'movies';
export const filters = [
{
key: 'all',
label: translate('All'),
filters: []
}
];
export const filterPredicates = {
added: function(item, filterValue, type) {
return dateFilterPredicate(item.added, filterValue, type);
}
};
export const sortPredicates = {
status: function(item) {
let result = 0;
if (item.monitored) {
result += 4;
}
if (item.status === 'announced') {
result++;
}
if (item.status === 'inCinemas') {
result += 2;
}
if (item.status === 'released') {
result += 3;
}
return result;
}
};
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isSaving: false,
saveError: null,
items: [],
sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
pendingChanges: {}
};
//
// Actions Types
export const FETCH_MOVIES = 'movies/fetchMovies';
export const SET_MOVIE_VALUE = 'movies/setMovieValue';
export const SAVE_MOVIE = 'movies/saveMovie';
export const DELETE_MOVIE = 'movies/deleteMovie';
export const TOGGLE_MOVIE_MONITORED = 'movies/toggleMovieMonitored';
//
// Action Creators
export const fetchMovies = createThunk(FETCH_MOVIES);
export const saveMovie = createThunk(SAVE_MOVIE, (payload) => {
const newPayload = {
...payload
};
if (payload.moveFiles) {
newPayload.queryParams = {
moveFiles: true
};
}
delete newPayload.moveFiles;
return newPayload;
});
export const deleteMovie = createThunk(DELETE_MOVIE, (payload) => {
return {
...payload,
queryParams: {
deleteFiles: payload.deleteFiles
}
};
});
export const toggleMovieMonitored = createThunk(TOGGLE_MOVIE_MONITORED);
export const setMovieValue = createAction(SET_MOVIE_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Helpers
function getSaveAjaxOptions({ ajaxOptions, payload }) {
if (payload.moveFolder) {
ajaxOptions.url = `${ajaxOptions.url}?moveFolder=true`;
}
return ajaxOptions;
}
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_MOVIES]: createFetchHandler(section, '/movie'),
[SAVE_MOVIE]: createSaveProviderHandler(section, '/movie', { getAjaxOptions: getSaveAjaxOptions }),
[DELETE_MOVIE]: createRemoveItemHandler(section, '/movie'),
[TOGGLE_MOVIE_MONITORED]: (getState, payload, dispatch) => {
const {
movieId: id,
monitored
} = payload;
const movie = _.find(getState().movies.items, { id });
dispatch(updateItem({
id,
section,
isSaving: true
}));
const promise = createAjaxRequest({
url: `/movie/${id}`,
method: 'PUT',
data: JSON.stringify({
...movie,
monitored
}),
dataType: 'json'
}).request;
promise.done((data) => {
dispatch(updateItem({
id,
section,
isSaving: false,
monitored
}));
});
promise.fail((xhr) => {
dispatch(updateItem({
id,
section,
isSaving: false
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_MOVIE_VALUE]: createSetSettingValueReducer(section)
}, defaultState, section);

@ -1,15 +0,0 @@
import _ from 'lodash';
import { createSelector } from 'reselect';
import createAllMoviesSelector from './createAllMoviesSelector';
function createExistingMovieSelector() {
return createSelector(
(state, { tmdbId }) => tmdbId,
createAllMoviesSelector(),
(tmdbId, movies) => {
return _.some(movies, { tmdbId });
}
);
}
export default createExistingMovieSelector;

@ -1,26 +0,0 @@
import _ from 'lodash';
import { createSelector } from 'reselect';
import createAllMoviesSelector from './createAllMoviesSelector';
function createImportMovieItemSelector() {
return createSelector(
(state, { id }) => id,
(state) => state.addMovie,
(state) => state.importMovie,
createAllMoviesSelector(),
(id, addMovie, importMovie, movies) => {
const item = _.find(importMovie.items, { id }) || {};
const selectedMovie = item && item.selectedMovie;
const isExistingMovie = !!selectedMovie && _.some(movies, { tmdbId: selectedMovie.tmdbId });
return {
defaultMonitor: addMovie.defaults.monitor,
defaultQualityProfileId: addMovie.defaults.qualityProfileId,
...item,
isExistingMovie
};
}
);
}
export default createImportMovieItemSelector;

@ -1,21 +0,0 @@
import { createSelector } from 'reselect';
import createAllMoviesSelector from './createAllMoviesSelector';
function createMovieCountSelector() {
return createSelector(
createAllMoviesSelector(),
(state) => state.movies.error,
(state) => state.movies.isFetching,
(state) => state.movies.isPopulated,
(movies, error, isFetching, isPopulated) => {
return {
count: movies.length,
error,
isFetching,
isPopulated
};
}
);
}
export default createMovieCountSelector;

@ -1,15 +0,0 @@
function formatRuntime(minutes, format) {
if (!minutes) {
return (format === 'hoursMinutes') ? '0m' : '0 mins';
}
if (format === 'minutes') {
return `${minutes} mins`;
}
const movieHours = Math.floor(minutes / 60);
const movieMinutes = (minutes <= 59) ? minutes : minutes % 60;
return `${((movieHours > 0) ? `${movieHours}h ` : '') + movieMinutes}m`;
}
export default formatRuntime;

@ -1,21 +0,0 @@
import _ from 'lodash';
import { update } from 'Store/Actions/baseActions';
function updateEpisodes(section, episodes, episodeIds, options) {
const data = _.reduce(episodes, (result, item) => {
if (episodeIds.indexOf(item.id) > -1) {
result.push({
...item,
...options
});
} else {
result.push(item);
}
return result;
}, []);
return update({ section, data });
}
export default updateEpisodes;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save