mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-04-18 19:15:10 -04:00
Cleanup unused frontend components
This commit is contained in:
parent
6dc475cf53
commit
2b6b17707d
110 changed files with 102 additions and 2602 deletions
2
.gitattributes
vendored
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'
|
||||
|
|
4
build.sh
4
build.sh
|
@ -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
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
debian/compat
vendored
|
@ -1 +0,0 @@
|
|||
8
|
12
debian/control
vendored
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
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
debian/install
vendored
|
@ -1 +0,0 @@
|
|||
nzbdrone_bin/* opt/NzbDrone
|
13
debian/rules
vendored
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…
Add table
Reference in a new issue