mirror of https://github.com/lidarr/Lidarr
Compare commits
120 Commits
v2.1.7.403
...
develop
Author | SHA1 | Date |
---|---|---|
Bogdan | 6ec298ed2a | |
Bogdan | 563db9231e | |
Bogdan | d27b062d6a | |
Bogdan | febb3ef485 | |
sillock1 | 30d9891bf0 | |
Bogdan | 2621acdae5 | |
Bogdan | 18772553f2 | |
Weblate | 0300bf2dd2 | |
Bogdan | 77861e4303 | |
Bogdan | 3545a7451e | |
Mark McDowall | 9f8c4530ca | |
Mickaël Thomas | 9da690f807 | |
Bogdan | 31f342b8ad | |
Bogdan | 596a36d45f | |
Bogdan | 94bb8a436b | |
Weblate | 94d2a20b6a | |
Bogdan | a25e5aae10 | |
Mark McDowall | f4a02ffc83 | |
Bogdan | 1bdcf91014 | |
Bogdan | 4d28d3f25a | |
Bogdan | 9660ec37cd | |
Bogdan | 66c7521f4b | |
Stevie Robinson | 8b57b33c99 | |
Mark McDowall | 580e4becbe | |
Christopher | 5f248aa25e | |
Bogdan | a735eccb65 | |
Weblate | d11ed42830 | |
Bogdan | b0038dd143 | |
Bogdan | 2e242aeb7b | |
Bogdan | 416d505316 | |
Bogdan | 4816f35256 | |
Josh McKinney | e42e0a72eb | |
Bogdan | db9e62f79d | |
Bogdan | bc69fa4842 | |
Bogdan | 86dad72c49 | |
Bogdan | 4a8d6c367d | |
Bogdan | c1926f8758 | |
Bogdan | 7820bcf91f | |
Servarr | 431ad0a028 | |
Bogdan | 59cf7a95c3 | |
Bogdan | e17e3633f8 | |
Bogdan | 46da2b49c6 | |
Josh McKinney | 3071977284 | |
Mark McDowall | b14e2bb618 | |
Mark McDowall | 8c09c0cb5c | |
Weblate | 8cebb21c2d | |
Bogdan | 74ac263b74 | |
Bogdan | adcec90ef8 | |
Qstick | daf8b94c8e | |
Bogdan | 7c4f0c597e | |
Bogdan | 1d2af2aab4 | |
Stevie Robinson | 5d537689fb | |
Cuki | ca6beea62b | |
Mark McDowall | a82c919093 | |
Mark McDowall | 2941e0c4b7 | |
Bogdan | ca0b900d92 | |
Weblate | 72f1b2075b | |
Bogdan | e847828191 | |
Bogdan | 2a10505dff | |
Mark McDowall | 28f2eb974d | |
Servarr | 13ce040e4d | |
Weblate | f477f9b287 | |
Bogdan | 0e84008669 | |
Louis R | 52b5ff6fdd | |
Carlos Gustavo Sarmiento | 1d0de51917 | |
Stevie Robinson | a8648fdb71 | |
Bogdan | f890a8c18f | |
Mark McDowall | e730cf6307 | |
Bogdan | 9f4d821a2d | |
Weblate | ce6e4555ec | |
Mark McDowall | 55eaecb3c8 | |
Bogdan | 63e36f71d2 | |
Bogdan | 89e184e768 | |
Mark McDowall | 873a225f0c | |
Stevie Robinson | b81170d911 | |
Servarr | 5ffde40320 | |
Mark McDowall | ebfa68087d | |
Bogdan | 1db0eb1029 | |
Bogdan | 967b58017a | |
Bogdan | 3754d611c7 | |
Weblate | 8035d4202f | |
Bogdan | 468f3acf85 | |
Bogdan | 29c77ec3a1 | |
bpoxy | d04bb5333a | |
Bogdan | 0d76fbcf0d | |
Weblate | 3df140b1f0 | |
Bogdan | 340ae78f46 | |
Helvio Pedreschi | 881fabad93 | |
Mark McDowall | be8f7e5618 | |
Bogdan | c9743448fd | |
Bogdan | 47e647ddb1 | |
Mark McDowall | f6529d5ad3 | |
Mark McDowall | fb1b7274d0 | |
Louis R | 33b12a532c | |
nopoz | cea5ee503f | |
Bogdan | 475590a21b | |
Weblate | 0ca0f68af1 | |
Bogdan | 2c19b5aa61 | |
Weblate | 7e0c5e0da5 | |
Mark McDowall | adecb7f73c | |
Mark McDowall | 98a90e2f8f | |
Bogdan | ce2bb5be1f | |
Bogdan | e446c25a01 | |
servarr[bot] | d38c101acd | |
Bogdan | 022fbf864c | |
Bogdan | 3ff9b8bd85 | |
Bogdan | 57926a61d2 | |
bakerboy448 | 87f88af7ee | |
Weblate | 30fc3fc70a | |
Bogdan | 4abca0c896 | |
Bogdan | b2f595436b | |
Bogdan | e7ae0b9e22 | |
Bogdan | 0431b257e1 | |
abcasada | 479e8cce20 | |
Bogdan | 27723eb3ea | |
Qstick | 616b529c9a | |
Qstick | 8b85d4c941 | |
Servarr | f13b095040 | |
Qstick | a4af75b60c | |
Qstick | c7faf7cc25 |
|
@ -0,0 +1,13 @@
|
||||||
|
// This file is used to open the backend and frontend in the same workspace, which is necessary as
|
||||||
|
// the frontend has vscode settings that are distinct from the backend
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": ".."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../frontend"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
|
||||||
|
{
|
||||||
|
"name": "Lidarr",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
"nodeGypDependencies": true,
|
||||||
|
"version": "16",
|
||||||
|
"nvmVersion": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forwardPorts": [8686],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": ["esbenp.prettier-vscode"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for more information:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
# https://containers.dev/guide/dependabot
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "devcontainers"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
|
@ -126,6 +126,7 @@ coverage*.xml
|
||||||
coverage*.json
|
coverage*.json
|
||||||
setup/Output/
|
setup/Output/
|
||||||
*.~is
|
*.~is
|
||||||
|
.mono
|
||||||
|
|
||||||
# VS outout folders
|
# VS outout folders
|
||||||
bin
|
bin
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"ms-dotnettools.csdevkit",
|
||||||
|
"ms-vscode-remote.remote-containers"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
|
||||||
|
"name": "Run Lidarr",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build dotnet",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/_output/net6.0/Lidarr",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build dotnet",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"msbuild",
|
||||||
|
"-restore",
|
||||||
|
"${workspaceFolder}/src/Lidarr.sln",
|
||||||
|
"-p:GenerateFullPaths=true",
|
||||||
|
"-p:Configuration=Debug",
|
||||||
|
"-p:Platform=Posix",
|
||||||
|
"-consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/src/Lidarr.sln",
|
||||||
|
"-property:GenerateFullPaths=true",
|
||||||
|
"-consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/src/Lidarr.sln"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -9,14 +9,14 @@ variables:
|
||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '2.1.7'
|
majorVersion: '2.4.0'
|
||||||
minorVersion: $[counter('minorVersion', 1076)]
|
minorVersion: $[counter('minorVersion', 1076)]
|
||||||
lidarrVersion: '$(majorVersion).$(minorVersion)'
|
lidarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(lidarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(lidarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.417'
|
dotnetVersion: '6.0.421'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '20.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
linuxImage: 'ubuntu-20.04'
|
linuxImage: 'ubuntu-20.04'
|
||||||
|
@ -166,10 +166,10 @@ stages:
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: UseNode@1
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: $(nodeVersion)
|
version: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
|
@ -1093,10 +1093,10 @@ stages:
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: UseNode@1
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: $(nodeVersion)
|
version: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
|
|
|
@ -28,7 +28,8 @@ module.exports = {
|
||||||
globals: {
|
globals: {
|
||||||
expect: false,
|
expect: false,
|
||||||
chai: false,
|
chai: false,
|
||||||
sinon: false
|
sinon: false,
|
||||||
|
JSX: true
|
||||||
},
|
},
|
||||||
|
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
|
|
@ -53,7 +53,7 @@ class DeleteAlbumModalContent extends Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
statistics,
|
statistics = {},
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
|
|
@ -35,3 +35,9 @@
|
||||||
|
|
||||||
width: 55px;
|
width: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indexerFlags {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ interface CssExports {
|
||||||
'audio': string;
|
'audio': string;
|
||||||
'customFormatScore': string;
|
'customFormatScore': string;
|
||||||
'duration': string;
|
'duration': string;
|
||||||
|
'indexerFlags': string;
|
||||||
'monitored': string;
|
'monitored': string;
|
||||||
'size': string;
|
'size': string;
|
||||||
'status': string;
|
'status': string;
|
||||||
|
|
|
@ -2,15 +2,19 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import AlbumFormats from 'Album/AlbumFormats';
|
import AlbumFormats from 'Album/AlbumFormats';
|
||||||
import EpisodeStatusConnector from 'Album/EpisodeStatusConnector';
|
import EpisodeStatusConnector from 'Album/EpisodeStatusConnector';
|
||||||
|
import IndexerFlags from 'Album/IndexerFlags';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
import MediaInfoConnector from 'TrackFile/MediaInfoConnector';
|
import MediaInfoConnector from 'TrackFile/MediaInfoConnector';
|
||||||
import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes';
|
import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes';
|
||||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import TrackActionsCell from './TrackActionsCell';
|
import TrackActionsCell from './TrackActionsCell';
|
||||||
import styles from './TrackRow.css';
|
import styles from './TrackRow.css';
|
||||||
|
|
||||||
|
@ -32,6 +36,7 @@ class TrackRow extends Component {
|
||||||
trackFileSize,
|
trackFileSize,
|
||||||
customFormats,
|
customFormats,
|
||||||
customFormatScore,
|
customFormatScore,
|
||||||
|
indexerFlags,
|
||||||
columns,
|
columns,
|
||||||
deleteTrackFile
|
deleteTrackFile
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -141,12 +146,30 @@ class TrackRow extends Component {
|
||||||
customFormats.length
|
customFormats.length
|
||||||
)}
|
)}
|
||||||
tooltip={<AlbumFormats formats={customFormats} />}
|
tooltip={<AlbumFormats formats={customFormats} />}
|
||||||
position={tooltipPositions.BOTTOM}
|
position={tooltipPositions.LEFT}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'indexerFlags') {
|
||||||
|
return (
|
||||||
|
<TableRowCell
|
||||||
|
key={name}
|
||||||
|
className={styles.indexerFlags}
|
||||||
|
>
|
||||||
|
{indexerFlags ? (
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||||
|
title={translate('IndexerFlags')}
|
||||||
|
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'size') {
|
if (name === 'size') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
|
@ -208,12 +231,14 @@ TrackRow.propTypes = {
|
||||||
trackFileSize: PropTypes.number,
|
trackFileSize: PropTypes.number,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
customFormatScore: PropTypes.number.isRequired,
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
mediaInfo: PropTypes.object,
|
mediaInfo: PropTypes.object,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
TrackRow.defaultProps = {
|
TrackRow.defaultProps = {
|
||||||
customFormats: []
|
customFormats: [],
|
||||||
|
indexerFlags: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TrackRow;
|
export default TrackRow;
|
||||||
|
|
|
@ -13,7 +13,8 @@ function createMapStateToProps() {
|
||||||
trackFilePath: trackFile ? trackFile.path : null,
|
trackFilePath: trackFile ? trackFile.path : null,
|
||||||
trackFileSize: trackFile ? trackFile.size : null,
|
trackFileSize: trackFile ? trackFile.size : null,
|
||||||
customFormats: trackFile ? trackFile.customFormats : [],
|
customFormats: trackFile ? trackFile.customFormats : [],
|
||||||
customFormatScore: trackFile ? trackFile.customFormatScore : 0
|
customFormatScore: trackFile ? trackFile.customFormatScore : 0,
|
||||||
|
indexerFlags: trackFile ? trackFile.indexerFlags : 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,7 +35,7 @@ class EditAlbumModalContent extends Component {
|
||||||
title,
|
title,
|
||||||
artistName,
|
artistName,
|
||||||
albumType,
|
albumType,
|
||||||
statistics,
|
statistics = {},
|
||||||
item,
|
item,
|
||||||
isSaving,
|
isSaving,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import createIndexerFlagsSelector from 'Store/Selectors/createIndexerFlagsSelector';
|
||||||
|
|
||||||
|
interface IndexerFlagsProps {
|
||||||
|
indexerFlags: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IndexerFlags({ indexerFlags = 0 }: IndexerFlagsProps) {
|
||||||
|
const allIndexerFlags = useSelector(createIndexerFlagsSelector);
|
||||||
|
|
||||||
|
const flags = allIndexerFlags.items.filter(
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
(item) => (indexerFlags & item.id) === item.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return flags.length ? (
|
||||||
|
<ul>
|
||||||
|
{flags.map((flag, index) => {
|
||||||
|
return <li key={index}>{flag.name}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexerFlags;
|
|
@ -15,7 +15,7 @@ function AlbumInteractiveSearchModal(props) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
size={sizes.EXTRA_LARGE}
|
size={sizes.EXTRA_EXTRA_LARGE}
|
||||||
closeOnBackgroundClick={false}
|
closeOnBackgroundClick={false}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
function AlbumInteractiveSearchModalContent(props) {
|
function AlbumInteractiveSearchModalContent(props) {
|
||||||
const {
|
const {
|
||||||
|
@ -18,7 +19,10 @@ function AlbumInteractiveSearchModalContent(props) {
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Interactive Search {albumId != null && `- ${albumTitle}`}
|
{albumTitle === undefined ?
|
||||||
|
translate('InteractiveSearchModalHeader') :
|
||||||
|
translate('InteractiveSearchModalHeaderTitle', { title: albumTitle })
|
||||||
|
}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
||||||
|
@ -32,7 +36,7 @@ function AlbumInteractiveSearchModalContent(props) {
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onModalClose}>
|
||||||
Close
|
{translate('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -12,11 +12,10 @@ function App({ store, history }) {
|
||||||
<DocumentTitle title={window.Lidarr.instanceName}>
|
<DocumentTitle title={window.Lidarr.instanceName}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<ApplyTheme>
|
<ApplyTheme />
|
||||||
<PageConnector>
|
<PageConnector>
|
||||||
<AppRoutes app={App} />
|
<AppRoutes app={App} />
|
||||||
</PageConnector>
|
</PageConnector>
|
||||||
</ApplyTheme>
|
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
</DocumentTitle>
|
</DocumentTitle>
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import themes from 'Styles/Themes';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.settings.ui.item.theme || window.Lidarr.theme,
|
|
||||||
(
|
|
||||||
theme
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
theme
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ApplyTheme({ theme, children }) {
|
|
||||||
// Update the CSS Variables
|
|
||||||
|
|
||||||
const updateCSSVariables = useCallback(() => {
|
|
||||||
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
|
||||||
const arrayOfVariableValues = Object.values(themes[theme]);
|
|
||||||
|
|
||||||
// Loop through each array key and set the CSS Variables
|
|
||||||
arrayOfVariableKeys.forEach((cssVariableKey, index) => {
|
|
||||||
// Based on our snippet from MDN
|
|
||||||
document.documentElement.style.setProperty(
|
|
||||||
`--${cssVariableKey}`,
|
|
||||||
arrayOfVariableValues[index]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [theme]);
|
|
||||||
|
|
||||||
// On Component Mount and Component Update
|
|
||||||
useEffect(() => {
|
|
||||||
updateCSSVariables(theme);
|
|
||||||
}, [updateCSSVariables, theme]);
|
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplyTheme.propTypes = {
|
|
||||||
theme: PropTypes.string.isRequired,
|
|
||||||
children: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(ApplyTheme);
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React, { Fragment, ReactNode, useCallback, useEffect } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import themes from 'Styles/Themes';
|
||||||
|
import AppState from './State/AppState';
|
||||||
|
|
||||||
|
interface ApplyThemeProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createThemeSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.settings.ui.item.theme || window.Lidarr.theme,
|
||||||
|
(theme) => {
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApplyTheme({ children }: ApplyThemeProps) {
|
||||||
|
const theme = useSelector(createThemeSelector());
|
||||||
|
|
||||||
|
const updateCSSVariables = useCallback(() => {
|
||||||
|
Object.entries(themes[theme]).forEach(([key, value]) => {
|
||||||
|
document.documentElement.style.setProperty(`--${key}`, value);
|
||||||
|
});
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
// On Component Mount and Component Update
|
||||||
|
useEffect(() => {
|
||||||
|
updateCSSVariables();
|
||||||
|
}, [updateCSSVariables, theme]);
|
||||||
|
|
||||||
|
return <Fragment>{children}</Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApplyTheme;
|
|
@ -1,8 +1,11 @@
|
||||||
import AlbumAppState from './AlbumAppState';
|
import AlbumAppState from './AlbumAppState';
|
||||||
import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState';
|
import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState';
|
||||||
|
import CalendarAppState from './CalendarAppState';
|
||||||
|
import CommandAppState from './CommandAppState';
|
||||||
import HistoryAppState from './HistoryAppState';
|
import HistoryAppState from './HistoryAppState';
|
||||||
import QueueAppState from './QueueAppState';
|
import QueueAppState from './QueueAppState';
|
||||||
import SettingsAppState from './SettingsAppState';
|
import SettingsAppState from './SettingsAppState';
|
||||||
|
import SystemAppState from './SystemAppState';
|
||||||
import TagsAppState from './TagsAppState';
|
import TagsAppState from './TagsAppState';
|
||||||
import TrackFilesAppState from './TrackFilesAppState';
|
import TrackFilesAppState from './TrackFilesAppState';
|
||||||
import TracksAppState from './TracksAppState';
|
import TracksAppState from './TracksAppState';
|
||||||
|
@ -52,12 +55,15 @@ interface AppState {
|
||||||
app: AppSectionState;
|
app: AppSectionState;
|
||||||
artist: ArtistAppState;
|
artist: ArtistAppState;
|
||||||
artistIndex: ArtistIndexAppState;
|
artistIndex: ArtistIndexAppState;
|
||||||
|
calendar: CalendarAppState;
|
||||||
|
commands: CommandAppState;
|
||||||
history: HistoryAppState;
|
history: HistoryAppState;
|
||||||
queue: QueueAppState;
|
queue: QueueAppState;
|
||||||
settings: SettingsAppState;
|
settings: SettingsAppState;
|
||||||
tags: TagsAppState;
|
tags: TagsAppState;
|
||||||
trackFiles: TrackFilesAppState;
|
trackFiles: TrackFilesAppState;
|
||||||
tracksSelection: TracksAppState;
|
tracksSelection: TracksAppState;
|
||||||
|
system: SystemAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppState;
|
export default AppState;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Album from 'Album/Album';
|
||||||
|
import AppSectionState, {
|
||||||
|
AppSectionFilterState,
|
||||||
|
} from 'App/State/AppSectionState';
|
||||||
|
|
||||||
|
interface CalendarAppState
|
||||||
|
extends AppSectionState<Album>,
|
||||||
|
AppSectionFilterState<Album> {}
|
||||||
|
|
||||||
|
export default CalendarAppState;
|
|
@ -0,0 +1,6 @@
|
||||||
|
import AppSectionState from 'App/State/AppSectionState';
|
||||||
|
import Command from 'Commands/Command';
|
||||||
|
|
||||||
|
export type CommandAppState = AppSectionState<Command>;
|
||||||
|
|
||||||
|
export default CommandAppState;
|
|
@ -1,11 +1,13 @@
|
||||||
import AppSectionState, {
|
import AppSectionState, {
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
|
AppSectionItemState,
|
||||||
AppSectionSaveState,
|
AppSectionSaveState,
|
||||||
AppSectionSchemaState,
|
AppSectionSchemaState,
|
||||||
} from 'App/State/AppSectionState';
|
} from 'App/State/AppSectionState';
|
||||||
import DownloadClient from 'typings/DownloadClient';
|
import DownloadClient from 'typings/DownloadClient';
|
||||||
import ImportList from 'typings/ImportList';
|
import ImportList from 'typings/ImportList';
|
||||||
import Indexer from 'typings/Indexer';
|
import Indexer from 'typings/Indexer';
|
||||||
|
import IndexerFlag from 'typings/IndexerFlag';
|
||||||
import MetadataProfile from 'typings/MetadataProfile';
|
import MetadataProfile from 'typings/MetadataProfile';
|
||||||
import Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
import QualityProfile from 'typings/QualityProfile';
|
import QualityProfile from 'typings/QualityProfile';
|
||||||
|
@ -44,17 +46,19 @@ export interface RootFolderAppState
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
AppSectionSaveState {}
|
AppSectionSaveState {}
|
||||||
|
|
||||||
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||||
|
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
importLists: ImportListAppState;
|
importLists: ImportListAppState;
|
||||||
|
indexerFlags: IndexerFlagSettingsAppState;
|
||||||
indexers: IndexerAppState;
|
indexers: IndexerAppState;
|
||||||
metadataProfiles: MetadataProfilesAppState;
|
metadataProfiles: MetadataProfilesAppState;
|
||||||
notifications: NotificationAppState;
|
notifications: NotificationAppState;
|
||||||
qualityProfiles: QualityProfilesAppState;
|
qualityProfiles: QualityProfilesAppState;
|
||||||
rootFolders: RootFolderAppState;
|
rootFolders: RootFolderAppState;
|
||||||
uiSettings: UiSettingsAppState;
|
ui: UiSettingsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAppState;
|
export default SettingsAppState;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import SystemStatus from 'typings/SystemStatus';
|
||||||
|
import { AppSectionItemState } from './AppSectionState';
|
||||||
|
|
||||||
|
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
||||||
|
|
||||||
|
interface SystemAppState {
|
||||||
|
status: SystemStatusAppState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemAppState;
|
|
@ -1,12 +1,32 @@
|
||||||
import ModelBase from 'App/ModelBase';
|
import ModelBase from 'App/ModelBase';
|
||||||
import AppSectionState, {
|
import AppSectionState, {
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState,
|
||||||
} from 'App/State/AppSectionState';
|
} from 'App/State/AppSectionState';
|
||||||
|
|
||||||
export interface Tag extends ModelBase {
|
export interface Tag extends ModelBase {
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {}
|
export interface TagDetail extends ModelBase {
|
||||||
|
label: string;
|
||||||
|
autoTagIds: number[];
|
||||||
|
delayProfileIds: number[];
|
||||||
|
downloadClientIds: [];
|
||||||
|
importListIds: number[];
|
||||||
|
indexerIds: number[];
|
||||||
|
notificationIds: number[];
|
||||||
|
restrictionIds: number[];
|
||||||
|
artistIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagDetailAppState
|
||||||
|
extends AppSectionState<TagDetail>,
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState {}
|
||||||
|
|
||||||
|
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {
|
||||||
|
details: TagDetailAppState;
|
||||||
|
}
|
||||||
|
|
||||||
export default TagsAppState;
|
export default TagsAppState;
|
||||||
|
|
|
@ -135,14 +135,14 @@ class DeleteArtistModalContent extends Component {
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onModalClose}>
|
||||||
Close
|
{translate('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={this.onDeleteArtistConfirmed}
|
onPress={this.onDeleteArtistConfirmed}
|
||||||
>
|
>
|
||||||
Delete
|
{translate('Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
@ -161,9 +161,7 @@ DeleteArtistModalContent.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
DeleteArtistModalContent.defaultProps = {
|
DeleteArtistModalContent.defaultProps = {
|
||||||
statistics: {
|
statistics: {}
|
||||||
trackFileCount: 0
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeleteArtistModalContent;
|
export default DeleteArtistModalContent;
|
||||||
|
|
|
@ -192,7 +192,7 @@ class ArtistDetails extends Component {
|
||||||
artistName,
|
artistName,
|
||||||
ratings,
|
ratings,
|
||||||
path,
|
path,
|
||||||
statistics,
|
statistics = {},
|
||||||
qualityProfileId,
|
qualityProfileId,
|
||||||
monitored,
|
monitored,
|
||||||
genres,
|
genres,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { Statistics } from 'Album/Album';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
@ -56,7 +57,7 @@ function AlbumDetails(props: AlbumDetailsProps) {
|
||||||
disambiguation,
|
disambiguation,
|
||||||
albumType,
|
albumType,
|
||||||
monitored,
|
monitored,
|
||||||
statistics,
|
statistics = {} as Statistics,
|
||||||
isSaving = false,
|
isSaving = false,
|
||||||
} = album;
|
} = album;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ const monitoredOptions = [
|
||||||
get value() {
|
get value() {
|
||||||
return translate('NoChange');
|
return translate('NoChange');
|
||||||
},
|
},
|
||||||
disabled: true,
|
isDisabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'monitored',
|
||||||
|
|
|
@ -14,7 +14,7 @@ function ArtistInteractiveSearchModal(props) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
size={sizes.EXTRA_LARGE}
|
size={sizes.EXTRA_EXTRA_LARGE}
|
||||||
closeOnBackgroundClick={false}
|
closeOnBackgroundClick={false}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import FilterModal from 'Components/Filter/FilterModal';
|
||||||
|
import { setCalendarFilter } from 'Store/Actions/calendarActions';
|
||||||
|
|
||||||
|
function createCalendarSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.calendar.items,
|
||||||
|
(calendar) => {
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFilterBuilderPropsSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.calendar.filterBuilderProps,
|
||||||
|
(filterBuilderProps) => {
|
||||||
|
return filterBuilderProps;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CalendarFilterModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CalendarFilterModal(props: CalendarFilterModalProps) {
|
||||||
|
const sectionItems = useSelector(createCalendarSelector());
|
||||||
|
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||||
|
const customFilterType = 'calendar';
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const dispatchSetFilter = useCallback(
|
||||||
|
(payload: unknown) => {
|
||||||
|
dispatch(setCalendarFilter(payload));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilterModal
|
||||||
|
// TODO: Don't spread all the props
|
||||||
|
{...props}
|
||||||
|
sectionItems={sectionItems}
|
||||||
|
filterBuilderProps={filterBuilderProps}
|
||||||
|
customFilterType={customFilterType}
|
||||||
|
dispatchSetFilter={dispatchSetFilter}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import { align, icons } from 'Helpers/Props';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import CalendarConnector from './CalendarConnector';
|
import CalendarConnector from './CalendarConnector';
|
||||||
|
import CalendarFilterModal from './CalendarFilterModal';
|
||||||
import CalendarLinkModal from './iCal/CalendarLinkModal';
|
import CalendarLinkModal from './iCal/CalendarLinkModal';
|
||||||
import LegendConnector from './Legend/LegendConnector';
|
import LegendConnector from './Legend/LegendConnector';
|
||||||
import CalendarOptionsModal from './Options/CalendarOptionsModal';
|
import CalendarOptionsModal from './Options/CalendarOptionsModal';
|
||||||
|
@ -78,6 +79,7 @@ class CalendarPage extends Component {
|
||||||
const {
|
const {
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
customFilters,
|
||||||
hasArtist,
|
hasArtist,
|
||||||
artistError,
|
artistError,
|
||||||
artistIsFetching,
|
artistIsFetching,
|
||||||
|
@ -137,7 +139,8 @@ class CalendarPage extends Component {
|
||||||
isDisabled={!hasArtist}
|
isDisabled={!hasArtist}
|
||||||
selectedFilterKey={selectedFilterKey}
|
selectedFilterKey={selectedFilterKey}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
customFilters={[]}
|
customFilters={customFilters}
|
||||||
|
filterModalConnectorComponent={CalendarFilterModal}
|
||||||
onFilterSelect={onFilterSelect}
|
onFilterSelect={onFilterSelect}
|
||||||
/>
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
@ -204,6 +207,7 @@ class CalendarPage extends Component {
|
||||||
CalendarPage.propTypes = {
|
CalendarPage.propTypes = {
|
||||||
selectedFilterKey: PropTypes.string.isRequired,
|
selectedFilterKey: PropTypes.string.isRequired,
|
||||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
hasArtist: PropTypes.bool.isRequired,
|
hasArtist: PropTypes.bool.isRequired,
|
||||||
artistError: PropTypes.object,
|
artistError: PropTypes.object,
|
||||||
artistIsFetching: PropTypes.bool.isRequired,
|
artistIsFetching: PropTypes.bool.isRequired,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import withCurrentPage from 'Components/withCurrentPage';
|
||||||
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import createArtistCountSelector from 'Store/Selectors/createArtistCountSelector';
|
import createArtistCountSelector from 'Store/Selectors/createArtistCountSelector';
|
||||||
|
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
|
@ -59,6 +60,7 @@ function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.calendar.selectedFilterKey,
|
(state) => state.calendar.selectedFilterKey,
|
||||||
(state) => state.calendar.filters,
|
(state) => state.calendar.filters,
|
||||||
|
createCustomFiltersSelector('calendar'),
|
||||||
createArtistCountSelector(),
|
createArtistCountSelector(),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
createMissingAlbumIdsSelector(),
|
createMissingAlbumIdsSelector(),
|
||||||
|
@ -67,6 +69,7 @@ function createMapStateToProps() {
|
||||||
(
|
(
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
customFilters,
|
||||||
artistCount,
|
artistCount,
|
||||||
uiSettings,
|
uiSettings,
|
||||||
missingAlbumIds,
|
missingAlbumIds,
|
||||||
|
@ -76,6 +79,7 @@ function createMapStateToProps() {
|
||||||
return {
|
return {
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
customFilters,
|
||||||
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
||||||
hasArtist: !!artistCount.count,
|
hasArtist: !!artistCount.count,
|
||||||
artistError: artistCount.error,
|
artistError: artistCount.error,
|
||||||
|
|
|
@ -13,6 +13,7 @@ export interface CommandBody {
|
||||||
trigger: string;
|
trigger: string;
|
||||||
suppressMessages: boolean;
|
suppressMessages: boolean;
|
||||||
artistId?: number;
|
artistId?: number;
|
||||||
|
artistIds?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Command extends ModelBase {
|
interface Command extends ModelBase {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { maxBy } from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
@ -50,7 +51,7 @@ class FilterBuilderModalContent extends Component {
|
||||||
if (id) {
|
if (id) {
|
||||||
dispatchSetFilter({ selectedFilterKey: id });
|
dispatchSetFilter({ selectedFilterKey: id });
|
||||||
} else {
|
} else {
|
||||||
const last = customFilters[customFilters.length -1];
|
const last = maxBy(customFilters, 'id');
|
||||||
dispatchSetFilter({ selectedFilterKey: last.id });
|
dispatchSetFilter({ selectedFilterKey: last.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +109,7 @@ class FilterBuilderModalContent extends Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
labelErrors: [
|
labelErrors: [
|
||||||
{
|
{
|
||||||
message: 'Label is required'
|
message: translate('LabelIsRequired')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -146,7 +147,7 @@ class FilterBuilderModalContent extends Component {
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Custom Filter
|
{translate('CustomFilter')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
@ -166,7 +167,9 @@ class FilterBuilderModalContent extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.label}>{translate('Filters')}</div>
|
<div className={styles.label}>
|
||||||
|
{translate('Filters')}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.rows}>
|
<div className={styles.rows}>
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,8 +37,8 @@ class CustomFilter extends Component {
|
||||||
dispatchSetFilter
|
dispatchSetFilter
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// Assume that delete and then unmounting means the delete was successful.
|
// Assume that delete and then unmounting means the deletion was successful.
|
||||||
// Moving this check to a ancestor would be more accurate, but would have
|
// Moving this check to an ancestor would be more accurate, but would have
|
||||||
// more boilerplate.
|
// more boilerplate.
|
||||||
if (this.state.isDeleting && id === selectedFilterKey) {
|
if (this.state.isDeleting && id === selectedFilterKey) {
|
||||||
dispatchSetFilter({ selectedFilterKey: 'all' });
|
dispatchSetFilter({ selectedFilterKey: 'all' });
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import TagInputConnector from './TagInputConnector';
|
||||||
|
|
||||||
|
interface ArtistTagInputProps {
|
||||||
|
name: string;
|
||||||
|
value: number | number[];
|
||||||
|
onChange: ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
value: number | number[];
|
||||||
|
}) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ArtistTagInput(props: ArtistTagInputProps) {
|
||||||
|
const { value, onChange, ...otherProps } = props;
|
||||||
|
const isArray = Array.isArray(value);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
({ name, value: newValue }: { name: string; value: number[] }) => {
|
||||||
|
if (isArray) {
|
||||||
|
onChange({ name, value: newValue });
|
||||||
|
} else {
|
||||||
|
onChange({
|
||||||
|
name,
|
||||||
|
value: newValue.length ? newValue[newValue.length - 1] : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isArray, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
let finalValue: number[] = [];
|
||||||
|
|
||||||
|
if (isArray) {
|
||||||
|
finalValue = value;
|
||||||
|
} else if (value === 0) {
|
||||||
|
finalValue = [];
|
||||||
|
} else {
|
||||||
|
finalValue = [value];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore 2786 'TagInputConnector' isn't typed yet
|
||||||
|
<TagInputConnector
|
||||||
|
{...otherProps}
|
||||||
|
value={finalValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -25,7 +25,8 @@ function createMapStateToProps() {
|
||||||
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
|
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
|
||||||
return {
|
return {
|
||||||
key: downloadClient.id,
|
key: downloadClient.id,
|
||||||
value: downloadClient.name
|
value: downloadClient.name,
|
||||||
|
hint: `(${downloadClient.id})`
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
.isDisabled {
|
.isDisabled {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownArrowContainer {
|
.dropdownArrowContainer {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Link from 'Components/Link/Link';
|
||||||
import { inputTypes, kinds } from 'Helpers/Props';
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AlbumReleaseSelectInputConnector from './AlbumReleaseSelectInputConnector';
|
import AlbumReleaseSelectInputConnector from './AlbumReleaseSelectInputConnector';
|
||||||
|
import ArtistTagInput from './ArtistTagInput';
|
||||||
import AutoCompleteInput from './AutoCompleteInput';
|
import AutoCompleteInput from './AutoCompleteInput';
|
||||||
import CaptchaInputConnector from './CaptchaInputConnector';
|
import CaptchaInputConnector from './CaptchaInputConnector';
|
||||||
import CheckInput from './CheckInput';
|
import CheckInput from './CheckInput';
|
||||||
|
@ -12,6 +13,7 @@ import DownloadClientSelectInputConnector from './DownloadClientSelectInputConne
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||||
import FormInputHelpText from './FormInputHelpText';
|
import FormInputHelpText from './FormInputHelpText';
|
||||||
|
import IndexerFlagsSelectInput from './IndexerFlagsSelectInput';
|
||||||
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
|
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
|
||||||
import KeyValueListInput from './KeyValueListInput';
|
import KeyValueListInput from './KeyValueListInput';
|
||||||
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
|
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
|
||||||
|
@ -83,6 +85,9 @@ function getComponent(type) {
|
||||||
case inputTypes.INDEXER_SELECT:
|
case inputTypes.INDEXER_SELECT:
|
||||||
return IndexerSelectInputConnector;
|
return IndexerSelectInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||||
|
return IndexerFlagsSelectInput;
|
||||||
|
|
||||||
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
||||||
return DownloadClientSelectInputConnector;
|
return DownloadClientSelectInputConnector;
|
||||||
|
|
||||||
|
@ -95,6 +100,9 @@ function getComponent(type) {
|
||||||
case inputTypes.DYNAMIC_SELECT:
|
case inputTypes.DYNAMIC_SELECT:
|
||||||
return EnhancedSelectInputConnector;
|
return EnhancedSelectInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.ARTIST_TAG:
|
||||||
|
return ArtistTagInput;
|
||||||
|
|
||||||
case inputTypes.SERIES_TYPE_SELECT:
|
case inputTypes.SERIES_TYPE_SELECT:
|
||||||
return SeriesTypeSelectInput;
|
return SeriesTypeSelectInput;
|
||||||
|
|
||||||
|
@ -292,6 +300,7 @@ FormInputGroup.propTypes = {
|
||||||
includeNoChangeDisabled: PropTypes.bool,
|
includeNoChangeDisabled: PropTypes.bool,
|
||||||
includeNone: PropTypes.bool,
|
includeNone: PropTypes.bool,
|
||||||
selectedValueOptions: PropTypes.object,
|
selectedValueOptions: PropTypes.object,
|
||||||
|
indexerFlags: PropTypes.number,
|
||||||
pending: PropTypes.bool,
|
pending: PropTypes.bool,
|
||||||
errors: PropTypes.arrayOf(PropTypes.object),
|
errors: PropTypes.arrayOf(PropTypes.object),
|
||||||
warnings: PropTypes.arrayOf(PropTypes.object),
|
warnings: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
|
const selectIndexerFlagsValues = (selectedFlags: number) =>
|
||||||
|
createSelector(
|
||||||
|
(state: AppState) => state.settings.indexerFlags,
|
||||||
|
(indexerFlags) => {
|
||||||
|
const value = indexerFlags.items.reduce((acc: number[], { id }) => {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
if ((selectedFlags & id) === id) {
|
||||||
|
acc.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const values = indexerFlags.items.map(({ id, name }) => ({
|
||||||
|
key: id,
|
||||||
|
value: name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
values,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IndexerFlagsSelectInputProps {
|
||||||
|
name: string;
|
||||||
|
indexerFlags: number;
|
||||||
|
onChange(payload: object): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
|
||||||
|
const { indexerFlags, onChange } = props;
|
||||||
|
|
||||||
|
const { value, values } = useSelector(selectIndexerFlagsValues(indexerFlags));
|
||||||
|
|
||||||
|
const onChangeWrapper = useCallback(
|
||||||
|
({ name, value }: { name: string; value: number[] }) => {
|
||||||
|
const indexerFlags = value.reduce((acc, flagId) => acc + flagId, 0);
|
||||||
|
|
||||||
|
onChange({ name, value: indexerFlags });
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EnhancedSelectInput
|
||||||
|
{...props}
|
||||||
|
value={value}
|
||||||
|
values={values}
|
||||||
|
onChange={onChangeWrapper}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexerFlagsSelectInput;
|
|
@ -38,7 +38,7 @@ function createMapStateToProps() {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: includeNoChangeDisabled
|
isDisabled: includeNoChangeDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ function createMapStateToProps() {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ function MonitorAlbumsSelectInput(props) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: includeNoChangeDisabled
|
isDisabled: includeNoChangeDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ function MonitorAlbumsSelectInput(props) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ function MonitorNewItemsSelectInput(props) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: includeNoChangeDisabled
|
isDisabled: includeNoChangeDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ function MonitorNewItemsSelectInput(props) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
.input {
|
|
||||||
composes: input from '~Components/Form/TextInput.css';
|
|
||||||
|
|
||||||
font-family: $passwordFamily;
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TextInput from './TextInput';
|
import TextInput from './TextInput';
|
||||||
import styles from './PasswordInput.css';
|
|
||||||
|
|
||||||
// Prevent a user from copying (or cutting) the password from the input
|
// Prevent a user from copying (or cutting) the password from the input
|
||||||
function onCopy(e) {
|
function onCopy(e) {
|
||||||
|
@ -13,17 +11,14 @@ function PasswordInput(props) {
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
{...props}
|
{...props}
|
||||||
|
type="password"
|
||||||
onCopy={onCopy}
|
onCopy={onCopy}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordInput.propTypes = {
|
PasswordInput.propTypes = {
|
||||||
className: PropTypes.string.isRequired
|
...TextInput.props
|
||||||
};
|
|
||||||
|
|
||||||
PasswordInput.defaultProps = {
|
|
||||||
className: styles.input
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PasswordInput;
|
export default PasswordInput;
|
||||||
|
|
|
@ -29,6 +29,8 @@ function getType({ type, selectOptionsProviderAction }) {
|
||||||
return inputTypes.DYNAMIC_SELECT;
|
return inputTypes.DYNAMIC_SELECT;
|
||||||
}
|
}
|
||||||
return inputTypes.SELECT;
|
return inputTypes.SELECT;
|
||||||
|
case 'artistTag':
|
||||||
|
return inputTypes.ARTIST_TAG;
|
||||||
case 'tag':
|
case 'tag':
|
||||||
return inputTypes.TEXT_TAG;
|
return inputTypes.TEXT_TAG;
|
||||||
case 'tagSelect':
|
case 'tagSelect':
|
||||||
|
|
|
@ -26,7 +26,7 @@ function createMapStateToProps() {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: includeNoChangeDisabled
|
isDisabled: includeNoChangeDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ function createMapStateToProps() {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ function SeriesTypeSelectInput(props) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: includeNoChangeDisabled
|
isDisabled: includeNoChangeDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ function SeriesTypeSelectInput(props) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ class TextTagInputConnector extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<TagInput
|
<TagInput
|
||||||
|
delimiters={['Tab', 'Enter', ',']}
|
||||||
tagList={[]}
|
tagList={[]}
|
||||||
onTagAdd={this.onTagAdd}
|
onTagAdd={this.onTagAdd}
|
||||||
onTagDelete={this.onTagDelete}
|
onTagDelete={this.onTagDelete}
|
||||||
|
|
|
@ -63,6 +63,13 @@
|
||||||
width: 1280px;
|
width: 1280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.extraExtraLarge {
|
||||||
|
composes: modal;
|
||||||
|
|
||||||
|
width: 1600px;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointExtraLarge) {
|
@media only screen and (max-width: $breakpointExtraLarge) {
|
||||||
.modal.extraLarge {
|
.modal.extraLarge {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
@ -90,7 +97,8 @@
|
||||||
.modal.small,
|
.modal.small,
|
||||||
.modal.medium,
|
.modal.medium,
|
||||||
.modal.large,
|
.modal.large,
|
||||||
.modal.extraLarge {
|
.modal.extraLarge,
|
||||||
|
.modal.extraExtraLarge {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
|
'extraExtraLarge': string;
|
||||||
'extraLarge': string;
|
'extraLarge': string;
|
||||||
'large': string;
|
'large': string;
|
||||||
'medium': string;
|
'medium': string;
|
||||||
|
|
|
@ -6,7 +6,14 @@ import { createSelector } from 'reselect';
|
||||||
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
||||||
import { fetchArtist } from 'Store/Actions/artistActions';
|
import { fetchArtist } from 'Store/Actions/artistActions';
|
||||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||||
import { fetchImportLists, fetchLanguages, fetchMetadataProfiles, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions';
|
import {
|
||||||
|
fetchImportLists,
|
||||||
|
fetchIndexerFlags,
|
||||||
|
fetchLanguages,
|
||||||
|
fetchMetadataProfiles,
|
||||||
|
fetchQualityProfiles,
|
||||||
|
fetchUISettings
|
||||||
|
} from 'Store/Actions/settingsActions';
|
||||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
import { fetchTags } from 'Store/Actions/tagActions';
|
import { fetchTags } from 'Store/Actions/tagActions';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
@ -44,6 +51,7 @@ const selectAppProps = createSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectIsPopulated = createSelector(
|
const selectIsPopulated = createSelector(
|
||||||
|
(state) => state.artist.isPopulated,
|
||||||
(state) => state.customFilters.isPopulated,
|
(state) => state.customFilters.isPopulated,
|
||||||
(state) => state.tags.isPopulated,
|
(state) => state.tags.isPopulated,
|
||||||
(state) => state.settings.ui.isPopulated,
|
(state) => state.settings.ui.isPopulated,
|
||||||
|
@ -51,9 +59,11 @@ const selectIsPopulated = createSelector(
|
||||||
(state) => state.settings.qualityProfiles.isPopulated,
|
(state) => state.settings.qualityProfiles.isPopulated,
|
||||||
(state) => state.settings.metadataProfiles.isPopulated,
|
(state) => state.settings.metadataProfiles.isPopulated,
|
||||||
(state) => state.settings.importLists.isPopulated,
|
(state) => state.settings.importLists.isPopulated,
|
||||||
|
(state) => state.settings.indexerFlags.isPopulated,
|
||||||
(state) => state.system.status.isPopulated,
|
(state) => state.system.status.isPopulated,
|
||||||
(state) => state.app.translations.isPopulated,
|
(state) => state.app.translations.isPopulated,
|
||||||
(
|
(
|
||||||
|
artistsIsPopulated,
|
||||||
customFiltersIsPopulated,
|
customFiltersIsPopulated,
|
||||||
tagsIsPopulated,
|
tagsIsPopulated,
|
||||||
uiSettingsIsPopulated,
|
uiSettingsIsPopulated,
|
||||||
|
@ -61,10 +71,12 @@ const selectIsPopulated = createSelector(
|
||||||
qualityProfilesIsPopulated,
|
qualityProfilesIsPopulated,
|
||||||
metadataProfilesIsPopulated,
|
metadataProfilesIsPopulated,
|
||||||
importListsIsPopulated,
|
importListsIsPopulated,
|
||||||
|
indexerFlagsIsPopulated,
|
||||||
systemStatusIsPopulated,
|
systemStatusIsPopulated,
|
||||||
translationsIsPopulated
|
translationsIsPopulated
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
|
artistsIsPopulated &&
|
||||||
customFiltersIsPopulated &&
|
customFiltersIsPopulated &&
|
||||||
tagsIsPopulated &&
|
tagsIsPopulated &&
|
||||||
uiSettingsIsPopulated &&
|
uiSettingsIsPopulated &&
|
||||||
|
@ -72,6 +84,7 @@ const selectIsPopulated = createSelector(
|
||||||
qualityProfilesIsPopulated &&
|
qualityProfilesIsPopulated &&
|
||||||
metadataProfilesIsPopulated &&
|
metadataProfilesIsPopulated &&
|
||||||
importListsIsPopulated &&
|
importListsIsPopulated &&
|
||||||
|
indexerFlagsIsPopulated &&
|
||||||
systemStatusIsPopulated &&
|
systemStatusIsPopulated &&
|
||||||
translationsIsPopulated
|
translationsIsPopulated
|
||||||
);
|
);
|
||||||
|
@ -79,6 +92,7 @@ const selectIsPopulated = createSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectErrors = createSelector(
|
const selectErrors = createSelector(
|
||||||
|
(state) => state.artist.error,
|
||||||
(state) => state.customFilters.error,
|
(state) => state.customFilters.error,
|
||||||
(state) => state.tags.error,
|
(state) => state.tags.error,
|
||||||
(state) => state.settings.ui.error,
|
(state) => state.settings.ui.error,
|
||||||
|
@ -86,9 +100,11 @@ const selectErrors = createSelector(
|
||||||
(state) => state.settings.qualityProfiles.error,
|
(state) => state.settings.qualityProfiles.error,
|
||||||
(state) => state.settings.metadataProfiles.error,
|
(state) => state.settings.metadataProfiles.error,
|
||||||
(state) => state.settings.importLists.error,
|
(state) => state.settings.importLists.error,
|
||||||
|
(state) => state.settings.indexerFlags.error,
|
||||||
(state) => state.system.status.error,
|
(state) => state.system.status.error,
|
||||||
(state) => state.app.translations.error,
|
(state) => state.app.translations.error,
|
||||||
(
|
(
|
||||||
|
artistsError,
|
||||||
customFiltersError,
|
customFiltersError,
|
||||||
tagsError,
|
tagsError,
|
||||||
uiSettingsError,
|
uiSettingsError,
|
||||||
|
@ -96,10 +112,12 @@ const selectErrors = createSelector(
|
||||||
qualityProfilesError,
|
qualityProfilesError,
|
||||||
metadataProfilesError,
|
metadataProfilesError,
|
||||||
importListsError,
|
importListsError,
|
||||||
|
indexerFlagsError,
|
||||||
systemStatusError,
|
systemStatusError,
|
||||||
translationsError
|
translationsError
|
||||||
) => {
|
) => {
|
||||||
const hasError = !!(
|
const hasError = !!(
|
||||||
|
artistsError ||
|
||||||
customFiltersError ||
|
customFiltersError ||
|
||||||
tagsError ||
|
tagsError ||
|
||||||
uiSettingsError ||
|
uiSettingsError ||
|
||||||
|
@ -107,6 +125,7 @@ const selectErrors = createSelector(
|
||||||
qualityProfilesError ||
|
qualityProfilesError ||
|
||||||
metadataProfilesError ||
|
metadataProfilesError ||
|
||||||
importListsError ||
|
importListsError ||
|
||||||
|
indexerFlagsError ||
|
||||||
systemStatusError ||
|
systemStatusError ||
|
||||||
translationsError
|
translationsError
|
||||||
);
|
);
|
||||||
|
@ -120,6 +139,7 @@ const selectErrors = createSelector(
|
||||||
qualityProfilesError,
|
qualityProfilesError,
|
||||||
metadataProfilesError,
|
metadataProfilesError,
|
||||||
importListsError,
|
importListsError,
|
||||||
|
indexerFlagsError,
|
||||||
systemStatusError,
|
systemStatusError,
|
||||||
translationsError
|
translationsError
|
||||||
};
|
};
|
||||||
|
@ -177,6 +197,9 @@ function createMapDispatchToProps(dispatch, props) {
|
||||||
dispatchFetchImportLists() {
|
dispatchFetchImportLists() {
|
||||||
dispatch(fetchImportLists());
|
dispatch(fetchImportLists());
|
||||||
},
|
},
|
||||||
|
dispatchFetchIndexerFlags() {
|
||||||
|
dispatch(fetchIndexerFlags());
|
||||||
|
},
|
||||||
dispatchFetchUISettings() {
|
dispatchFetchUISettings() {
|
||||||
dispatch(fetchUISettings());
|
dispatch(fetchUISettings());
|
||||||
},
|
},
|
||||||
|
@ -217,6 +240,7 @@ class PageConnector extends Component {
|
||||||
this.props.dispatchFetchQualityProfiles();
|
this.props.dispatchFetchQualityProfiles();
|
||||||
this.props.dispatchFetchMetadataProfiles();
|
this.props.dispatchFetchMetadataProfiles();
|
||||||
this.props.dispatchFetchImportLists();
|
this.props.dispatchFetchImportLists();
|
||||||
|
this.props.dispatchFetchIndexerFlags();
|
||||||
this.props.dispatchFetchUISettings();
|
this.props.dispatchFetchUISettings();
|
||||||
this.props.dispatchFetchStatus();
|
this.props.dispatchFetchStatus();
|
||||||
this.props.dispatchFetchTranslations();
|
this.props.dispatchFetchTranslations();
|
||||||
|
@ -243,6 +267,7 @@ class PageConnector extends Component {
|
||||||
dispatchFetchQualityProfiles,
|
dispatchFetchQualityProfiles,
|
||||||
dispatchFetchMetadataProfiles,
|
dispatchFetchMetadataProfiles,
|
||||||
dispatchFetchImportLists,
|
dispatchFetchImportLists,
|
||||||
|
dispatchFetchIndexerFlags,
|
||||||
dispatchFetchUISettings,
|
dispatchFetchUISettings,
|
||||||
dispatchFetchStatus,
|
dispatchFetchStatus,
|
||||||
dispatchFetchTranslations,
|
dispatchFetchTranslations,
|
||||||
|
@ -284,6 +309,7 @@ PageConnector.propTypes = {
|
||||||
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
|
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchMetadataProfiles: PropTypes.func.isRequired,
|
dispatchFetchMetadataProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchImportLists: PropTypes.func.isRequired,
|
dispatchFetchImportLists: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchIndexerFlags: PropTypes.func.isRequired,
|
||||||
dispatchFetchUISettings: PropTypes.func.isRequired,
|
dispatchFetchUISettings: PropTypes.func.isRequired,
|
||||||
dispatchFetchStatus: PropTypes.func.isRequired,
|
dispatchFetchStatus: PropTypes.func.isRequired,
|
||||||
dispatchFetchTranslations: PropTypes.func.isRequired,
|
dispatchFetchTranslations: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -266,7 +266,7 @@ class SignalRConnector extends Component {
|
||||||
handleWantedCutoff = (body) => {
|
handleWantedCutoff = (body) => {
|
||||||
if (body.action === 'updated') {
|
if (body.action === 'updated') {
|
||||||
this.props.dispatchUpdateItem({
|
this.props.dispatchUpdateItem({
|
||||||
section: 'cutoffUnmet',
|
section: 'wanted.cutoffUnmet',
|
||||||
updateOnly: true,
|
updateOnly: true,
|
||||||
...body.resource
|
...body.resource
|
||||||
});
|
});
|
||||||
|
@ -276,7 +276,7 @@ class SignalRConnector extends Component {
|
||||||
handleWantedMissing = (body) => {
|
handleWantedMissing = (body) => {
|
||||||
if (body.action === 'updated') {
|
if (body.action === 'updated') {
|
||||||
this.props.dispatchUpdateItem({
|
this.props.dispatchUpdateItem({
|
||||||
section: 'missing',
|
section: 'wanted.missing',
|
||||||
updateOnly: true,
|
updateOnly: true,
|
||||||
...body.resource
|
...body.resource
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,14 +25,3 @@
|
||||||
font-family: 'Ubuntu Mono';
|
font-family: 'Ubuntu Mono';
|
||||||
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
|
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* text-security-disc
|
|
||||||
*/
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-family: 'text-security-disc';
|
|
||||||
src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -15,5 +15,5 @@
|
||||||
"start_url": "../../../../",
|
"start_url": "../../../../",
|
||||||
"theme_color": "#3a3f51",
|
"theme_color": "#3a3f51",
|
||||||
"background_color": "#3a3f51",
|
"background_color": "#3a3f51",
|
||||||
"display": "minimal-ui"
|
"display": "standalone"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
export default function useModalOpenState(
|
||||||
|
initialState: boolean
|
||||||
|
): [boolean, () => void, () => void] {
|
||||||
|
const [isOpen, setOpen] = useState(initialState);
|
||||||
|
|
||||||
|
const setModalOpen = useCallback(() => {
|
||||||
|
setOpen(true);
|
||||||
|
}, [setOpen]);
|
||||||
|
|
||||||
|
const setModalClosed = useCallback(() => {
|
||||||
|
setOpen(false);
|
||||||
|
}, [setOpen]);
|
||||||
|
|
||||||
|
return [isOpen, setModalOpen, setModalClosed];
|
||||||
|
}
|
|
@ -60,6 +60,7 @@ import {
|
||||||
faFileImport as fasFileImport,
|
faFileImport as fasFileImport,
|
||||||
faFileInvoice as farFileInvoice,
|
faFileInvoice as farFileInvoice,
|
||||||
faFilter as fasFilter,
|
faFilter as fasFilter,
|
||||||
|
faFlag as fasFlag,
|
||||||
faFolderOpen as fasFolderOpen,
|
faFolderOpen as fasFolderOpen,
|
||||||
faForward as fasForward,
|
faForward as fasForward,
|
||||||
faHeart as fasHeart,
|
faHeart as fasHeart,
|
||||||
|
@ -158,6 +159,7 @@ export const FILE = farFile;
|
||||||
export const FILE_IMPORT = fasFileImport;
|
export const FILE_IMPORT = fasFileImport;
|
||||||
export const FILE_MISSING = fasFileCircleQuestion;
|
export const FILE_MISSING = fasFileCircleQuestion;
|
||||||
export const FILTER = fasFilter;
|
export const FILTER = fasFilter;
|
||||||
|
export const FLAG = fasFlag;
|
||||||
export const FOLDER = farFolder;
|
export const FOLDER = farFolder;
|
||||||
export const FOLDER_OPEN = fasFolderOpen;
|
export const FOLDER_OPEN = fasFolderOpen;
|
||||||
export const GROUP = farObjectGroup;
|
export const GROUP = farObjectGroup;
|
||||||
|
|
|
@ -15,10 +15,12 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
||||||
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
|
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
|
||||||
export const ALBUM_RELEASE_SELECT = 'albumReleaseSelect';
|
export const ALBUM_RELEASE_SELECT = 'albumReleaseSelect';
|
||||||
export const INDEXER_SELECT = 'indexerSelect';
|
export const INDEXER_SELECT = 'indexerSelect';
|
||||||
|
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
||||||
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
|
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
|
||||||
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
||||||
export const SELECT = 'select';
|
export const SELECT = 'select';
|
||||||
export const SERIES_TYPE_SELECT = 'artistTypeSelect';
|
export const SERIES_TYPE_SELECT = 'artistTypeSelect';
|
||||||
|
export const ARTIST_TAG = 'artistTag';
|
||||||
export const DYNAMIC_SELECT = 'dynamicSelect';
|
export const DYNAMIC_SELECT = 'dynamicSelect';
|
||||||
export const TAG = 'tag';
|
export const TAG = 'tag';
|
||||||
export const TAG_SELECT = 'tagSelect';
|
export const TAG_SELECT = 'tagSelect';
|
||||||
|
@ -48,6 +50,7 @@ export const all = [
|
||||||
DOWNLOAD_CLIENT_SELECT,
|
DOWNLOAD_CLIENT_SELECT,
|
||||||
ROOT_FOLDER_SELECT,
|
ROOT_FOLDER_SELECT,
|
||||||
SELECT,
|
SELECT,
|
||||||
|
ARTIST_TAG,
|
||||||
DYNAMIC_SELECT,
|
DYNAMIC_SELECT,
|
||||||
SERIES_TYPE_SELECT,
|
SERIES_TYPE_SELECT,
|
||||||
TAG,
|
TAG,
|
||||||
|
|
|
@ -3,5 +3,5 @@ export const SMALL = 'small';
|
||||||
export const MEDIUM = 'medium';
|
export const MEDIUM = 'medium';
|
||||||
export const LARGE = 'large';
|
export const LARGE = 'large';
|
||||||
export const EXTRA_LARGE = 'extraLarge';
|
export const EXTRA_LARGE = 'extraLarge';
|
||||||
|
export const EXTRA_EXTRA_LARGE = 'extraExtraLarge';
|
||||||
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE];
|
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE, EXTRA_EXTRA_LARGE];
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import { inputTypes } from 'Helpers/Props';
|
|
||||||
import shortenList from 'Utilities/String/shortenList';
|
import shortenList from 'Utilities/String/shortenList';
|
||||||
import titleCase from 'Utilities/String/titleCase';
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
|
|
||||||
|
@ -56,8 +55,7 @@ class SelectAlbumReleaseRow extends Component {
|
||||||
if (name === 'release') {
|
if (name === 'release') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
<FormInputGroup
|
<SelectInput
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name={id.toString()}
|
name={id.toString()}
|
||||||
values={_.map(releases, (r) => ({
|
values={_.map(releases, (r) => ({
|
||||||
key: r.id,
|
key: r.id,
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import SelectIndexerFlagsModalContentConnector from './SelectIndexerFlagsModalContentConnector';
|
||||||
|
|
||||||
|
class SelectIndexerFlagsModal extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<SelectIndexerFlagsModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectIndexerFlagsModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectIndexerFlagsModal;
|
|
@ -0,0 +1,7 @@
|
||||||
|
.modalBody {
|
||||||
|
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'input': string;
|
'modalBody': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
|
@ -0,0 +1,106 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
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 { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './SelectIndexerFlagsModalContent.css';
|
||||||
|
|
||||||
|
class SelectIndexerFlagsModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
const {
|
||||||
|
indexerFlags
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
indexerFlags
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onIndexerFlagsChange = ({ value }) => {
|
||||||
|
this.setState({ indexerFlags: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onIndexerFlagsSelect = () => {
|
||||||
|
this.props.onIndexerFlagsSelect(this.state);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
onModalClose
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
indexerFlags
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
Manual Import - Set indexer Flags
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody
|
||||||
|
className={styles.modalBody}
|
||||||
|
scrollDirection={scrollDirections.NONE}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{translate('IndexerFlags')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.INDEXER_FLAGS_SELECT}
|
||||||
|
name="indexerFlags"
|
||||||
|
indexerFlags={indexerFlags}
|
||||||
|
autoFocus={true}
|
||||||
|
onChange={this.onIndexerFlagsChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
{translate('Cancel')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
kind={kinds.SUCCESS}
|
||||||
|
onPress={this.onIndexerFlagsSelect}
|
||||||
|
>
|
||||||
|
{translate('SetIndexerFlags')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectIndexerFlagsModalContent.propTypes = {
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
|
onIndexerFlagsSelect: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectIndexerFlagsModalContent;
|
|
@ -0,0 +1,54 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { saveInteractiveImportItem, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||||
|
import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent';
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
|
||||||
|
dispatchSaveInteractiveImportItems: saveInteractiveImportItem
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectIndexerFlagsModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onIndexerFlagsSelect = ({ indexerFlags }) => {
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
dispatchUpdateInteractiveImportItems,
|
||||||
|
dispatchSaveInteractiveImportItems
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchUpdateInteractiveImportItems({
|
||||||
|
ids,
|
||||||
|
indexerFlags
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatchSaveInteractiveImportItems({ ids });
|
||||||
|
|
||||||
|
this.props.onModalClose(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectIndexerFlagsModalContent
|
||||||
|
{...this.props}
|
||||||
|
onIndexerFlagsSelect={this.onIndexerFlagsSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectIndexerFlagsModalContentConnector.propTypes = {
|
||||||
|
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(SelectIndexerFlagsModalContentConnector);
|
|
@ -20,6 +20,7 @@ import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
||||||
import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumReleaseModal';
|
import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumReleaseModal';
|
||||||
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
||||||
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
|
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
|
||||||
|
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
|
@ -30,7 +31,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import InteractiveImportRow from './InteractiveImportRow';
|
import InteractiveImportRow from './InteractiveImportRow';
|
||||||
import styles from './InteractiveImportModalContent.css';
|
import styles from './InteractiveImportModalContent.css';
|
||||||
|
|
||||||
const columns = [
|
const COLUMNS = [
|
||||||
{
|
{
|
||||||
name: 'path',
|
name: 'path',
|
||||||
label: () => translate('Path'),
|
label: () => translate('Path'),
|
||||||
|
@ -79,11 +80,21 @@ const columns = [
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'indexerFlags',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.FLAG,
|
||||||
|
title: () => translate('IndexerFlags')
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'rejections',
|
name: 'rejections',
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
name: icons.DANGER,
|
name: icons.DANGER,
|
||||||
kind: kinds.DANGER
|
kind: kinds.DANGER,
|
||||||
|
title: () => translate('Rejections')
|
||||||
}),
|
}),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
|
@ -107,6 +118,7 @@ const ALBUM = 'album';
|
||||||
const ALBUM_RELEASE = 'albumRelease';
|
const ALBUM_RELEASE = 'albumRelease';
|
||||||
const RELEASE_GROUP = 'releaseGroup';
|
const RELEASE_GROUP = 'releaseGroup';
|
||||||
const QUALITY = 'quality';
|
const QUALITY = 'quality';
|
||||||
|
const INDEXER_FLAGS = 'indexerFlags';
|
||||||
|
|
||||||
const replaceExistingFilesOptions = {
|
const replaceExistingFilesOptions = {
|
||||||
COMBINE: 'combine',
|
COMBINE: 'combine',
|
||||||
|
@ -301,6 +313,21 @@ class InteractiveImportModalContent extends Component {
|
||||||
inconsistentAlbumReleases
|
inconsistentAlbumReleases
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const allColumns = _.cloneDeep(COLUMNS);
|
||||||
|
const columns = allColumns.map((column) => {
|
||||||
|
const showIndexerFlags = items.some((item) => item.indexerFlags);
|
||||||
|
|
||||||
|
if (!showIndexerFlags) {
|
||||||
|
const indexerFlagsColumn = allColumns.find((c) => c.name === 'indexerFlags');
|
||||||
|
|
||||||
|
if (indexerFlagsColumn) {
|
||||||
|
indexerFlagsColumn.isVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return column;
|
||||||
|
});
|
||||||
|
|
||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
|
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
|
||||||
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
|
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
|
||||||
|
@ -310,7 +337,8 @@ class InteractiveImportModalContent extends Component {
|
||||||
{ key: ALBUM, value: translate('SelectAlbum') },
|
{ key: ALBUM, value: translate('SelectAlbum') },
|
||||||
{ key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') },
|
{ key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') },
|
||||||
{ key: QUALITY, value: translate('SelectQuality') },
|
{ key: QUALITY, value: translate('SelectQuality') },
|
||||||
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
|
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') },
|
||||||
|
{ key: INDEXER_FLAGS, value: translate('SelectIndexerFlags') }
|
||||||
];
|
];
|
||||||
|
|
||||||
if (allowArtistChange) {
|
if (allowArtistChange) {
|
||||||
|
@ -433,6 +461,7 @@ class InteractiveImportModalContent extends Component {
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
{...item}
|
{...item}
|
||||||
allowArtistChange={allowArtistChange}
|
allowArtistChange={allowArtistChange}
|
||||||
|
columns={columns}
|
||||||
onSelectedChange={this.onSelectedChange}
|
onSelectedChange={this.onSelectedChange}
|
||||||
onValidRowChange={this.onValidRowChange}
|
onValidRowChange={this.onValidRowChange}
|
||||||
/>
|
/>
|
||||||
|
@ -547,6 +576,13 @@ class InteractiveImportModalContent extends Component {
|
||||||
onModalClose={this.onSelectModalClose}
|
onModalClose={this.onSelectModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectIndexerFlagsModal
|
||||||
|
isOpen={selectModalOpen === INDEXER_FLAGS}
|
||||||
|
ids={selectedIds}
|
||||||
|
indexerFlags={0}
|
||||||
|
onModalClose={this.onSelectModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmImportModal
|
<ConfirmImportModal
|
||||||
isOpen={isConfirmImportModalOpen}
|
isOpen={isConfirmImportModalOpen}
|
||||||
albums={albumsImported}
|
albums={albumsImported}
|
||||||
|
|
|
@ -135,6 +135,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||||
albumReleaseId,
|
albumReleaseId,
|
||||||
tracks,
|
tracks,
|
||||||
quality,
|
quality,
|
||||||
|
indexerFlags,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
|
@ -165,6 +166,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||||
albumReleaseId,
|
albumReleaseId,
|
||||||
trackIds: _.map(tracks, 'id'),
|
trackIds: _.map(tracks, 'id'),
|
||||||
quality,
|
quality,
|
||||||
|
indexerFlags,
|
||||||
downloadId: this.props.downloadId,
|
downloadId: this.props.downloadId,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import AlbumFormats from 'Album/AlbumFormats';
|
import AlbumFormats from 'Album/AlbumFormats';
|
||||||
|
import IndexerFlags from 'Album/IndexerFlags';
|
||||||
import TrackQuality from 'Album/TrackQuality';
|
import TrackQuality from 'Album/TrackQuality';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
@ -13,6 +14,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, kinds, sortDirections, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sortDirections, tooltipPositions } from 'Helpers/Props';
|
||||||
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
||||||
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
||||||
|
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||||
import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
|
import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
|
||||||
|
@ -35,7 +37,8 @@ class InteractiveImportRow extends Component {
|
||||||
isSelectAlbumModalOpen: false,
|
isSelectAlbumModalOpen: false,
|
||||||
isSelectTrackModalOpen: false,
|
isSelectTrackModalOpen: false,
|
||||||
isSelectReleaseGroupModalOpen: false,
|
isSelectReleaseGroupModalOpen: false,
|
||||||
isSelectQualityModalOpen: false
|
isSelectQualityModalOpen: false,
|
||||||
|
isSelectIndexerFlagsModalOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,14 +48,16 @@ class InteractiveImportRow extends Component {
|
||||||
artist,
|
artist,
|
||||||
album,
|
album,
|
||||||
tracks,
|
tracks,
|
||||||
quality
|
quality,
|
||||||
|
size
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
artist &&
|
artist &&
|
||||||
album != null &&
|
album != null &&
|
||||||
tracks.length &&
|
tracks.length &&
|
||||||
quality
|
quality &&
|
||||||
|
size > 0
|
||||||
) {
|
) {
|
||||||
this.props.onSelectedChange({ id, value: true });
|
this.props.onSelectedChange({ id, value: true });
|
||||||
}
|
}
|
||||||
|
@ -130,6 +135,10 @@ class InteractiveImportRow extends Component {
|
||||||
this.setState({ isSelectQualityModalOpen: true });
|
this.setState({ isSelectQualityModalOpen: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectIndexerFlagsPress = () => {
|
||||||
|
this.setState({ isSelectIndexerFlagsModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
onSelectArtistModalClose = (changed) => {
|
onSelectArtistModalClose = (changed) => {
|
||||||
this.setState({ isSelectArtistModalOpen: false });
|
this.setState({ isSelectArtistModalOpen: false });
|
||||||
this.selectRowAfterChange(changed);
|
this.selectRowAfterChange(changed);
|
||||||
|
@ -155,6 +164,11 @@ class InteractiveImportRow extends Component {
|
||||||
this.selectRowAfterChange(changed);
|
this.selectRowAfterChange(changed);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectIndexerFlagsModalClose = (changed) => {
|
||||||
|
this.setState({ isSelectIndexerFlagsModalOpen: false });
|
||||||
|
this.selectRowAfterChange(changed);
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -171,7 +185,9 @@ class InteractiveImportRow extends Component {
|
||||||
releaseGroup,
|
releaseGroup,
|
||||||
size,
|
size,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
indexerFlags,
|
||||||
rejections,
|
rejections,
|
||||||
|
columns,
|
||||||
isReprocessing,
|
isReprocessing,
|
||||||
audioTags,
|
audioTags,
|
||||||
additionalFile,
|
additionalFile,
|
||||||
|
@ -184,7 +200,8 @@ class InteractiveImportRow extends Component {
|
||||||
isSelectAlbumModalOpen,
|
isSelectAlbumModalOpen,
|
||||||
isSelectTrackModalOpen,
|
isSelectTrackModalOpen,
|
||||||
isSelectReleaseGroupModalOpen,
|
isSelectReleaseGroupModalOpen,
|
||||||
isSelectQualityModalOpen
|
isSelectQualityModalOpen,
|
||||||
|
isSelectIndexerFlagsModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const artistName = artist ? artist.artistName : '';
|
const artistName = artist ? artist.artistName : '';
|
||||||
|
@ -204,6 +221,7 @@ class InteractiveImportRow extends Component {
|
||||||
const showTrackNumbersLoading = isReprocessing && !tracks.length;
|
const showTrackNumbersLoading = isReprocessing && !tracks.length;
|
||||||
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
||||||
const showQualityPlaceholder = isSelected && !quality;
|
const showQualityPlaceholder = isSelected && !quality;
|
||||||
|
const showIndexerFlagsPlaceholder = isSelected && !indexerFlags;
|
||||||
|
|
||||||
const pathCellContents = (
|
const pathCellContents = (
|
||||||
<div>
|
<div>
|
||||||
|
@ -219,6 +237,8 @@ class InteractiveImportRow extends Component {
|
||||||
/>
|
/>
|
||||||
) : pathCellContents;
|
) : pathCellContents;
|
||||||
|
|
||||||
|
const isIndexerFlagsColumnVisible = columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
className={additionalFile ? styles.additionalFile : undefined}
|
className={additionalFile ? styles.additionalFile : undefined}
|
||||||
|
@ -322,6 +342,28 @@ class InteractiveImportRow extends Component {
|
||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
{isIndexerFlagsColumnVisible ? (
|
||||||
|
<TableRowCellButton
|
||||||
|
title={translate('ClickToChangeIndexerFlags')}
|
||||||
|
onPress={this.onSelectIndexerFlagsPress}
|
||||||
|
>
|
||||||
|
{showIndexerFlagsPlaceholder ? (
|
||||||
|
<InteractiveImportRowCellPlaceholder isOptional={true} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{indexerFlags ? (
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||||
|
title={translate('IndexerFlags')}
|
||||||
|
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableRowCellButton>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
{
|
{
|
||||||
rejections.length ?
|
rejections.length ?
|
||||||
|
@ -395,6 +437,13 @@ class InteractiveImportRow extends Component {
|
||||||
real={quality ? quality.revision.real > 0 : false}
|
real={quality ? quality.revision.real > 0 : false}
|
||||||
onModalClose={this.onSelectQualityModalClose}
|
onModalClose={this.onSelectQualityModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectIndexerFlagsModal
|
||||||
|
isOpen={isSelectIndexerFlagsModalOpen}
|
||||||
|
ids={[id]}
|
||||||
|
indexerFlags={indexerFlags ?? 0}
|
||||||
|
onModalClose={this.onSelectIndexerFlagsModalClose}
|
||||||
|
/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -413,7 +462,9 @@ InteractiveImportRow.propTypes = {
|
||||||
quality: PropTypes.object,
|
quality: PropTypes.object,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
audioTags: PropTypes.object.isRequired,
|
audioTags: PropTypes.object.isRequired,
|
||||||
additionalFile: PropTypes.bool.isRequired,
|
additionalFile: PropTypes.bool.isRequired,
|
||||||
isReprocessing: PropTypes.bool,
|
isReprocessing: PropTypes.bool,
|
||||||
|
|
|
@ -48,7 +48,7 @@ class InteractiveImportModal extends Component {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
size={sizes.EXTRA_LARGE}
|
size={sizes.EXTRA_EXTRA_LARGE}
|
||||||
closeOnBackgroundClick={false}
|
closeOnBackgroundClick={false}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -65,6 +65,15 @@ const columns = [
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'indexerFlags',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.FLAG,
|
||||||
|
title: () => translate('IndexerFlags')
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'rejections',
|
name: 'rejections',
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.rejected,
|
.rejected,
|
||||||
|
.indexerFlags,
|
||||||
.download {
|
.download {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ interface CssExports {
|
||||||
'customFormatScore': string;
|
'customFormatScore': string;
|
||||||
'download': string;
|
'download': string;
|
||||||
'indexer': string;
|
'indexer': string;
|
||||||
|
'indexerFlags': string;
|
||||||
'peers': string;
|
'peers': string;
|
||||||
'protocol': string;
|
'protocol': string;
|
||||||
'quality': string;
|
'quality': string;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||||
import AlbumFormats from 'Album/AlbumFormats';
|
import AlbumFormats from 'Album/AlbumFormats';
|
||||||
|
import IndexerFlags from 'Album/IndexerFlags';
|
||||||
import TrackQuality from 'Album/TrackQuality';
|
import TrackQuality from 'Album/TrackQuality';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
@ -48,12 +49,12 @@ function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
|
||||||
if (isGrabbing) {
|
if (isGrabbing) {
|
||||||
return '';
|
return '';
|
||||||
} else if (isGrabbed) {
|
} else if (isGrabbed) {
|
||||||
return 'Added to downloaded queue';
|
return translate('AddedToDownloadQueue');
|
||||||
} else if (grabError) {
|
} else if (grabError) {
|
||||||
return grabError;
|
return grabError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Add to downloaded queue';
|
return translate('AddToDownloadQueue');
|
||||||
}
|
}
|
||||||
|
|
||||||
class InteractiveSearchRow extends Component {
|
class InteractiveSearchRow extends Component {
|
||||||
|
@ -129,6 +130,7 @@ class InteractiveSearchRow extends Component {
|
||||||
quality,
|
quality,
|
||||||
customFormatScore,
|
customFormatScore,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
indexerFlags = 0,
|
||||||
rejections,
|
rejections,
|
||||||
downloadAllowed,
|
downloadAllowed,
|
||||||
isGrabbing,
|
isGrabbing,
|
||||||
|
@ -187,10 +189,21 @@ class InteractiveSearchRow extends Component {
|
||||||
formatCustomFormatScore(customFormatScore, customFormats.length)
|
formatCustomFormatScore(customFormatScore, customFormats.length)
|
||||||
}
|
}
|
||||||
tooltip={<AlbumFormats formats={customFormats} />}
|
tooltip={<AlbumFormats formats={customFormats} />}
|
||||||
position={tooltipPositions.BOTTOM}
|
position={tooltipPositions.LEFT}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.indexerFlags}>
|
||||||
|
{indexerFlags ? (
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||||
|
title={translate('IndexerFlags')}
|
||||||
|
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.rejected}>
|
<TableRowCell className={styles.rejected}>
|
||||||
{
|
{
|
||||||
!!rejections.length &&
|
!!rejections.length &&
|
||||||
|
@ -236,7 +249,9 @@ class InteractiveSearchRow extends Component {
|
||||||
isOpen={this.state.isConfirmGrabModalOpen}
|
isOpen={this.state.isConfirmGrabModalOpen}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
title={translate('GrabRelease')}
|
title={translate('GrabRelease')}
|
||||||
message={translate('GrabReleaseMessageText', [title])}
|
message={translate('GrabReleaseUnknownArtistOrAlbumMessageText', {
|
||||||
|
title
|
||||||
|
})}
|
||||||
confirmLabel={translate('Grab')}
|
confirmLabel={translate('Grab')}
|
||||||
onConfirm={this.onGrabConfirm}
|
onConfirm={this.onGrabConfirm}
|
||||||
onCancel={this.onGrabCancel}
|
onCancel={this.onGrabCancel}
|
||||||
|
@ -263,6 +278,7 @@ InteractiveSearchRow.propTypes = {
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
customFormatScore: PropTypes.number.isRequired,
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
|
rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
downloadAllowed: PropTypes.bool.isRequired,
|
downloadAllowed: PropTypes.bool.isRequired,
|
||||||
isGrabbing: PropTypes.bool.isRequired,
|
isGrabbing: PropTypes.bool.isRequired,
|
||||||
|
@ -275,6 +291,7 @@ InteractiveSearchRow.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
InteractiveSearchRow.defaultProps = {
|
InteractiveSearchRow.defaultProps = {
|
||||||
|
indexerFlags: 0,
|
||||||
rejections: [],
|
rejections: [],
|
||||||
isGrabbing: false,
|
isGrabbing: false,
|
||||||
isGrabbed: false
|
isGrabbed: false
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import Card from 'Components/Card';
|
import Card from 'Components/Card';
|
||||||
import FieldSet from 'Components/FieldSet';
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Form from 'Components/Form/Form';
|
import Form from 'Components/Form/Form';
|
||||||
|
@ -150,6 +151,11 @@ class EditCustomFormatModalContent extends Component {
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<FieldSet legend={translate('Conditions')}>
|
<FieldSet legend={translate('Conditions')}>
|
||||||
|
<Alert kind={kinds.INFO}>
|
||||||
|
<div>
|
||||||
|
{translate('CustomFormatsSettingsTriggerInfo')}
|
||||||
|
</div>
|
||||||
|
</Alert>
|
||||||
<div className={styles.customFormats}>
|
<div className={styles.customFormats}>
|
||||||
{
|
{
|
||||||
specifications.map((tag) => {
|
specifications.map((tag) => {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||||
|
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './EditDownloadClientModalContent.css';
|
import styles from './EditDownloadClientModalContent.css';
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ class EditDownloadClientModalContent extends Component {
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onSavePress,
|
onSavePress,
|
||||||
onTestPress,
|
onTestPress,
|
||||||
|
onAdvancedSettingsPress,
|
||||||
onDeleteDownloadClientPress,
|
onDeleteDownloadClientPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -206,6 +208,12 @@ class EditDownloadClientModalContent extends Component {
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<AdvancedSettingsButton
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
onAdvancedSettingsPress={onAdvancedSettingsPress}
|
||||||
|
showLabel={false}
|
||||||
|
/>
|
||||||
|
|
||||||
<SpinnerErrorButton
|
<SpinnerErrorButton
|
||||||
isSpinning={isTesting}
|
isSpinning={isTesting}
|
||||||
error={saveError}
|
error={saveError}
|
||||||
|
@ -246,6 +254,7 @@ EditDownloadClientModalContent.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
onSavePress: PropTypes.func.isRequired,
|
||||||
onTestPress: PropTypes.func.isRequired,
|
onTestPress: PropTypes.func.isRequired,
|
||||||
|
onAdvancedSettingsPress: PropTypes.func.isRequired,
|
||||||
onDeleteDownloadClientPress: PropTypes.func
|
onDeleteDownloadClientPress: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
|
import {
|
||||||
|
saveDownloadClient,
|
||||||
|
setDownloadClientFieldValue,
|
||||||
|
setDownloadClientValue,
|
||||||
|
testDownloadClient,
|
||||||
|
toggleAdvancedSettings
|
||||||
|
} from 'Store/Actions/settingsActions';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
||||||
|
|
||||||
|
@ -23,7 +29,8 @@ const mapDispatchToProps = {
|
||||||
setDownloadClientValue,
|
setDownloadClientValue,
|
||||||
setDownloadClientFieldValue,
|
setDownloadClientFieldValue,
|
||||||
saveDownloadClient,
|
saveDownloadClient,
|
||||||
testDownloadClient
|
testDownloadClient,
|
||||||
|
toggleAdvancedSettings
|
||||||
};
|
};
|
||||||
|
|
||||||
class EditDownloadClientModalContentConnector extends Component {
|
class EditDownloadClientModalContentConnector extends Component {
|
||||||
|
@ -56,6 +63,10 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||||
this.props.testDownloadClient({ id: this.props.id });
|
this.props.testDownloadClient({ id: this.props.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onAdvancedSettingsPress = () => {
|
||||||
|
this.props.toggleAdvancedSettings();
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -65,6 +76,7 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
onTestPress={this.onTestPress}
|
onTestPress={this.onTestPress}
|
||||||
|
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
|
||||||
onInputChange={this.onInputChange}
|
onInputChange={this.onInputChange}
|
||||||
onFieldChange={this.onFieldChange}
|
onFieldChange={this.onFieldChange}
|
||||||
/>
|
/>
|
||||||
|
@ -82,6 +94,7 @@ EditDownloadClientModalContentConnector.propTypes = {
|
||||||
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
||||||
saveDownloadClient: PropTypes.func.isRequired,
|
saveDownloadClient: PropTypes.func.isRequired,
|
||||||
testDownloadClient: PropTypes.func.isRequired,
|
testDownloadClient: PropTypes.func.isRequired,
|
||||||
|
toggleAdvancedSettings: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ const enableOptions = [
|
||||||
get value() {
|
get value() {
|
||||||
return translate('NoChange');
|
return translate('NoChange');
|
||||||
},
|
},
|
||||||
disabled: true,
|
isDisabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'enabled',
|
key: 'enabled',
|
||||||
|
|
|
@ -20,6 +20,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
|
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
|
||||||
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
|
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './EditImportListModalContent.css';
|
import styles from './EditImportListModalContent.css';
|
||||||
|
@ -66,6 +67,7 @@ function EditImportListModalContent(props) {
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onSavePress,
|
onSavePress,
|
||||||
onTestPress,
|
onTestPress,
|
||||||
|
onAdvancedSettingsPress,
|
||||||
onDeleteImportListPress,
|
onDeleteImportListPress,
|
||||||
showMetadataProfile,
|
showMetadataProfile,
|
||||||
...otherProps
|
...otherProps
|
||||||
|
@ -333,6 +335,12 @@ function EditImportListModalContent(props) {
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<AdvancedSettingsButton
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
onAdvancedSettingsPress={onAdvancedSettingsPress}
|
||||||
|
showLabel={false}
|
||||||
|
/>
|
||||||
|
|
||||||
<SpinnerErrorButton
|
<SpinnerErrorButton
|
||||||
isSpinning={isTesting}
|
isSpinning={isTesting}
|
||||||
error={saveError}
|
error={saveError}
|
||||||
|
@ -373,6 +381,7 @@ EditImportListModalContent.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
onSavePress: PropTypes.func.isRequired,
|
||||||
onTestPress: PropTypes.func.isRequired,
|
onTestPress: PropTypes.func.isRequired,
|
||||||
|
onAdvancedSettingsPress: PropTypes.func.isRequired,
|
||||||
onDeleteImportListPress: PropTypes.func
|
onDeleteImportListPress: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { saveImportList, setImportListFieldValue, setImportListValue, testImportList } from 'Store/Actions/settingsActions';
|
import {
|
||||||
|
saveImportList,
|
||||||
|
setImportListFieldValue,
|
||||||
|
setImportListValue,
|
||||||
|
testImportList,
|
||||||
|
toggleAdvancedSettings
|
||||||
|
} from 'Store/Actions/settingsActions';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import EditImportListModalContent from './EditImportListModalContent';
|
import EditImportListModalContent from './EditImportListModalContent';
|
||||||
|
|
||||||
|
@ -25,7 +31,8 @@ const mapDispatchToProps = {
|
||||||
setImportListValue,
|
setImportListValue,
|
||||||
setImportListFieldValue,
|
setImportListFieldValue,
|
||||||
saveImportList,
|
saveImportList,
|
||||||
testImportList
|
testImportList,
|
||||||
|
toggleAdvancedSettings
|
||||||
};
|
};
|
||||||
|
|
||||||
class EditImportListModalContentConnector extends Component {
|
class EditImportListModalContentConnector extends Component {
|
||||||
|
@ -58,6 +65,10 @@ class EditImportListModalContentConnector extends Component {
|
||||||
this.props.testImportList({ id: this.props.id });
|
this.props.testImportList({ id: this.props.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onAdvancedSettingsPress = () => {
|
||||||
|
this.props.toggleAdvancedSettings();
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -67,6 +78,7 @@ class EditImportListModalContentConnector extends Component {
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
onTestPress={this.onTestPress}
|
onTestPress={this.onTestPress}
|
||||||
|
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
|
||||||
onInputChange={this.onInputChange}
|
onInputChange={this.onInputChange}
|
||||||
onFieldChange={this.onFieldChange}
|
onFieldChange={this.onFieldChange}
|
||||||
/>
|
/>
|
||||||
|
@ -84,6 +96,7 @@ EditImportListModalContentConnector.propTypes = {
|
||||||
setImportListFieldValue: PropTypes.func.isRequired,
|
setImportListFieldValue: PropTypes.func.isRequired,
|
||||||
saveImportList: PropTypes.func.isRequired,
|
saveImportList: PropTypes.func.isRequired,
|
||||||
testImportList: PropTypes.func.isRequired,
|
testImportList: PropTypes.func.isRequired,
|
||||||
|
toggleAdvancedSettings: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ const autoAddOptions = [
|
||||||
get value() {
|
get value() {
|
||||||
return translate('NoChange');
|
return translate('NoChange');
|
||||||
},
|
},
|
||||||
disabled: true,
|
isDisabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'enabled',
|
key: 'enabled',
|
||||||
|
|
|
@ -160,7 +160,7 @@ function EditIndexerModalContent(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="priority"
|
name="priority"
|
||||||
helpText={translate('PriorityHelpText')}
|
helpText={translate('IndexerPriorityHelpText')}
|
||||||
min={1}
|
min={1}
|
||||||
max={50}
|
max={50}
|
||||||
{...priority}
|
{...priority}
|
||||||
|
|
|
@ -32,7 +32,7 @@ const enableOptions = [
|
||||||
get value() {
|
get value() {
|
||||||
return translate('NoChange');
|
return translate('NoChange');
|
||||||
},
|
},
|
||||||
disabled: true,
|
isDisabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'enabled',
|
key: 'enabled',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.option {
|
.option {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
border: 1px solid var(--borderColor);
|
border: 1px solid var(--borderColor);
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
width: 460px;
|
width: 490px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large {
|
.large {
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
.token {
|
.token {
|
||||||
flex: 0 0 50%;
|
flex: 0 0 50%;
|
||||||
padding: 6px 16px;
|
padding: 6px;
|
||||||
background-color: var(--popoverTitleBackgroundColor);
|
background-color: var(--popoverTitleBackgroundColor);
|
||||||
font-family: $monoSpaceFontFamily;
|
font-family: $monoSpaceFontFamily;
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,9 @@
|
||||||
.example {
|
.example {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: stretch;
|
justify-content: space-between;
|
||||||
flex: 0 0 50%;
|
flex: 0 0 50%;
|
||||||
padding: 6px 16px;
|
padding: 6px;
|
||||||
background-color: var(--popoverBodyBackgroundColor);
|
background-color: var(--popoverBodyBackgroundColor);
|
||||||
|
|
||||||
.footNote {
|
.footNote {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { inputTypes, kinds } from 'Helpers/Props';
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import NotificationEventItems from './NotificationEventItems';
|
import NotificationEventItems from './NotificationEventItems';
|
||||||
import styles from './EditNotificationModalContent.css';
|
import styles from './EditNotificationModalContent.css';
|
||||||
|
@ -32,6 +33,7 @@ function EditNotificationModalContent(props) {
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onSavePress,
|
onSavePress,
|
||||||
onTestPress,
|
onTestPress,
|
||||||
|
onAdvancedSettingsPress,
|
||||||
onDeleteNotificationPress,
|
onDeleteNotificationPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -140,6 +142,12 @@ function EditNotificationModalContent(props) {
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<AdvancedSettingsButton
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
onAdvancedSettingsPress={onAdvancedSettingsPress}
|
||||||
|
showLabel={false}
|
||||||
|
/>
|
||||||
|
|
||||||
<SpinnerErrorButton
|
<SpinnerErrorButton
|
||||||
isSpinning={isTesting}
|
isSpinning={isTesting}
|
||||||
error={saveError}
|
error={saveError}
|
||||||
|
@ -179,6 +187,7 @@ EditNotificationModalContent.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
onSavePress: PropTypes.func.isRequired,
|
||||||
onTestPress: PropTypes.func.isRequired,
|
onTestPress: PropTypes.func.isRequired,
|
||||||
|
onAdvancedSettingsPress: PropTypes.func.isRequired,
|
||||||
onDeleteNotificationPress: PropTypes.func
|
onDeleteNotificationPress: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { saveNotification, setNotificationFieldValue, setNotificationValue, testNotification } from 'Store/Actions/settingsActions';
|
import {
|
||||||
|
saveNotification,
|
||||||
|
setNotificationFieldValue,
|
||||||
|
setNotificationValue,
|
||||||
|
testNotification,
|
||||||
|
toggleAdvancedSettings
|
||||||
|
} from 'Store/Actions/settingsActions';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import EditNotificationModalContent from './EditNotificationModalContent';
|
import EditNotificationModalContent from './EditNotificationModalContent';
|
||||||
|
|
||||||
|
@ -23,7 +29,8 @@ const mapDispatchToProps = {
|
||||||
setNotificationValue,
|
setNotificationValue,
|
||||||
setNotificationFieldValue,
|
setNotificationFieldValue,
|
||||||
saveNotification,
|
saveNotification,
|
||||||
testNotification
|
testNotification,
|
||||||
|
toggleAdvancedSettings
|
||||||
};
|
};
|
||||||
|
|
||||||
class EditNotificationModalContentConnector extends Component {
|
class EditNotificationModalContentConnector extends Component {
|
||||||
|
@ -56,6 +63,10 @@ class EditNotificationModalContentConnector extends Component {
|
||||||
this.props.testNotification({ id: this.props.id });
|
this.props.testNotification({ id: this.props.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onAdvancedSettingsPress = () => {
|
||||||
|
this.props.toggleAdvancedSettings();
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -65,6 +76,7 @@ class EditNotificationModalContentConnector extends Component {
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
onTestPress={this.onTestPress}
|
onTestPress={this.onTestPress}
|
||||||
|
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
|
||||||
onInputChange={this.onInputChange}
|
onInputChange={this.onInputChange}
|
||||||
onFieldChange={this.onFieldChange}
|
onFieldChange={this.onFieldChange}
|
||||||
/>
|
/>
|
||||||
|
@ -82,6 +94,7 @@ EditNotificationModalContentConnector.propTypes = {
|
||||||
setNotificationFieldValue: PropTypes.func.isRequired,
|
setNotificationFieldValue: PropTypes.func.isRequired,
|
||||||
saveNotification: PropTypes.func.isRequired,
|
saveNotification: PropTypes.func.isRequired,
|
||||||
testNotification: PropTypes.func.isRequired,
|
testNotification: PropTypes.func.isRequired,
|
||||||
|
toggleAdvancedSettings: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default function TagInUse(props) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > 1 && labelPlural ) {
|
if (count > 1 && labelPlural) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{count} {labelPlural.toLowerCase()}
|
{count} {labelPlural.toLowerCase()}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
|
import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
|
||||||
import { fetchTagDetails } from 'Store/Actions/tagActions';
|
import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions';
|
||||||
import Tags from './Tags';
|
import Tags from './Tags';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -25,6 +25,7 @@ function createMapStateToProps() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
dispatchFetchTags: fetchTags,
|
||||||
dispatchFetchTagDetails: fetchTagDetails,
|
dispatchFetchTagDetails: fetchTagDetails,
|
||||||
dispatchFetchDelayProfiles: fetchDelayProfiles,
|
dispatchFetchDelayProfiles: fetchDelayProfiles,
|
||||||
dispatchFetchImportLists: fetchImportLists,
|
dispatchFetchImportLists: fetchImportLists,
|
||||||
|
@ -41,6 +42,7 @@ class MetadatasConnector extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const {
|
||||||
|
dispatchFetchTags,
|
||||||
dispatchFetchTagDetails,
|
dispatchFetchTagDetails,
|
||||||
dispatchFetchDelayProfiles,
|
dispatchFetchDelayProfiles,
|
||||||
dispatchFetchImportLists,
|
dispatchFetchImportLists,
|
||||||
|
@ -50,6 +52,7 @@ class MetadatasConnector extends Component {
|
||||||
dispatchFetchDownloadClients
|
dispatchFetchDownloadClients
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchFetchTags();
|
||||||
dispatchFetchTagDetails();
|
dispatchFetchTagDetails();
|
||||||
dispatchFetchDelayProfiles();
|
dispatchFetchDelayProfiles();
|
||||||
dispatchFetchImportLists();
|
dispatchFetchImportLists();
|
||||||
|
@ -72,6 +75,7 @@ class MetadatasConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
MetadatasConnector.propTypes = {
|
MetadatasConnector.propTypes = {
|
||||||
|
dispatchFetchTags: PropTypes.func.isRequired,
|
||||||
dispatchFetchTagDetails: PropTypes.func.isRequired,
|
dispatchFetchTagDetails: PropTypes.func.isRequired,
|
||||||
dispatchFetchDelayProfiles: PropTypes.func.isRequired,
|
dispatchFetchDelayProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchImportLists: PropTypes.func.isRequired,
|
dispatchFetchImportLists: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -22,19 +22,19 @@ export const firstDayOfWeekOptions = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const weekColumnOptions = [
|
export const weekColumnOptions = [
|
||||||
{ key: 'ddd M/D', value: 'Tue 3/25' },
|
{ key: 'ddd M/D', value: 'Tue 3/25', hint: 'ddd M/D' },
|
||||||
{ key: 'ddd MM/DD', value: 'Tue 03/25' },
|
{ key: 'ddd MM/DD', value: 'Tue 03/25', hint: 'ddd MM/DD' },
|
||||||
{ key: 'ddd D/M', value: 'Tue 25/3' },
|
{ key: 'ddd D/M', value: 'Tue 25/3', hint: 'ddd D/M' },
|
||||||
{ key: 'ddd DD/MM', value: 'Tue 25/03' }
|
{ key: 'ddd DD/MM', value: 'Tue 25/03', hint: 'ddd DD/MM' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const shortDateFormatOptions = [
|
const shortDateFormatOptions = [
|
||||||
{ key: 'MMM D YYYY', value: 'Mar 25 2014' },
|
{ key: 'MMM D YYYY', value: 'Mar 25 2014', hint: 'MMM D YYYY' },
|
||||||
{ key: 'DD MMM YYYY', value: '25 Mar 2014' },
|
{ key: 'DD MMM YYYY', value: '25 Mar 2014', hint: 'DD MMM YYYY' },
|
||||||
{ key: 'MM/D/YYYY', value: '03/25/2014' },
|
{ key: 'MM/D/YYYY', value: '03/25/2014', hint: 'MM/D/YYYY' },
|
||||||
{ key: 'MM/DD/YYYY', value: '03/25/2014' },
|
{ key: 'MM/DD/YYYY', value: '03/25/2014', hint: 'MM/DD/YYYY' },
|
||||||
{ key: 'DD/MM/YYYY', value: '25/03/2014' },
|
{ key: 'DD/MM/YYYY', value: '25/03/2014', hint: 'DD/MM/YYYY' },
|
||||||
{ key: 'YYYY-MM-DD', value: '2014-03-25' }
|
{ key: 'YYYY-MM-DD', value: '2014-03-25', hint: 'YYYY-MM-DD' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const longDateFormatOptions = [
|
const longDateFormatOptions = [
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import $ from 'jquery';
|
||||||
|
import _ from 'lodash';
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import getProviderState from 'Utilities/State/getProviderState';
|
import getProviderState from 'Utilities/State/getProviderState';
|
||||||
import { set } from '../baseActions';
|
import { set } from '../baseActions';
|
||||||
|
|
||||||
const abortCurrentRequests = {};
|
const abortCurrentRequests = {};
|
||||||
|
let lastTestData = null;
|
||||||
|
|
||||||
export function createCancelTestProviderHandler(section) {
|
export function createCancelTestProviderHandler(section) {
|
||||||
return function(getState, payload, dispatch) {
|
return function(getState, payload, dispatch) {
|
||||||
|
@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) {
|
||||||
return function(getState, payload, dispatch) {
|
return function(getState, payload, dispatch) {
|
||||||
dispatch(set({ section, isTesting: true }));
|
dispatch(set({ section, isTesting: true }));
|
||||||
|
|
||||||
const testData = getProviderState(payload, getState, section);
|
const {
|
||||||
|
queryParams = {},
|
||||||
|
...otherPayload
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const testData = getProviderState({ ...otherPayload }, getState, section);
|
||||||
|
const params = { ...queryParams };
|
||||||
|
|
||||||
|
// If the user is re-testing the same provider without changes
|
||||||
|
// force it to be tested.
|
||||||
|
|
||||||
|
if (_.isEqual(testData, lastTestData)) {
|
||||||
|
params.forceTest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTestData = testData;
|
||||||
|
|
||||||
const ajaxOptions = {
|
const ajaxOptions = {
|
||||||
url: `${url}/test`,
|
url: `${url}/test?${$.param(params, true)}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) {
|
||||||
abortCurrentRequests[section] = abortRequest;
|
abortCurrentRequests[section] = abortRequest;
|
||||||
|
|
||||||
request.done((data) => {
|
request.done((data) => {
|
||||||
|
lastTestData = null;
|
||||||
|
|
||||||
dispatch(set({
|
dispatch(set({
|
||||||
section,
|
section,
|
||||||
isTesting: false,
|
isTesting: false,
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
|
import { createThunk } from 'Store/thunks';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
const section = 'settings.indexerFlags';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_INDEXER_FLAGS = 'settings/indexerFlags/fetchIndexerFlags';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchIndexerFlags = createThunk(FETCH_INDEXER_FLAGS);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Details
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
defaultState: {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
actionHandlers: {
|
||||||
|
[FETCH_INDEXER_FLAGS]: createFetchHandler(section, '/indexerFlag')
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
reducers: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -4,9 +4,10 @@ import { createAction } from 'redux-actions';
|
||||||
import { batchActions } from 'redux-batched-actions';
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import * as calendarViews from 'Calendar/calendarViews';
|
import * as calendarViews from 'Calendar/calendarViews';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import { filterTypes } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes } from 'Helpers/Props';
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
|
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import { set, update } from './baseActions';
|
import { set, update } from './baseActions';
|
||||||
import { executeCommandHelper } from './commandActions';
|
import { executeCommandHelper } from './commandActions';
|
||||||
|
@ -54,8 +55,8 @@ export const defaultState = {
|
||||||
label: () => translate('All'),
|
label: () => translate('All'),
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'unmonitored',
|
||||||
value: false,
|
value: [true],
|
||||||
type: filterTypes.EQUAL
|
type: filterTypes.EQUAL
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -65,19 +66,35 @@ export const defaultState = {
|
||||||
label: () => translate('MonitoredOnly'),
|
label: () => translate('MonitoredOnly'),
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'unmonitored',
|
||||||
value: true,
|
value: [false],
|
||||||
type: filterTypes.EQUAL
|
type: filterTypes.EQUAL
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
filterBuilderProps: [
|
||||||
|
{
|
||||||
|
name: 'unmonitored',
|
||||||
|
label: () => translate('IncludeUnmonitored'),
|
||||||
|
type: filterBuilderTypes.EQUAL,
|
||||||
|
valueType: filterBuilderValueTypes.BOOL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tags',
|
||||||
|
label: () => translate('Tags'),
|
||||||
|
type: filterBuilderTypes.CONTAINS,
|
||||||
|
valueType: filterBuilderValueTypes.TAG
|
||||||
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const persistState = [
|
export const persistState = [
|
||||||
'calendar.view',
|
'calendar.view',
|
||||||
'calendar.selectedFilterKey',
|
'calendar.selectedFilterKey',
|
||||||
'calendar.options'
|
'calendar.options',
|
||||||
|
'calendar.customFilters'
|
||||||
];
|
];
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -189,6 +206,10 @@ function isRangePopulated(start, end, state) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCustomFilters(state, type) {
|
||||||
|
return state.customFilters.items.filter((customFilter) => customFilter.type === type);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
|
@ -210,7 +231,8 @@ export const actionHandlers = handleThunks({
|
||||||
[FETCH_CALENDAR]: function(getState, payload, dispatch) {
|
[FETCH_CALENDAR]: function(getState, payload, dispatch) {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const calendar = state.calendar;
|
const calendar = state.calendar;
|
||||||
const unmonitored = calendar.selectedFilterKey === 'all';
|
const customFilters = getCustomFilters(state, section);
|
||||||
|
const selectedFilters = findSelectedFilters(calendar.selectedFilterKey, calendar.filters, customFilters);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
time = calendar.time,
|
time = calendar.time,
|
||||||
|
@ -237,13 +259,26 @@ export const actionHandlers = handleThunks({
|
||||||
|
|
||||||
dispatch(set(attrs));
|
dispatch(set(attrs));
|
||||||
|
|
||||||
|
const requestParams = {
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
};
|
||||||
|
|
||||||
|
selectedFilters.forEach((selectedFilter) => {
|
||||||
|
if (selectedFilter.key === 'unmonitored') {
|
||||||
|
requestParams.unmonitored = selectedFilter.value.includes(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilter.key === 'tags') {
|
||||||
|
requestParams.tags = selectedFilter.value.join(',');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestParams.unmonitored = requestParams.unmonitored ?? false;
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: '/calendar',
|
url: '/calendar',
|
||||||
data: {
|
data: requestParams
|
||||||
unmonitored,
|
|
||||||
start,
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
promise.done((data) => {
|
promise.done((data) => {
|
||||||
|
|
|
@ -208,6 +208,7 @@ export const actionHandlers = handleThunks({
|
||||||
trackIds: (item.tracks || []).map((e) => e.id),
|
trackIds: (item.tracks || []).map((e) => e.id),
|
||||||
quality: item.quality,
|
quality: item.quality,
|
||||||
releaseGroup: item.releaseGroup,
|
releaseGroup: item.releaseGroup,
|
||||||
|
indexerFlags: item.indexerFlags,
|
||||||
downloadId: item.downloadId,
|
downloadId: item.downloadId,
|
||||||
additionalFile: item.additionalFile,
|
additionalFile: item.additionalFile,
|
||||||
replaceExistingFiles: item.replaceExistingFiles,
|
replaceExistingFiles: item.replaceExistingFiles,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import downloadClients from './Settings/downloadClients';
|
||||||
import general from './Settings/general';
|
import general from './Settings/general';
|
||||||
import importListExclusions from './Settings/importListExclusions';
|
import importListExclusions from './Settings/importListExclusions';
|
||||||
import importLists from './Settings/importLists';
|
import importLists from './Settings/importLists';
|
||||||
|
import indexerFlags from './Settings/indexerFlags';
|
||||||
import indexerOptions from './Settings/indexerOptions';
|
import indexerOptions from './Settings/indexerOptions';
|
||||||
import indexers from './Settings/indexers';
|
import indexers from './Settings/indexers';
|
||||||
import languages from './Settings/languages';
|
import languages from './Settings/languages';
|
||||||
|
@ -38,6 +39,7 @@ export * from './Settings/downloadClientOptions';
|
||||||
export * from './Settings/general';
|
export * from './Settings/general';
|
||||||
export * from './Settings/importLists';
|
export * from './Settings/importLists';
|
||||||
export * from './Settings/importListExclusions';
|
export * from './Settings/importListExclusions';
|
||||||
|
export * from './Settings/indexerFlags';
|
||||||
export * from './Settings/indexerOptions';
|
export * from './Settings/indexerOptions';
|
||||||
export * from './Settings/indexers';
|
export * from './Settings/indexers';
|
||||||
export * from './Settings/languages';
|
export * from './Settings/languages';
|
||||||
|
@ -73,6 +75,7 @@ export const defaultState = {
|
||||||
downloadClients: downloadClients.defaultState,
|
downloadClients: downloadClients.defaultState,
|
||||||
downloadClientOptions: downloadClientOptions.defaultState,
|
downloadClientOptions: downloadClientOptions.defaultState,
|
||||||
general: general.defaultState,
|
general: general.defaultState,
|
||||||
|
indexerFlags: indexerFlags.defaultState,
|
||||||
indexerOptions: indexerOptions.defaultState,
|
indexerOptions: indexerOptions.defaultState,
|
||||||
indexers: indexers.defaultState,
|
indexers: indexers.defaultState,
|
||||||
importLists: importLists.defaultState,
|
importLists: importLists.defaultState,
|
||||||
|
@ -119,6 +122,7 @@ export const actionHandlers = handleThunks({
|
||||||
...downloadClients.actionHandlers,
|
...downloadClients.actionHandlers,
|
||||||
...downloadClientOptions.actionHandlers,
|
...downloadClientOptions.actionHandlers,
|
||||||
...general.actionHandlers,
|
...general.actionHandlers,
|
||||||
|
...indexerFlags.actionHandlers,
|
||||||
...indexerOptions.actionHandlers,
|
...indexerOptions.actionHandlers,
|
||||||
...indexers.actionHandlers,
|
...indexers.actionHandlers,
|
||||||
...importLists.actionHandlers,
|
...importLists.actionHandlers,
|
||||||
|
@ -156,6 +160,7 @@ export const reducers = createHandleActions({
|
||||||
...downloadClients.reducers,
|
...downloadClients.reducers,
|
||||||
...downloadClientOptions.reducers,
|
...downloadClientOptions.reducers,
|
||||||
...general.reducers,
|
...general.reducers,
|
||||||
|
...indexerFlags.reducers,
|
||||||
...indexerOptions.reducers,
|
...indexerOptions.reducers,
|
||||||
...indexers.reducers,
|
...indexers.reducers,
|
||||||
...importLists.reducers,
|
...importLists.reducers,
|
||||||
|
|
|
@ -77,6 +77,15 @@ export const defaultState = {
|
||||||
}),
|
}),
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'indexerFlags',
|
||||||
|
columnLabel: () => translate('IndexerFlags'),
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.FLAG,
|
||||||
|
title: () => translate('IndexerFlags')
|
||||||
|
}),
|
||||||
|
isVisible: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
label: () => translate('Status'),
|
label: () => translate('Status'),
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
|
||||||
function createAllArtistSelector() {
|
function createAllArtistSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.artist,
|
(state: AppState) => state.artist,
|
||||||
(artist) => {
|
(artist) => {
|
||||||
return artist.items;
|
return artist.items;
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue