mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37a9f670dd | |||
| 11eda3b11b | |||
| 04682c9d91 | |||
| 50fdc449ac | |||
| b8c295727a | |||
| 93ee466780 | |||
| 77f1e8f8c9 | |||
| 1aa746bea1 | |||
| 490041d77c | |||
| 5dc5592c17 | |||
| 8fb1aff68a | |||
| a397a19034 | |||
| d0df761422 | |||
| 4781675c1a | |||
| 0361262bb4 |
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.7 KiB |
+1
-1
@@ -9,7 +9,7 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.3.20'
|
majorVersion: '0.3.21'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
|
|||||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
|||||||
|
import AuthorsAppState from './AuthorsAppState';
|
||||||
|
import CommandAppState from './CommandAppState';
|
||||||
import SettingsAppState from './SettingsAppState';
|
import SettingsAppState from './SettingsAppState';
|
||||||
import TagsAppState from './TagsAppState';
|
import TagsAppState from './TagsAppState';
|
||||||
|
|
||||||
@@ -34,6 +36,8 @@ export interface CustomFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
|
authors: AuthorsAppState;
|
||||||
|
commands: CommandAppState;
|
||||||
settings: SettingsAppState;
|
settings: SettingsAppState;
|
||||||
tags: TagsAppState;
|
tags: TagsAppState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import AppSectionState, {
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState,
|
||||||
|
} from 'App/State/AppSectionState';
|
||||||
|
import Author from 'Author/Author';
|
||||||
|
|
||||||
|
interface AuthorsAppState
|
||||||
|
extends AppSectionState<Author>,
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState {
|
||||||
|
itemMap: Record<number, number>;
|
||||||
|
|
||||||
|
deleteOptions: {
|
||||||
|
addImportListExclusion: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthorsAppState;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import AppSectionState from 'App/State/AppSectionState';
|
||||||
|
import Command from 'Commands/Command';
|
||||||
|
|
||||||
|
export type CommandAppState = AppSectionState<Command>;
|
||||||
|
|
||||||
|
export default CommandAppState;
|
||||||
@@ -5,6 +5,7 @@ import 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 Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
import { UiSettings } from 'typings/UiSettings';
|
import { UiSettings } from 'typings/UiSettings';
|
||||||
|
|
||||||
@@ -27,11 +28,13 @@ export interface NotificationAppState
|
|||||||
extends AppSectionState<Notification>,
|
extends AppSectionState<Notification>,
|
||||||
AppSectionDeleteState {}
|
AppSectionDeleteState {}
|
||||||
|
|
||||||
|
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||||
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
importLists: ImportListAppState;
|
importLists: ImportListAppState;
|
||||||
|
indexerFlags: IndexerFlagSettingsAppState;
|
||||||
indexers: IndexerAppState;
|
indexers: IndexerAppState;
|
||||||
notifications: NotificationAppState;
|
notifications: NotificationAppState;
|
||||||
uiSettings: UiSettingsAppState;
|
uiSettings: UiSettingsAppState;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import ModelBase from 'App/ModelBase';
|
||||||
|
|
||||||
|
interface Author extends ModelBase {
|
||||||
|
added: string;
|
||||||
|
genres: string[];
|
||||||
|
monitored: boolean;
|
||||||
|
overview: string;
|
||||||
|
path: string;
|
||||||
|
qualityProfileId: number;
|
||||||
|
metadataProfileId: number;
|
||||||
|
rootFolderPath: string;
|
||||||
|
sortName: string;
|
||||||
|
tags: number[];
|
||||||
|
authorName: string;
|
||||||
|
isSaving?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Author;
|
||||||
@@ -2,11 +2,11 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
|
||||||
function AuthorNameLink({ titleSlug, authorName }) {
|
function AuthorNameLink({ titleSlug, authorName, ...otherProps }) {
|
||||||
const link = `/author/${titleSlug}`;
|
const link = `/author/${titleSlug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={link}>
|
<Link to={link} {...otherProps}>
|
||||||
{authorName}
|
{authorName}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,3 +27,9 @@
|
|||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indexerFlags {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
'indexerFlags': string;
|
||||||
'monitored': string;
|
'monitored': string;
|
||||||
'pageCount': string;
|
'pageCount': string;
|
||||||
'position': string;
|
'position': string;
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
|
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
|
||||||
import BookTitleLink from 'Book/BookTitleLink';
|
import BookTitleLink from 'Book/BookTitleLink';
|
||||||
|
import IndexerFlags from 'Book/IndexerFlags';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
import StarRating from 'Components/StarRating';
|
import StarRating from 'Components/StarRating';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import BookStatus from './BookStatus';
|
import BookStatus from './BookStatus';
|
||||||
import styles from './BookRow.css';
|
import styles from './BookRow.css';
|
||||||
|
|
||||||
@@ -67,6 +72,7 @@ class BookRow extends Component {
|
|||||||
authorMonitored,
|
authorMonitored,
|
||||||
titleSlug,
|
titleSlug,
|
||||||
bookFiles,
|
bookFiles,
|
||||||
|
indexerFlags,
|
||||||
isEditorActive,
|
isEditorActive,
|
||||||
isSelected,
|
isSelected,
|
||||||
onSelectedChange,
|
onSelectedChange,
|
||||||
@@ -190,6 +196,24 @@ class BookRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 === 'status') {
|
if (name === 'status') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
@@ -235,6 +259,7 @@ BookRow.propTypes = {
|
|||||||
position: PropTypes.string,
|
position: PropTypes.string,
|
||||||
pageCount: PropTypes.number,
|
pageCount: PropTypes.number,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool,
|
isSaving: PropTypes.bool,
|
||||||
authorMonitored: PropTypes.bool.isRequired,
|
authorMonitored: PropTypes.bool.isRequired,
|
||||||
@@ -246,4 +271,8 @@ BookRow.propTypes = {
|
|||||||
onMonitorBookPress: PropTypes.func.isRequired
|
onMonitorBookPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BookRow.defaultProps = {
|
||||||
|
indexerFlags: 0
|
||||||
|
};
|
||||||
|
|
||||||
export default BookRow;
|
export default BookRow;
|
||||||
|
|||||||
@@ -7,21 +7,18 @@ import BookRow from './BookRow';
|
|||||||
const selectBookFiles = createSelector(
|
const selectBookFiles = createSelector(
|
||||||
(state) => state.bookFiles,
|
(state) => state.bookFiles,
|
||||||
(bookFiles) => {
|
(bookFiles) => {
|
||||||
const {
|
const { items } = bookFiles;
|
||||||
items
|
|
||||||
} = bookFiles;
|
|
||||||
|
|
||||||
const bookFileDict = items.reduce((acc, file) => {
|
return items.reduce((acc, file) => {
|
||||||
const bookId = file.bookId;
|
const bookId = file.bookId;
|
||||||
if (!acc.hasOwnProperty(bookId)) {
|
if (!acc.hasOwnProperty(bookId)) {
|
||||||
acc[bookId] = [];
|
acc[bookId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[bookId].push(file);
|
acc[bookId].push(file);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return bookFileDict;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -31,10 +28,14 @@ function createMapStateToProps() {
|
|||||||
selectBookFiles,
|
selectBookFiles,
|
||||||
(state, { id }) => id,
|
(state, { id }) => id,
|
||||||
(author = {}, bookFiles, bookId) => {
|
(author = {}, bookFiles, bookId) => {
|
||||||
|
const files = bookFiles[bookId] ?? [];
|
||||||
|
const bookFile = files[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authorMonitored: author.monitored,
|
authorMonitored: author.monitored,
|
||||||
authorName: author.authorName,
|
authorName: author.authorName,
|
||||||
bookFiles: bookFiles[bookId] ?? []
|
bookFiles: files,
|
||||||
|
indexerFlags: bookFile ? bookFile.indexerFlags : 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class AuthorEditorFooter extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const monitoredOptions = [
|
const monitoredOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{ key: NO_CHANGE, value: translate('NoChange'), isDisabled: true },
|
||||||
{ key: 'monitored', value: translate('Monitored') },
|
{ key: 'monitored', value: translate('Monitored') },
|
||||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -84,9 +84,15 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.authorLink {
|
||||||
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
|
margin-right: 15px;
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
.duration {
|
.duration {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
margin-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailsLabel {
|
.detailsLabel {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'alternateTitlesIconContainer': string;
|
'alternateTitlesIconContainer': string;
|
||||||
|
'authorLink': string;
|
||||||
'backdrop': string;
|
'backdrop': string;
|
||||||
'backdropOverlay': string;
|
'backdropOverlay': string;
|
||||||
'cover': string;
|
'cover': string;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
|
import AuthorNameLink from 'Author/AuthorNameLink';
|
||||||
import BookCover from 'Book/BookCover';
|
import BookCover from 'Book/BookCover';
|
||||||
import HeartRating from 'Components/HeartRating';
|
import HeartRating from 'Components/HeartRating';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
@@ -113,7 +114,7 @@ class BookDetailsHeader extends Component {
|
|||||||
className={styles.monitorToggleButton}
|
className={styles.monitorToggleButton}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
size={isSmallScreen ? 30: 40}
|
size={isSmallScreen ? 30 : 40}
|
||||||
onPress={onMonitorTogglePress}
|
onPress={onMonitorTogglePress}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +132,12 @@ class BookDetailsHeader extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{author.authorName}
|
<AuthorNameLink
|
||||||
|
className={styles.authorLink}
|
||||||
|
titleSlug={author.titleSlug}
|
||||||
|
authorName={author.authorName}
|
||||||
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!pageCount &&
|
!!pageCount &&
|
||||||
<span className={styles.duration}>
|
<span className={styles.duration}>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class BookEditorFooter extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const monitoredOptions = [
|
const monitoredOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{ key: NO_CHANGE, value: translate('NoChange'), isDisabled: true },
|
||||||
{ key: 'monitored', value: translate('Monitored') },
|
{ key: 'monitored', value: translate('Monitored') },
|
||||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -116,7 +116,7 @@ class BookFileEditorTableContent extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, [{ key: 'selectQuality', value: 'Select Quality', disabled: true }]);
|
}, [{ key: 'selectQuality', value: translate('SelectQuality'), isDisabled: true }]);
|
||||||
|
|
||||||
const hasSelectedFiles = this.getSelectedIds().length > 0;
|
const hasSelectedFiles = this.getSelectedIds().length > 0;
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class BookshelfFooter extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const monitoredOptions = [
|
const monitoredOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{ key: NO_CHANGE, value: translate('NoChange'), isDisabled: true },
|
||||||
{ key: 'monitored', value: translate('Monitored') },
|
{ key: 'monitored', value: translate('Monitored') },
|
||||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import ModelBase from 'App/ModelBase';
|
||||||
|
|
||||||
|
export interface CommandBody {
|
||||||
|
sendUpdatesToClient: boolean;
|
||||||
|
updateScheduledTask: boolean;
|
||||||
|
completionMessage: string;
|
||||||
|
requiresDiskAccess: boolean;
|
||||||
|
isExclusive: boolean;
|
||||||
|
isLongRunning: boolean;
|
||||||
|
name: string;
|
||||||
|
lastExecutionTime: string;
|
||||||
|
lastStartTime: string;
|
||||||
|
trigger: string;
|
||||||
|
suppressMessages: boolean;
|
||||||
|
authorId?: number;
|
||||||
|
authorIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Command extends ModelBase {
|
||||||
|
name: string;
|
||||||
|
commandName: string;
|
||||||
|
message: string;
|
||||||
|
body: CommandBody;
|
||||||
|
priority: string;
|
||||||
|
status: string;
|
||||||
|
result: string;
|
||||||
|
queued: string;
|
||||||
|
started: string;
|
||||||
|
ended: string;
|
||||||
|
duration: string;
|
||||||
|
trigger: string;
|
||||||
|
stateChangeTime: string;
|
||||||
|
sendUpdatesToClient: boolean;
|
||||||
|
updateScheduledTask: boolean;
|
||||||
|
lastExecutionTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Command;
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
.isDisabled {
|
.isDisabled {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownArrowContainer {
|
.dropdownArrowContainer {
|
||||||
|
|||||||
@@ -14,6 +14,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 +84,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;
|
||||||
|
|
||||||
@@ -288,6 +292,7 @@ FormInputGroup.propTypes = {
|
|||||||
includeNoChange: PropTypes.bool,
|
includeNoChange: PropTypes.bool,
|
||||||
includeNoChangeDisabled: PropTypes.bool,
|
includeNoChangeDisabled: 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;
|
||||||
@@ -39,7 +39,7 @@ function createMapStateToProps() {
|
|||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: includeNoChangeDisabled
|
isDisabled: includeNoChangeDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ function createMapStateToProps() {
|
|||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function MonitorBooksSelectInput(props) {
|
|||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ function MonitorBooksSelectInput(props) {
|
|||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function MonitorNewItemsSelectInput(props) {
|
|||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: translate('NoChange'),
|
value: translate('NoChange'),
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ function MonitorNewItemsSelectInput(props) {
|
|||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'mixed',
|
key: 'mixed',
|
||||||
value: '(Mixed)',
|
value: '(Mixed)',
|
||||||
disabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,7 @@
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
color: var(--themeRed);
|
color: var(--themeRed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rating {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'heart': string;
|
'heart': string;
|
||||||
|
'rating': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import styles from './HeartRating.css';
|
|||||||
|
|
||||||
function HeartRating({ rating, iconSize }) {
|
function HeartRating({ rating, iconSize }) {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span className={styles.rating}>
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.heart}
|
className={styles.heart}
|
||||||
name={icons.HEART}
|
name={icons.HEART}
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Ac
|
|||||||
import { fetchAuthor } from 'Store/Actions/authorActions';
|
import { fetchAuthor } from 'Store/Actions/authorActions';
|
||||||
import { fetchBooks } from 'Store/Actions/bookActions';
|
import { fetchBooks } from 'Store/Actions/bookActions';
|
||||||
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.authors.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,
|
||||||
(
|
(
|
||||||
|
authorsIsPopulated,
|
||||||
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 (
|
||||||
|
authorsIsPopulated &&
|
||||||
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.authors.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,
|
||||||
(
|
(
|
||||||
|
authorsError,
|
||||||
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 = !!(
|
||||||
|
authorsError ||
|
||||||
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());
|
||||||
},
|
},
|
||||||
@@ -218,6 +241,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();
|
||||||
@@ -245,6 +269,7 @@ class PageConnector extends Component {
|
|||||||
dispatchFetchQualityProfiles,
|
dispatchFetchQualityProfiles,
|
||||||
dispatchFetchMetadataProfiles,
|
dispatchFetchMetadataProfiles,
|
||||||
dispatchFetchImportLists,
|
dispatchFetchImportLists,
|
||||||
|
dispatchFetchIndexerFlags,
|
||||||
dispatchFetchUISettings,
|
dispatchFetchUISettings,
|
||||||
dispatchFetchStatus,
|
dispatchFetchStatus,
|
||||||
dispatchFetchTranslations,
|
dispatchFetchTranslations,
|
||||||
@@ -287,6 +312,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,
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -155,6 +156,7 @@ export const FATAL = fasTimesCircle;
|
|||||||
export const FILE = farFile;
|
export const FILE = farFile;
|
||||||
export const FILEIMPORT = fasFileImport;
|
export const FILEIMPORT = fasFileImport;
|
||||||
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,6 +15,7 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
|||||||
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
|
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
|
||||||
export const BOOK_EDITION_SELECT = 'bookEditionSelect';
|
export const BOOK_EDITION_SELECT = 'bookEditionSelect';
|
||||||
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';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'modalBody': string;
|
||||||
|
}
|
||||||
|
export const cssExports: 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;
|
||||||
+54
@@ -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 SelectAuthorModal from 'InteractiveImport/Author/SelectAuthorModal';
|
|||||||
import SelectBookModal from 'InteractiveImport/Book/SelectBookModal';
|
import SelectBookModal from 'InteractiveImport/Book/SelectBookModal';
|
||||||
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
|
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
|
||||||
import SelectEditionModal from 'InteractiveImport/Edition/SelectEditionModal';
|
import SelectEditionModal from 'InteractiveImport/Edition/SelectEditionModal';
|
||||||
|
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: 'Path',
|
label: 'Path',
|
||||||
@@ -74,11 +75,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
|
||||||
@@ -102,6 +113,7 @@ const BOOK = 'book';
|
|||||||
const EDITION = 'edition';
|
const EDITION = 'edition';
|
||||||
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',
|
||||||
@@ -288,6 +300,21 @@ class InteractiveImportModalContent extends Component {
|
|||||||
inconsistentBookReleases
|
inconsistentBookReleases
|
||||||
} = 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 importIdsByBook = _.chain(items).filter((x) => x.book).groupBy((x) => x.book.id).mapValues((x) => x.map((y) => y.id)).value();
|
const importIdsByBook = _.chain(items).filter((x) => x.book).groupBy((x) => x.book.id).mapValues((x) => x.map((y) => y.id)).value();
|
||||||
@@ -299,7 +326,8 @@ class InteractiveImportModalContent extends Component {
|
|||||||
{ key: BOOK, value: translate('SelectBook') },
|
{ key: BOOK, value: translate('SelectBook') },
|
||||||
{ key: EDITION, value: translate('SelectEdition') },
|
{ key: EDITION, value: translate('SelectEdition') },
|
||||||
{ 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 (allowAuthorChange) {
|
if (allowAuthorChange) {
|
||||||
@@ -422,6 +450,7 @@ class InteractiveImportModalContent extends Component {
|
|||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
{...item}
|
{...item}
|
||||||
allowAuthorChange={allowAuthorChange}
|
allowAuthorChange={allowAuthorChange}
|
||||||
|
columns={columns}
|
||||||
onSelectedChange={this.onSelectedChange}
|
onSelectedChange={this.onSelectedChange}
|
||||||
onValidRowChange={this.onValidRowChange}
|
onValidRowChange={this.onValidRowChange}
|
||||||
/>
|
/>
|
||||||
@@ -518,6 +547,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}
|
||||||
books={booksImported}
|
books={booksImported}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||||||
book,
|
book,
|
||||||
foreignEditionId,
|
foreignEditionId,
|
||||||
quality,
|
quality,
|
||||||
|
indexerFlags,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
@@ -158,6 +159,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||||||
bookId: book.id,
|
bookId: book.id,
|
||||||
foreignEditionId,
|
foreignEditionId,
|
||||||
quality,
|
quality,
|
||||||
|
indexerFlags,
|
||||||
downloadId: this.props.downloadId,
|
downloadId: this.props.downloadId,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import BookFormats from 'Book/BookFormats';
|
import BookFormats from 'Book/BookFormats';
|
||||||
import BookQuality from 'Book/BookQuality';
|
import BookQuality from 'Book/BookQuality';
|
||||||
|
import IndexerFlags from 'Book/IndexerFlags';
|
||||||
import FileDetails from 'BookFile/FileDetails';
|
import FileDetails from 'BookFile/FileDetails';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
@@ -14,6 +15,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
|
|||||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||||
import SelectAuthorModal from 'InteractiveImport/Author/SelectAuthorModal';
|
import SelectAuthorModal from 'InteractiveImport/Author/SelectAuthorModal';
|
||||||
import SelectBookModal from 'InteractiveImport/Book/SelectBookModal';
|
import SelectBookModal from 'InteractiveImport/Book/SelectBookModal';
|
||||||
|
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 formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
@@ -34,7 +36,8 @@ class InteractiveImportRow extends Component {
|
|||||||
isSelectAuthorModalOpen: false,
|
isSelectAuthorModalOpen: false,
|
||||||
isSelectBookModalOpen: false,
|
isSelectBookModalOpen: false,
|
||||||
isSelectReleaseGroupModalOpen: false,
|
isSelectReleaseGroupModalOpen: false,
|
||||||
isSelectQualityModalOpen: false
|
isSelectQualityModalOpen: false,
|
||||||
|
isSelectIndexerFlagsModalOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +136,10 @@ class InteractiveImportRow extends Component {
|
|||||||
this.setState({ isSelectQualityModalOpen: true });
|
this.setState({ isSelectQualityModalOpen: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectIndexerFlagsPress = () => {
|
||||||
|
this.setState({ isSelectIndexerFlagsModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
onSelectAuthorModalClose = (changed) => {
|
onSelectAuthorModalClose = (changed) => {
|
||||||
this.setState({ isSelectAuthorModalOpen: false });
|
this.setState({ isSelectAuthorModalOpen: false });
|
||||||
this.selectRowAfterChange(changed);
|
this.selectRowAfterChange(changed);
|
||||||
@@ -153,6 +160,11 @@ class InteractiveImportRow extends Component {
|
|||||||
this.selectRowAfterChange(changed);
|
this.selectRowAfterChange(changed);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectIndexerFlagsModalClose = (changed) => {
|
||||||
|
this.setState({ isSelectIndexerFlagsModalOpen: false });
|
||||||
|
this.selectRowAfterChange(changed);
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -167,7 +179,9 @@ class InteractiveImportRow extends Component {
|
|||||||
releaseGroup,
|
releaseGroup,
|
||||||
size,
|
size,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
indexerFlags,
|
||||||
rejections,
|
rejections,
|
||||||
|
columns,
|
||||||
additionalFile,
|
additionalFile,
|
||||||
isSelected,
|
isSelected,
|
||||||
isReprocessing,
|
isReprocessing,
|
||||||
@@ -180,7 +194,8 @@ class InteractiveImportRow extends Component {
|
|||||||
isSelectAuthorModalOpen,
|
isSelectAuthorModalOpen,
|
||||||
isSelectBookModalOpen,
|
isSelectBookModalOpen,
|
||||||
isSelectReleaseGroupModalOpen,
|
isSelectReleaseGroupModalOpen,
|
||||||
isSelectQualityModalOpen
|
isSelectQualityModalOpen,
|
||||||
|
isSelectIndexerFlagsModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const authorName = author ? author.authorName : '';
|
const authorName = author ? author.authorName : '';
|
||||||
@@ -193,6 +208,7 @@ class InteractiveImportRow extends Component {
|
|||||||
const showBookNumberPlaceholder = !isReprocessing && isSelected && !!author && !book;
|
const showBookNumberPlaceholder = !isReprocessing && isSelected && !!author && !book;
|
||||||
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 onClick={this.onDetailsPress}>
|
<div onClick={this.onDetailsPress}>
|
||||||
@@ -215,6 +231,8 @@ class InteractiveImportRow extends Component {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isIndexerFlagsColumnVisible = columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
className={additionalFile ? styles.additionalFile : undefined}
|
className={additionalFile ? styles.additionalFile : undefined}
|
||||||
@@ -307,6 +325,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 ?
|
||||||
@@ -378,6 +418,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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -395,7 +442,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,
|
||||||
|
|||||||
@@ -62,6 +62,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, {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rejected,
|
.rejected,
|
||||||
|
.indexerFlags,
|
||||||
.download {
|
.download {
|
||||||
composes: cell;
|
composes: cell;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,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;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||||
import BookFormats from 'Book/BookFormats';
|
import BookFormats from 'Book/BookFormats';
|
||||||
import BookQuality from 'Book/BookQuality';
|
import BookQuality from 'Book/BookQuality';
|
||||||
|
import IndexerFlags from 'Book/IndexerFlags';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
@@ -129,6 +130,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
quality,
|
quality,
|
||||||
customFormatScore,
|
customFormatScore,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
indexerFlags = 0,
|
||||||
rejections,
|
rejections,
|
||||||
downloadAllowed,
|
downloadAllowed,
|
||||||
isGrabbing,
|
isGrabbing,
|
||||||
@@ -189,10 +191,21 @@ class InteractiveSearchRow extends Component {
|
|||||||
formatCustomFormatScore(customFormatScore, customFormats.length)
|
formatCustomFormatScore(customFormatScore, customFormats.length)
|
||||||
}
|
}
|
||||||
tooltip={<BookFormats formats={customFormats} />}
|
tooltip={<BookFormats 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 &&
|
||||||
@@ -265,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,
|
||||||
@@ -277,6 +291,7 @@ InteractiveSearchRow.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
InteractiveSearchRow.defaultProps = {
|
InteractiveSearchRow.defaultProps = {
|
||||||
|
indexerFlags: 0,
|
||||||
rejections: [],
|
rejections: [],
|
||||||
isGrabbing: false,
|
isGrabbing: false,
|
||||||
isGrabbed: false
|
isGrabbed: false
|
||||||
|
|||||||
+1
-1
@@ -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
-1
@@ -32,7 +32,7 @@ const autoAddOptions = [
|
|||||||
get value() {
|
get value() {
|
||||||
return translate('NoChange');
|
return translate('NoChange');
|
||||||
},
|
},
|
||||||
disabled: true,
|
isDisabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'enabled',
|
key: 'enabled',
|
||||||
|
|||||||
+1
-1
@@ -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 {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import React from 'react';
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { batchActions } from 'redux-batched-actions';
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import bookEntities from 'Book/bookEntities';
|
import bookEntities from 'Book/bookEntities';
|
||||||
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
|
import Icon from 'Components/Icon';
|
||||||
|
import { filterTypePredicates, filterTypes, icons, sortDirections } 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 dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
|
import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
|
||||||
@@ -243,6 +245,15 @@ export const defaultState = {
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'indexerFlags',
|
||||||
|
columnLabel: () => translate('IndexerFlags'),
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.FLAG,
|
||||||
|
title: () => translate('IndexerFlags')
|
||||||
|
}),
|
||||||
|
isVisible: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ export const actionHandlers = handleThunks({
|
|||||||
foreignEditionId: item.foreignEditionId ? item.ForeignEditionId : undefined,
|
foreignEditionId: item.foreignEditionId ? item.ForeignEditionId : undefined,
|
||||||
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,
|
||||||
|
|||||||
@@ -10,6 +10,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';
|
||||||
@@ -35,6 +36,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';
|
||||||
@@ -70,6 +72,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,
|
||||||
@@ -115,6 +118,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,
|
||||||
@@ -151,6 +155,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,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
|
||||||
|
const createIndexerFlagsSelector = createSelector(
|
||||||
|
(state: AppState) => state.settings.indexerFlags,
|
||||||
|
(indexerFlags) => indexerFlags
|
||||||
|
);
|
||||||
|
|
||||||
|
export default createIndexerFlagsSelector;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
|
||||||
|
function createMultiAuthorsSelector(authorIds: number[]) {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.authors.itemMap,
|
||||||
|
(state: AppState) => state.authors.items,
|
||||||
|
(itemMap, allAuthors) => {
|
||||||
|
return authorIds.map((authorId) => allAuthors[itemMap[authorId]]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createMultiAuthorsSelector;
|
||||||
@@ -10,15 +10,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commandName {
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userAgent {
|
|
||||||
color: #b0b0b0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queued,
|
.queued,
|
||||||
.started,
|
.started,
|
||||||
.ended {
|
.ended {
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'actions': string;
|
'actions': string;
|
||||||
'commandName': string;
|
|
||||||
'duration': string;
|
'duration': string;
|
||||||
'ended': string;
|
'ended': string;
|
||||||
'queued': string;
|
'queued': string;
|
||||||
'started': string;
|
'started': string;
|
||||||
'trigger': string;
|
'trigger': string;
|
||||||
'triggerContent': string;
|
'triggerContent': string;
|
||||||
'userAgent': string;
|
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|||||||
@@ -1,279 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
||||||
import TableRow from 'Components/Table/TableRow';
|
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
|
||||||
import formatDate from 'Utilities/Date/formatDate';
|
|
||||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
|
||||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
|
||||||
import titleCase from 'Utilities/String/titleCase';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './QueuedTaskRow.css';
|
|
||||||
|
|
||||||
function getStatusIconProps(status, message) {
|
|
||||||
const title = titleCase(status);
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case 'queued':
|
|
||||||
return {
|
|
||||||
name: icons.PENDING,
|
|
||||||
title
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'started':
|
|
||||||
return {
|
|
||||||
name: icons.REFRESH,
|
|
||||||
isSpinning: true,
|
|
||||||
title
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'completed':
|
|
||||||
return {
|
|
||||||
name: icons.CHECK,
|
|
||||||
kind: kinds.SUCCESS,
|
|
||||||
title: message === 'Completed' ? title : `${title}: ${message}`
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'failed':
|
|
||||||
return {
|
|
||||||
name: icons.FATAL,
|
|
||||||
kind: kinds.DANGER,
|
|
||||||
title: `${title}: ${message}`
|
|
||||||
};
|
|
||||||
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
name: icons.UNKNOWN,
|
|
||||||
title
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFormattedDates(props) {
|
|
||||||
const {
|
|
||||||
queued,
|
|
||||||
started,
|
|
||||||
ended,
|
|
||||||
showRelativeDates,
|
|
||||||
shortDateFormat
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
if (showRelativeDates) {
|
|
||||||
return {
|
|
||||||
queuedAt: moment(queued).fromNow(),
|
|
||||||
startedAt: started ? moment(started).fromNow() : '-',
|
|
||||||
endedAt: ended ? moment(ended).fromNow() : '-'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
queuedAt: formatDate(queued, shortDateFormat),
|
|
||||||
startedAt: started ? formatDate(started, shortDateFormat) : '-',
|
|
||||||
endedAt: ended ? formatDate(ended, shortDateFormat) : '-'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class QueuedTaskRow extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
...getFormattedDates(props),
|
|
||||||
isCancelConfirmModalOpen: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this._updateTimeoutId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.setUpdateTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const {
|
|
||||||
queued,
|
|
||||||
started,
|
|
||||||
ended
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (
|
|
||||||
queued !== prevProps.queued ||
|
|
||||||
started !== prevProps.started ||
|
|
||||||
ended !== prevProps.ended
|
|
||||||
) {
|
|
||||||
this.setState(getFormattedDates(this.props));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this._updateTimeoutId) {
|
|
||||||
this._updateTimeoutId = clearTimeout(this._updateTimeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
setUpdateTimer() {
|
|
||||||
this._updateTimeoutId = setTimeout(() => {
|
|
||||||
this.setState(getFormattedDates(this.props));
|
|
||||||
this.setUpdateTimer();
|
|
||||||
}, 30000);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onCancelPress = () => {
|
|
||||||
this.setState({
|
|
||||||
isCancelConfirmModalOpen: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onAbortCancel = () => {
|
|
||||||
this.setState({
|
|
||||||
isCancelConfirmModalOpen: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
trigger,
|
|
||||||
commandName,
|
|
||||||
queued,
|
|
||||||
started,
|
|
||||||
ended,
|
|
||||||
status,
|
|
||||||
duration,
|
|
||||||
message,
|
|
||||||
clientUserAgent,
|
|
||||||
longDateFormat,
|
|
||||||
timeFormat,
|
|
||||||
onCancelPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
queuedAt,
|
|
||||||
startedAt,
|
|
||||||
endedAt,
|
|
||||||
isCancelConfirmModalOpen
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
let triggerIcon = icons.QUICK;
|
|
||||||
|
|
||||||
if (trigger === 'manual') {
|
|
||||||
triggerIcon = icons.INTERACTIVE;
|
|
||||||
} else if (trigger === 'scheduled') {
|
|
||||||
triggerIcon = icons.SCHEDULED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow>
|
|
||||||
<TableRowCell className={styles.trigger}>
|
|
||||||
<span className={styles.triggerContent}>
|
|
||||||
<Icon
|
|
||||||
name={triggerIcon}
|
|
||||||
title={titleCase(trigger)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
{...getStatusIconProps(status, message)}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
<span className={styles.commandName}>
|
|
||||||
{commandName}
|
|
||||||
</span>
|
|
||||||
{
|
|
||||||
clientUserAgent ?
|
|
||||||
<span className={styles.userAgent} title={translate('UserAgentProvidedByTheAppThatCalledTheAPI')}>
|
|
||||||
from: {clientUserAgent}
|
|
||||||
</span> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell
|
|
||||||
className={styles.queued}
|
|
||||||
title={formatDateTime(queued, longDateFormat, timeFormat)}
|
|
||||||
>
|
|
||||||
{queuedAt}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell
|
|
||||||
className={styles.started}
|
|
||||||
title={formatDateTime(started, longDateFormat, timeFormat)}
|
|
||||||
>
|
|
||||||
{startedAt}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell
|
|
||||||
className={styles.ended}
|
|
||||||
title={formatDateTime(ended, longDateFormat, timeFormat)}
|
|
||||||
>
|
|
||||||
{endedAt}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.duration}>
|
|
||||||
{formatTimeSpan(duration)}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell
|
|
||||||
className={styles.actions}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
status === 'queued' &&
|
|
||||||
<IconButton
|
|
||||||
title={translate('RemovedFromTaskQueue')}
|
|
||||||
name={icons.REMOVE}
|
|
||||||
onPress={this.onCancelPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={isCancelConfirmModalOpen}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
title={translate('Cancel')}
|
|
||||||
message={translate('CancelMessageText')}
|
|
||||||
confirmLabel={translate('YesCancel')}
|
|
||||||
cancelLabel={translate('NoLeaveIt')}
|
|
||||||
onConfirm={onCancelPress}
|
|
||||||
onCancel={this.onAbortCancel}
|
|
||||||
/>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QueuedTaskRow.propTypes = {
|
|
||||||
trigger: PropTypes.string.isRequired,
|
|
||||||
commandName: PropTypes.string.isRequired,
|
|
||||||
queued: PropTypes.string.isRequired,
|
|
||||||
started: PropTypes.string,
|
|
||||||
ended: PropTypes.string,
|
|
||||||
status: PropTypes.string.isRequired,
|
|
||||||
duration: PropTypes.string,
|
|
||||||
message: PropTypes.string,
|
|
||||||
clientUserAgent: PropTypes.string,
|
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
|
||||||
timeFormat: PropTypes.string.isRequired,
|
|
||||||
onCancelPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default QueuedTaskRow;
|
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { CommandBody } from 'Commands/Command';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||||
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import { cancelCommand } from 'Store/Actions/commandActions';
|
||||||
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
|
import formatDate from 'Utilities/Date/formatDate';
|
||||||
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||||
|
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||||
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import QueuedTaskRowNameCell from './QueuedTaskRowNameCell';
|
||||||
|
import styles from './QueuedTaskRow.css';
|
||||||
|
|
||||||
|
function getStatusIconProps(status: string, message: string | undefined) {
|
||||||
|
const title = titleCase(status);
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'queued':
|
||||||
|
return {
|
||||||
|
name: icons.PENDING,
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
|
||||||
|
case 'started':
|
||||||
|
return {
|
||||||
|
name: icons.REFRESH,
|
||||||
|
isSpinning: true,
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
|
||||||
|
case 'completed':
|
||||||
|
return {
|
||||||
|
name: icons.CHECK,
|
||||||
|
kind: kinds.SUCCESS,
|
||||||
|
title: message === 'Completed' ? title : `${title}: ${message}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
case 'failed':
|
||||||
|
return {
|
||||||
|
name: icons.FATAL,
|
||||||
|
kind: kinds.DANGER,
|
||||||
|
title: `${title}: ${message}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
name: icons.UNKNOWN,
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormattedDates(
|
||||||
|
queued: string,
|
||||||
|
started: string | undefined,
|
||||||
|
ended: string | undefined,
|
||||||
|
showRelativeDates: boolean,
|
||||||
|
shortDateFormat: string
|
||||||
|
) {
|
||||||
|
if (showRelativeDates) {
|
||||||
|
return {
|
||||||
|
queuedAt: moment(queued).fromNow(),
|
||||||
|
startedAt: started ? moment(started).fromNow() : '-',
|
||||||
|
endedAt: ended ? moment(ended).fromNow() : '-',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
queuedAt: formatDate(queued, shortDateFormat),
|
||||||
|
startedAt: started ? formatDate(started, shortDateFormat) : '-',
|
||||||
|
endedAt: ended ? formatDate(ended, shortDateFormat) : '-',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueuedTimes {
|
||||||
|
queuedAt: string;
|
||||||
|
startedAt: string;
|
||||||
|
endedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueuedTaskRowProps {
|
||||||
|
id: number;
|
||||||
|
trigger: string;
|
||||||
|
commandName: string;
|
||||||
|
queued: string;
|
||||||
|
started?: string;
|
||||||
|
ended?: string;
|
||||||
|
status: string;
|
||||||
|
duration?: string;
|
||||||
|
message?: string;
|
||||||
|
body: CommandBody;
|
||||||
|
clientUserAgent?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QueuedTaskRow(props: QueuedTaskRowProps) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
trigger,
|
||||||
|
commandName,
|
||||||
|
queued,
|
||||||
|
started,
|
||||||
|
ended,
|
||||||
|
status,
|
||||||
|
duration,
|
||||||
|
message,
|
||||||
|
body,
|
||||||
|
clientUserAgent,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { longDateFormat, shortDateFormat, showRelativeDates, timeFormat } =
|
||||||
|
useSelector(createUISettingsSelector());
|
||||||
|
|
||||||
|
const updateTimeTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [times, setTimes] = useState<QueuedTimes>(
|
||||||
|
getFormattedDates(
|
||||||
|
queued,
|
||||||
|
started,
|
||||||
|
ended,
|
||||||
|
showRelativeDates,
|
||||||
|
shortDateFormat
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [
|
||||||
|
isCancelConfirmModalOpen,
|
||||||
|
openCancelConfirmModal,
|
||||||
|
closeCancelConfirmModal,
|
||||||
|
] = useModalOpenState(false);
|
||||||
|
|
||||||
|
const handleCancelPress = useCallback(() => {
|
||||||
|
dispatch(cancelCommand({ id }));
|
||||||
|
}, [id, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateTimeTimeoutId.current = setTimeout(() => {
|
||||||
|
setTimes(
|
||||||
|
getFormattedDates(
|
||||||
|
queued,
|
||||||
|
started,
|
||||||
|
ended,
|
||||||
|
showRelativeDates,
|
||||||
|
shortDateFormat
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (updateTimeTimeoutId.current) {
|
||||||
|
clearTimeout(updateTimeTimeoutId.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [queued, started, ended, showRelativeDates, shortDateFormat, setTimes]);
|
||||||
|
|
||||||
|
const { queuedAt, startedAt, endedAt } = times;
|
||||||
|
|
||||||
|
let triggerIcon = icons.QUICK;
|
||||||
|
|
||||||
|
if (trigger === 'manual') {
|
||||||
|
triggerIcon = icons.INTERACTIVE;
|
||||||
|
} else if (trigger === 'scheduled') {
|
||||||
|
triggerIcon = icons.SCHEDULED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableRowCell className={styles.trigger}>
|
||||||
|
<span className={styles.triggerContent}>
|
||||||
|
<Icon name={triggerIcon} title={titleCase(trigger)} />
|
||||||
|
|
||||||
|
<Icon {...getStatusIconProps(status, message)} />
|
||||||
|
</span>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<QueuedTaskRowNameCell
|
||||||
|
commandName={commandName}
|
||||||
|
body={body}
|
||||||
|
clientUserAgent={clientUserAgent}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TableRowCell
|
||||||
|
className={styles.queued}
|
||||||
|
title={formatDateTime(queued, longDateFormat, timeFormat)}
|
||||||
|
>
|
||||||
|
{queuedAt}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell
|
||||||
|
className={styles.started}
|
||||||
|
title={formatDateTime(started, longDateFormat, timeFormat)}
|
||||||
|
>
|
||||||
|
{startedAt}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell
|
||||||
|
className={styles.ended}
|
||||||
|
title={formatDateTime(ended, longDateFormat, timeFormat)}
|
||||||
|
>
|
||||||
|
{endedAt}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.duration}>
|
||||||
|
{formatTimeSpan(duration)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.actions}>
|
||||||
|
{status === 'queued' && (
|
||||||
|
<IconButton
|
||||||
|
title={translate('RemovedFromTaskQueue')}
|
||||||
|
name={icons.REMOVE}
|
||||||
|
onPress={openCancelConfirmModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isCancelConfirmModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('Cancel')}
|
||||||
|
message={translate('CancelPendingTask')}
|
||||||
|
confirmLabel={translate('YesCancel')}
|
||||||
|
cancelLabel={translate('NoLeaveIt')}
|
||||||
|
onConfirm={handleCancelPress}
|
||||||
|
onCancel={closeCancelConfirmModal}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { cancelCommand } from 'Store/Actions/commandActions';
|
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
|
||||||
import QueuedTaskRow from './QueuedTaskRow';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
createUISettingsSelector(),
|
|
||||||
(uiSettings) => {
|
|
||||||
return {
|
|
||||||
showRelativeDates: uiSettings.showRelativeDates,
|
|
||||||
shortDateFormat: uiSettings.shortDateFormat,
|
|
||||||
longDateFormat: uiSettings.longDateFormat,
|
|
||||||
timeFormat: uiSettings.timeFormat
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
return {
|
|
||||||
onCancelPress() {
|
|
||||||
dispatch(cancelCommand({
|
|
||||||
id: props.id
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(QueuedTaskRow);
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.commandName {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userAgent {
|
||||||
|
color: #b0b0b0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'commandName': string;
|
||||||
|
'userAgent': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { CommandBody } from 'Commands/Command';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import createMultiAuthorsSelector from 'Store/Selectors/createMultiAuthorsSelector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './QueuedTaskRowNameCell.css';
|
||||||
|
|
||||||
|
export interface QueuedTaskRowNameCellProps {
|
||||||
|
commandName: string;
|
||||||
|
body: CommandBody;
|
||||||
|
clientUserAgent?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QueuedTaskRowNameCell(
|
||||||
|
props: QueuedTaskRowNameCellProps
|
||||||
|
) {
|
||||||
|
const { commandName, body, clientUserAgent } = props;
|
||||||
|
const movieIds = [...(body.authorIds ?? [])];
|
||||||
|
|
||||||
|
if (body.authorId) {
|
||||||
|
movieIds.push(body.authorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const authors = useSelector(createMultiAuthorsSelector(movieIds));
|
||||||
|
const sortedAuthors = authors.sort((a, b) =>
|
||||||
|
a.sortName.localeCompare(b.sortName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRowCell>
|
||||||
|
<span className={styles.commandName}>
|
||||||
|
{commandName}
|
||||||
|
{sortedAuthors.length ? (
|
||||||
|
<span> - {sortedAuthors.map((a) => a.authorName).join(', ')}</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{clientUserAgent ? (
|
||||||
|
<span
|
||||||
|
className={styles.userAgent}
|
||||||
|
title={translate('TaskUserAgentTooltip')}
|
||||||
|
>
|
||||||
|
{translate('From')}: {clientUserAgent}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import FieldSet from 'Components/FieldSet';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import QueuedTaskRowConnector from './QueuedTaskRowConnector';
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
name: 'trigger',
|
|
||||||
label: '',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'commandName',
|
|
||||||
label: () => translate('Name'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'queued',
|
|
||||||
label: () => translate('Queued'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'started',
|
|
||||||
label: () => translate('Started'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ended',
|
|
||||||
label: () => translate('Ended'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'duration',
|
|
||||||
label: () => translate('Duration'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'actions',
|
|
||||||
isVisible: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
function QueuedTasks(props) {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
items
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FieldSet legend={translate('Queue')}>
|
|
||||||
{
|
|
||||||
isFetching && !isPopulated &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated &&
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<QueuedTaskRowConnector
|
|
||||||
key={item.id}
|
|
||||||
{...item}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
}
|
|
||||||
</FieldSet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
QueuedTasks.propTypes = {
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
items: PropTypes.array.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default QueuedTasks;
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import { fetchCommands } from 'Store/Actions/commandActions';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import QueuedTaskRow from './QueuedTaskRow';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'trigger',
|
||||||
|
label: '',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'commandName',
|
||||||
|
label: () => translate('Name'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'queued',
|
||||||
|
label: () => translate('Queued'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'started',
|
||||||
|
label: () => translate('Started'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ended',
|
||||||
|
label: () => translate('Ended'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'duration',
|
||||||
|
label: () => translate('Duration'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function QueuedTasks() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { isFetching, isPopulated, items } = useSelector(
|
||||||
|
(state: AppState) => state.commands
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchCommands());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldSet legend={translate('Queue')}>
|
||||||
|
{isFetching && !isPopulated && <LoadingIndicator />}
|
||||||
|
|
||||||
|
{isPopulated && (
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{items.map((item) => {
|
||||||
|
return <QueuedTaskRow key={item.id} {...item} />;
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</FieldSet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { fetchCommands } from 'Store/Actions/commandActions';
|
|
||||||
import QueuedTasks from './QueuedTasks';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.commands,
|
|
||||||
(commands) => {
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchFetchCommands: fetchCommands
|
|
||||||
};
|
|
||||||
|
|
||||||
class QueuedTasksConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatchFetchCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<QueuedTasks
|
|
||||||
{...this.props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QueuedTasksConnector.propTypes = {
|
|
||||||
dispatchFetchCommands: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(QueuedTasksConnector);
|
|
||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import QueuedTasksConnector from './Queued/QueuedTasksConnector';
|
import QueuedTasks from './Queued/QueuedTasks';
|
||||||
import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector';
|
import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector';
|
||||||
|
|
||||||
function Tasks() {
|
function Tasks() {
|
||||||
@@ -10,7 +10,7 @@ function Tasks() {
|
|||||||
<PageContent title={translate('Tasks')}>
|
<PageContent title={translate('Tasks')}>
|
||||||
<PageContentBody>
|
<PageContentBody>
|
||||||
<ScheduledTasksConnector />
|
<ScheduledTasksConnector />
|
||||||
<QueuedTasksConnector />
|
<QueuedTasks />
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
interface IndexerFlag {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexerFlag;
|
||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
|
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
|
||||||
"lint-fix": "yarn lint --fix",
|
"lint-fix": "yarn lint --fix",
|
||||||
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
||||||
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
|
"stylelint-windows": "stylelint \"frontend/**/*.css\" --config frontend/.stylelintrc"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
||||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||||
<PackageVersion Include="Polly" Version="8.2.0" />
|
<PackageVersion Include="Polly" Version="8.3.1" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||||
<PackageVersion Include="Sentry" Version="3.31.0" />
|
<PackageVersion Include="Sentry" Version="3.31.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.2" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
||||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using NUnit.Framework;
|
|||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.BookImport;
|
using NzbDrone.Core.MediaFiles.BookImport;
|
||||||
using NzbDrone.Core.MediaFiles.BookImport.Aggregation;
|
using NzbDrone.Core.MediaFiles.BookImport.Aggregation;
|
||||||
@@ -134,6 +135,10 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
|
|||||||
.Setup(s => s.ReadTags(It.IsAny<IFileInfo>()))
|
.Setup(s => s.ReadTags(It.IsAny<IFileInfo>()))
|
||||||
.Returns(new ParsedTrackInfo());
|
.Returns(new ParsedTrackInfo());
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(x => x.FindByDownloadId(It.IsAny<string>()))
|
||||||
|
.Returns(new List<EntityHistory>());
|
||||||
|
|
||||||
GivenSpecifications(_bookpass1);
|
GivenSpecifications(_bookpass1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework;
|
|||||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[Ignore("Waiting for metadata to be back again", Until = "2024-03-15 00:00:00Z")]
|
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")]
|
||||||
public class BookInfoProxyFixture : CoreTest<BookInfoProxy>
|
public class BookInfoProxyFixture : CoreTest<BookInfoProxy>
|
||||||
{
|
{
|
||||||
private MetadataProfile _metadataProfile;
|
private MetadataProfile _metadataProfile;
|
||||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("1128601", "Guards! Guards!")]
|
[TestCase("1128601", "Guards! Guards!")]
|
||||||
[TestCase("3293141", "Ιλιάς")]
|
[TestCase("3293141", "Ἰλιάς")]
|
||||||
public void should_be_able_to_get_book_detail(string mbId, string name)
|
public void should_be_able_to_get_book_detail(string mbId, string name)
|
||||||
{
|
{
|
||||||
var details = Subject.GetBookInfo(mbId);
|
var details = Subject.GetBookInfo(mbId);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ using NzbDrone.Test.Common;
|
|||||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[Ignore("Waiting for metadata to be back again", Until = "2024-03-15 00:00:00Z")]
|
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")]
|
||||||
public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy>
|
public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy>
|
||||||
{
|
{
|
||||||
[SetUp]
|
[SetUp]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Blocklisting
|
namespace NzbDrone.Core.Blocklisting
|
||||||
@@ -19,6 +20,7 @@ namespace NzbDrone.Core.Blocklisting
|
|||||||
public long? Size { get; set; }
|
public long? Size { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
public string Indexer { get; set; }
|
public string Indexer { get; set; }
|
||||||
|
public IndexerFlags IndexerFlags { get; set; }
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public string TorrentInfoHash { get; set; }
|
public string TorrentInfoHash { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,11 @@ namespace NzbDrone.Core.Blocklisting
|
|||||||
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash")
|
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
|
||||||
|
{
|
||||||
|
blocklist.IndexerFlags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
_blocklistRepository.Insert(blocklist);
|
_blocklistRepository.Insert(blocklist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,5 +20,7 @@ namespace NzbDrone.Core.Books.Commands
|
|||||||
public override bool SendUpdatesToClient => true;
|
public override bool SendUpdatesToClient => true;
|
||||||
|
|
||||||
public override bool UpdateScheduledTask => !AuthorId.HasValue;
|
public override bool UpdateScheduledTask => !AuthorId.HasValue;
|
||||||
|
|
||||||
|
public override string CompletionMessage => "Completed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,7 @@ namespace NzbDrone.Core.Books.Commands
|
|||||||
public override bool SendUpdatesToClient => true;
|
public override bool SendUpdatesToClient => true;
|
||||||
|
|
||||||
public override bool UpdateScheduledTask => !BookId.HasValue;
|
public override bool UpdateScheduledTask => !BookId.HasValue;
|
||||||
|
|
||||||
|
public override string CompletionMessage => "Completed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -38,7 +39,8 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
{
|
{
|
||||||
BookInfo = remoteBook.ParsedBookInfo,
|
BookInfo = remoteBook.ParsedBookInfo,
|
||||||
Author = remoteBook.Author,
|
Author = remoteBook.Author,
|
||||||
Size = size
|
Size = size,
|
||||||
|
IndexerFlags = remoteBook.Release?.IndexerFlags ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
@@ -70,7 +72,8 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
{
|
{
|
||||||
BookInfo = bookInfo,
|
BookInfo = bookInfo,
|
||||||
Author = author,
|
Author = author,
|
||||||
Size = blocklist.Size ?? 0
|
Size = blocklist.Size ?? 0,
|
||||||
|
IndexerFlags = blocklist.IndexerFlags
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
@@ -81,6 +84,7 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
var parsed = Parser.Parser.ParseBookTitle(history.SourceTitle);
|
var parsed = Parser.Parser.ParseBookTitle(history.SourceTitle);
|
||||||
|
|
||||||
long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
|
long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
|
||||||
|
Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags indexerFlags);
|
||||||
|
|
||||||
var bookInfo = new ParsedBookInfo
|
var bookInfo = new ParsedBookInfo
|
||||||
{
|
{
|
||||||
@@ -94,7 +98,8 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
{
|
{
|
||||||
BookInfo = bookInfo,
|
BookInfo = bookInfo,
|
||||||
Author = author,
|
Author = author,
|
||||||
Size = size
|
Size = size,
|
||||||
|
IndexerFlags = indexerFlags
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
@@ -114,7 +119,8 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
{
|
{
|
||||||
BookInfo = bookInfo,
|
BookInfo = bookInfo,
|
||||||
Author = localBook.Author,
|
Author = localBook.Author,
|
||||||
Size = localBook.Size
|
Size = localBook.Size,
|
||||||
|
IndexerFlags = localBook.IndexerFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
@@ -181,6 +187,7 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
BookInfo = bookInfo,
|
BookInfo = bookInfo,
|
||||||
Author = author,
|
Author = author,
|
||||||
Size = bookFile.Size,
|
Size = bookFile.Size,
|
||||||
|
IndexerFlags = bookFile.IndexerFlags,
|
||||||
Filename = Path.GetFileName(bookFile.Path)
|
Filename = Path.GetFileName(bookFile.Path)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
public ParsedBookInfo BookInfo { get; set; }
|
public ParsedBookInfo BookInfo { get; set; }
|
||||||
public Author Author { get; set; }
|
public Author Author { get; set; }
|
||||||
public long Size { get; set; }
|
public long Size { get; set; }
|
||||||
|
public IndexerFlags IndexerFlags { get; set; }
|
||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
|
|
||||||
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series)
|
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.CustomFormats
|
||||||
|
{
|
||||||
|
public class IndexerFlagSpecificationValidator : AbstractValidator<IndexerFlagSpecification>
|
||||||
|
{
|
||||||
|
public IndexerFlagSpecificationValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Value).NotEmpty();
|
||||||
|
RuleFor(c => c.Value).Custom((flag, context) =>
|
||||||
|
{
|
||||||
|
if (!Enum.IsDefined(typeof(IndexerFlags), flag))
|
||||||
|
{
|
||||||
|
context.AddFailure($"Invalid indexer flag condition value: {flag}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IndexerFlagSpecification : CustomFormatSpecificationBase
|
||||||
|
{
|
||||||
|
private static readonly IndexerFlagSpecificationValidator Validator = new ();
|
||||||
|
|
||||||
|
public override int Order => 4;
|
||||||
|
public override string ImplementationName => "Indexer Flag";
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "CustomFormatsSpecificationFlag", Type = FieldType.Select, SelectOptions = typeof(IndexerFlags))]
|
||||||
|
public int Value { get; set; }
|
||||||
|
|
||||||
|
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||||
|
{
|
||||||
|
return input.IndexerFlags.HasFlag((IndexerFlags)Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(040)]
|
||||||
|
public class add_indexer_flags : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Blocklist").AddColumn("IndexerFlags").AsInt32().WithDefaultValue(0);
|
||||||
|
Alter.Table("BookFiles").AddColumn("IndexerFlags").AsInt32().WithDefaultValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ using NzbDrone.Core.Download.History;
|
|||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.TrackedDownloads
|
namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
{
|
{
|
||||||
@@ -156,11 +157,10 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
|||||||
var firstHistoryItem = historyItems.First();
|
var firstHistoryItem = historyItems.First();
|
||||||
var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == EntityHistoryEventType.Grabbed);
|
var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == EntityHistoryEventType.Grabbed);
|
||||||
|
|
||||||
trackedDownload.Indexer = grabbedEvent?.Data["indexer"];
|
trackedDownload.Indexer = grabbedEvent?.Data?.GetValueOrDefault("indexer");
|
||||||
|
|
||||||
if (parsedBookInfo == null ||
|
if (parsedBookInfo == null ||
|
||||||
trackedDownload.RemoteBook == null ||
|
trackedDownload.RemoteBook?.Author == null ||
|
||||||
trackedDownload.RemoteBook.Author == null ||
|
|
||||||
trackedDownload.RemoteBook.Books.Empty())
|
trackedDownload.RemoteBook.Books.Empty())
|
||||||
{
|
{
|
||||||
// Try parsing the original source title and if that fails, try parsing it as a special
|
// Try parsing the original source title and if that fails, try parsing it as a special
|
||||||
@@ -192,6 +192,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackedDownload.RemoteBook != null &&
|
||||||
|
Enum.TryParse(grabbedEvent?.Data?.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
|
||||||
|
{
|
||||||
|
trackedDownload.RemoteBook.Release ??= new ReleaseInfo();
|
||||||
|
trackedDownload.RemoteBook.Release.IndexerFlags = flags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate custom formats
|
// Calculate custom formats
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ namespace NzbDrone.Core.History
|
|||||||
history.Data.Add("DownloadForced", (!message.Book.DownloadAllowed).ToString());
|
history.Data.Add("DownloadForced", (!message.Book.DownloadAllowed).ToString());
|
||||||
history.Data.Add("CustomFormatScore", message.Book.CustomFormatScore.ToString());
|
history.Data.Add("CustomFormatScore", message.Book.CustomFormatScore.ToString());
|
||||||
history.Data.Add("ReleaseSource", message.Book.ReleaseSource.ToString());
|
history.Data.Add("ReleaseSource", message.Book.ReleaseSource.ToString());
|
||||||
|
history.Data.Add("IndexerFlags", message.Book.Release.IndexerFlags.ToString());
|
||||||
|
|
||||||
if (!message.Book.ParsedBookInfo.ReleaseHash.IsNullOrWhiteSpace())
|
if (!message.Book.ParsedBookInfo.ReleaseHash.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
@@ -201,6 +202,8 @@ namespace NzbDrone.Core.History
|
|||||||
|
|
||||||
history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson());
|
history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson());
|
||||||
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup);
|
||||||
|
history.Data.Add("IndexerFlags", message.TrackedDownload?.RemoteBook?.Release?.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,6 +240,7 @@ namespace NzbDrone.Core.History
|
|||||||
history.Data.Add("DownloadClientName", message.DownloadClientInfo?.Name);
|
history.Data.Add("DownloadClientName", message.DownloadClientInfo?.Name);
|
||||||
history.Data.Add("ReleaseGroup", message.BookInfo.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.BookInfo.ReleaseGroup);
|
||||||
history.Data.Add("Size", message.BookInfo.Size.ToString());
|
history.Data.Add("Size", message.BookInfo.Size.ToString());
|
||||||
|
history.Data.Add("IndexerFlags", message.BookInfo.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
@@ -290,6 +294,7 @@ namespace NzbDrone.Core.History
|
|||||||
|
|
||||||
history.Data.Add("Reason", message.Reason.ToString());
|
history.Data.Add("Reason", message.Reason.ToString());
|
||||||
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
|
||||||
|
history.Data.Add("IndexerFlags", message.BookFile.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
@@ -313,6 +318,7 @@ namespace NzbDrone.Core.History
|
|||||||
history.Data.Add("Path", path);
|
history.Data.Add("Path", path);
|
||||||
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
|
||||||
history.Data.Add("Size", message.BookFile.Size.ToString());
|
history.Data.Add("Size", message.BookFile.Size.ToString());
|
||||||
|
history.Data.Add("IndexerFlags", message.BookFile.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.IndexerSearch
|
|||||||
|
|
||||||
var reports = batch.SelectMany(x => x).ToList();
|
var reports = batch.SelectMany(x => x).ToList();
|
||||||
|
|
||||||
_logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
|
_logger.ProgressDebug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
|
||||||
|
|
||||||
// Update the last search time for all albums if at least 1 indexer was searched.
|
// Update the last search time for all albums if at least 1 indexer was searched.
|
||||||
if (indexers.Any())
|
if (indexers.Any())
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||||||
{
|
{
|
||||||
var id = result.Id;
|
var id = result.Id;
|
||||||
|
|
||||||
//if (result.FreeLeech)
|
torrentInfos.Add(new TorrentInfo
|
||||||
torrentInfos.Add(new TorrentInfo()
|
|
||||||
{
|
{
|
||||||
Guid = $"FileList-{id}",
|
Guid = $"FileList-{id}",
|
||||||
Title = result.Name,
|
Title = result.Name,
|
||||||
@@ -48,13 +47,31 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||||||
InfoUrl = GetInfoUrl(id),
|
InfoUrl = GetInfoUrl(id),
|
||||||
Seeders = result.Seeders,
|
Seeders = result.Seeders,
|
||||||
Peers = result.Leechers + result.Seeders,
|
Peers = result.Leechers + result.Seeders,
|
||||||
PublishDate = result.UploadDate.ToUniversalTime()
|
PublishDate = result.UploadDate.ToUniversalTime(),
|
||||||
|
IndexerFlags = GetIndexerFlags(result)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return torrentInfos.ToArray();
|
return torrentInfos.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IndexerFlags GetIndexerFlags(FileListTorrent item)
|
||||||
|
{
|
||||||
|
IndexerFlags flags = 0;
|
||||||
|
|
||||||
|
if (item.FreeLeech)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Internal)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetDownloadUrl(string torrentId)
|
private string GetDownloadUrl(string torrentId)
|
||||||
{
|
{
|
||||||
var url = new HttpUri(_settings.BaseUrl)
|
var url = new HttpUri(_settings.BaseUrl)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||||||
public uint Files { get; set; }
|
public uint Files { get; set; }
|
||||||
[JsonProperty(PropertyName = "imdb")]
|
[JsonProperty(PropertyName = "imdb")]
|
||||||
public string ImdbId { get; set; }
|
public string ImdbId { get; set; }
|
||||||
|
public bool Internal { get; set; }
|
||||||
[JsonProperty(PropertyName = "freeleech")]
|
[JsonProperty(PropertyName = "freeleech")]
|
||||||
public bool FreeLeech { get; set; }
|
public bool FreeLeech { get; set; }
|
||||||
[JsonProperty(PropertyName = "upload_date")]
|
[JsonProperty(PropertyName = "upload_date")]
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
|||||||
public string Leechers { get; set; }
|
public string Leechers { get; set; }
|
||||||
public bool IsFreeLeech { get; set; }
|
public bool IsFreeLeech { get; set; }
|
||||||
public bool IsNeutralLeech { get; set; }
|
public bool IsNeutralLeech { get; set; }
|
||||||
|
public bool IsFreeload { get; set; }
|
||||||
public bool IsPersonalFreeLeech { get; set; }
|
public bool IsPersonalFreeLeech { get; set; }
|
||||||
public bool CanUseToken { get; set; }
|
public bool CanUseToken { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
|||||||
var author = WebUtility.HtmlDecode(result.Author);
|
var author = WebUtility.HtmlDecode(result.Author);
|
||||||
var book = WebUtility.HtmlDecode(result.GroupName);
|
var book = WebUtility.HtmlDecode(result.GroupName);
|
||||||
|
|
||||||
torrentInfos.Add(new GazelleInfo()
|
torrentInfos.Add(new GazelleInfo
|
||||||
{
|
{
|
||||||
Guid = string.Format("Gazelle-{0}", id),
|
Guid = string.Format("Gazelle-{0}", id),
|
||||||
Author = author,
|
Author = author,
|
||||||
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
|||||||
Seeders = int.Parse(torrent.Seeders),
|
Seeders = int.Parse(torrent.Seeders),
|
||||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||||
PublishDate = torrent.Time.ToUniversalTime(),
|
PublishDate = torrent.Time.ToUniversalTime(),
|
||||||
Scene = torrent.Scene,
|
IndexerFlags = GetIndexerFlags(torrent)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +88,23 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IndexerFlags GetIndexerFlags(GazelleTorrent torrent)
|
||||||
|
{
|
||||||
|
IndexerFlags flags = 0;
|
||||||
|
|
||||||
|
if (torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsFreeload || torrent.IsPersonalFreeLeech)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent.Scene)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetDownloadUrl(int torrentId)
|
private string GetDownloadUrl(int torrentId)
|
||||||
{
|
{
|
||||||
var url = new HttpUri(_settings.BaseUrl)
|
var url = new HttpUri(_settings.BaseUrl)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace NzbDrone.Core.Indexers
|
|||||||
public class RssSyncCommand : Command
|
public class RssSyncCommand : Command
|
||||||
{
|
{
|
||||||
public override bool SendUpdatesToClient => true;
|
public override bool SendUpdatesToClient => true;
|
||||||
|
|
||||||
public override bool IsLongRunning => true;
|
public override bool IsLongRunning => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,18 @@ namespace NzbDrone.Core.Indexers.Torznab
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
|
||||||
|
{
|
||||||
|
var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo;
|
||||||
|
|
||||||
|
if (torrentInfo != null)
|
||||||
|
{
|
||||||
|
torrentInfo.IndexerFlags = GetFlags(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return torrentInfo;
|
||||||
|
}
|
||||||
|
|
||||||
protected override string GetInfoUrl(XElement item)
|
protected override string GetInfoUrl(XElement item)
|
||||||
{
|
{
|
||||||
return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments"));
|
return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments"));
|
||||||
@@ -194,6 +206,53 @@ namespace NzbDrone.Core.Indexers.Torznab
|
|||||||
return base.GetPeers(item);
|
return base.GetPeers(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IndexerFlags GetFlags(XElement item)
|
||||||
|
{
|
||||||
|
IndexerFlags flags = 0;
|
||||||
|
|
||||||
|
var downloadFactor = TryGetFloatTorznabAttribute(item, "downloadvolumefactor", 1);
|
||||||
|
var uploadFactor = TryGetFloatTorznabAttribute(item, "uploadvolumefactor", 1);
|
||||||
|
|
||||||
|
if (downloadFactor == 0.5)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Halfleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadFactor == 0.75)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech25;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadFactor == 0.25)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech75;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadFactor == 0.0)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadFactor == 2.0)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.DoubleUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags = TryGetMultipleTorznabAttributes(item, "tag");
|
||||||
|
|
||||||
|
if (tags.Any(t => t.EqualsIgnoreCase("internal")))
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tags.Any(t => t.EqualsIgnoreCase("scene")))
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "")
|
protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "")
|
||||||
{
|
{
|
||||||
var attrElement = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
var attrElement = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||||
@@ -209,6 +268,13 @@ namespace NzbDrone.Core.Indexers.Torznab
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected float TryGetFloatTorznabAttribute(XElement item, string key, float defaultValue = 0)
|
||||||
|
{
|
||||||
|
var attr = TryGetTorznabAttribute(item, key, defaultValue.ToString());
|
||||||
|
|
||||||
|
return float.TryParse(attr, out var result) ? result : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
protected List<string> TryGetMultipleTorznabAttributes(XElement item, string key)
|
protected List<string> TryGetMultipleTorznabAttributes(XElement item, string key)
|
||||||
{
|
{
|
||||||
var attrElements = item.Elements(ns + "attr").Where(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
var attrElements = item.Elements(ns + "attr").Where(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|||||||
@@ -631,5 +631,10 @@
|
|||||||
"ExtraFileExtensionsHelpTextsExamples": "أمثلة: \".sub أو .nfo\" أو \"sub، nfo\"",
|
"ExtraFileExtensionsHelpTextsExamples": "أمثلة: \".sub أو .nfo\" أو \"sub، nfo\"",
|
||||||
"AutoRedownloadFailed": "التحميل فشل",
|
"AutoRedownloadFailed": "التحميل فشل",
|
||||||
"SourceTitle": "عنوان المصدر",
|
"SourceTitle": "عنوان المصدر",
|
||||||
"RemoveQueueItemConfirmation": "هل تريد بالتأكيد إزالة {0} عنصر {1} من قائمة الانتظار؟"
|
"RemoveQueueItemConfirmation": "هل تريد بالتأكيد إزالة {0} عنصر {1} من قائمة الانتظار؟",
|
||||||
|
"ImportLists": "القوائم",
|
||||||
|
"ListsSettingsSummary": "القوائم",
|
||||||
|
"SelectDropdown": "'تحديد...",
|
||||||
|
"SelectQuality": "حدد الجودة",
|
||||||
|
"CustomFilter": "مرشحات مخصصة"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -630,5 +630,11 @@
|
|||||||
"ExtraFileExtensionsHelpText": "Списък с допълнителни файлове за импортиране, разделени със запетая (.nfo ще бъде импортиран като .nfo-orig)",
|
"ExtraFileExtensionsHelpText": "Списък с допълнителни файлове за импортиране, разделени със запетая (.nfo ще бъде импортиран като .nfo-orig)",
|
||||||
"ExtraFileExtensionsHelpTextsExamples": "Примери: '.sub, .nfo' или 'sub, nfo'",
|
"ExtraFileExtensionsHelpTextsExamples": "Примери: '.sub, .nfo' или 'sub, nfo'",
|
||||||
"AutoRedownloadFailed": "Изтеглянето се провали",
|
"AutoRedownloadFailed": "Изтеглянето се провали",
|
||||||
"SourceTitle": "Заглавие на източника"
|
"SourceTitle": "Заглавие на източника",
|
||||||
|
"ImportLists": "Списъци",
|
||||||
|
"ListsSettingsSummary": "Списъци",
|
||||||
|
"SelectDropdown": "„Изберете ...",
|
||||||
|
"SelectQuality": "Изберете Качество",
|
||||||
|
"CustomFilter": "Персонализирани филтри",
|
||||||
|
"RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -688,5 +688,8 @@
|
|||||||
"SourceTitle": "Název zdroje",
|
"SourceTitle": "Název zdroje",
|
||||||
"AutoRedownloadFailed": "Opětovné stažení se nezdařilo",
|
"AutoRedownloadFailed": "Opětovné stažení se nezdařilo",
|
||||||
"AutoRedownloadFailedFromInteractiveSearch": "Opětovné stažení z interaktivního vyhledávání selhalo",
|
"AutoRedownloadFailedFromInteractiveSearch": "Opětovné stažení z interaktivního vyhledávání selhalo",
|
||||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automaticky vyhledat a pokusit se o stažení jiného vydání, pokud bylo neúspěšné vydání zachyceno z interaktivního vyhledávání"
|
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automaticky vyhledat a pokusit se o stažení jiného vydání, pokud bylo neúspěšné vydání zachyceno z interaktivního vyhledávání",
|
||||||
|
"SelectDropdown": "'Vybrat...",
|
||||||
|
"CustomFilter": "Vlastní filtry",
|
||||||
|
"SelectQuality": "Vyberte kvalitu"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,10 +363,10 @@
|
|||||||
"TagIsNotUsedAndCanBeDeleted": "Tag bruges ikke og kan slettes",
|
"TagIsNotUsedAndCanBeDeleted": "Tag bruges ikke og kan slettes",
|
||||||
"Tags": "Mærker",
|
"Tags": "Mærker",
|
||||||
"Tasks": "Opgaver",
|
"Tasks": "Opgaver",
|
||||||
"TestAll": "Test alle",
|
"TestAll": "Afprøv alle",
|
||||||
"TestAllClients": "Test alle klienter",
|
"TestAllClients": "Afprøv alle klienter",
|
||||||
"TestAllIndexers": "Test alle indeksører",
|
"TestAllIndexers": "Afprøv alle indeks",
|
||||||
"TestAllLists": "Test alle lister",
|
"TestAllLists": "Afprøv alle lister",
|
||||||
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Dette gælder for alle indeksører. Følg de regler, der er angivet af dem",
|
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Dette gælder for alle indeksører. Følg de regler, der er angivet af dem",
|
||||||
"TimeFormat": "Tidsformat",
|
"TimeFormat": "Tidsformat",
|
||||||
"Title": "Titel",
|
"Title": "Titel",
|
||||||
@@ -542,7 +542,7 @@
|
|||||||
"Yesterday": "I går",
|
"Yesterday": "I går",
|
||||||
"RestartRequiredHelpTextWarning": "Kræver genstart for at træde i kraft",
|
"RestartRequiredHelpTextWarning": "Kræver genstart for at træde i kraft",
|
||||||
"AddList": "Tilføj Liste",
|
"AddList": "Tilføj Liste",
|
||||||
"Test": "Prøve",
|
"Test": "Afprøv",
|
||||||
"RenameFiles": "Omdøb filer",
|
"RenameFiles": "Omdøb filer",
|
||||||
"ManualImportSelectEdition": "Manuel import - Vælg film",
|
"ManualImportSelectEdition": "Manuel import - Vælg film",
|
||||||
"ImportListExclusions": "Slet udelukkelse af importliste",
|
"ImportListExclusions": "Slet udelukkelse af importliste",
|
||||||
@@ -638,5 +638,11 @@
|
|||||||
"ExtraFileExtensionsHelpTextsExamples": "Eksempler: '.sub, .nfo' eller 'sub, nfo'",
|
"ExtraFileExtensionsHelpTextsExamples": "Eksempler: '.sub, .nfo' eller 'sub, nfo'",
|
||||||
"AutoRedownloadFailed": "Download fejlede",
|
"AutoRedownloadFailed": "Download fejlede",
|
||||||
"SourceTitle": "Kildetitel",
|
"SourceTitle": "Kildetitel",
|
||||||
"RemoveQueueItemConfirmation": "Er du sikker på, at du vil fjerne {0} element {1} fra køen?"
|
"RemoveQueueItemConfirmation": "Er du sikker på, at du vil fjerne {0} element {1} fra køen?",
|
||||||
|
"ImportLists": "Lister",
|
||||||
|
"ListsSettingsSummary": "Lister",
|
||||||
|
"CustomFilter": "Bruger Tilpassede Filtere",
|
||||||
|
"SelectDropdown": "'Vælg...",
|
||||||
|
"SelectQuality": "Vælg Kvalitet",
|
||||||
|
"ApplyChanges": "Anvend ændringer"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,7 +505,7 @@
|
|||||||
"ThisCannotBeCancelled": "Nach dem Start kann dies nicht mehr abgebrochen werden ohne alle Indexer zu deaktivieren.",
|
"ThisCannotBeCancelled": "Nach dem Start kann dies nicht mehr abgebrochen werden ohne alle Indexer zu deaktivieren.",
|
||||||
"UnselectAll": "Alle abwählen",
|
"UnselectAll": "Alle abwählen",
|
||||||
"UpdateSelected": "Auswahl aktualisieren",
|
"UpdateSelected": "Auswahl aktualisieren",
|
||||||
"Wanted": "› Gesucht",
|
"Wanted": "Gesucht",
|
||||||
"CreateEmptyAuthorFolders": "Leere Filmordner erstellen",
|
"CreateEmptyAuthorFolders": "Leere Filmordner erstellen",
|
||||||
"All": "Alle",
|
"All": "Alle",
|
||||||
"Country": "Land",
|
"Country": "Land",
|
||||||
@@ -990,5 +990,13 @@
|
|||||||
"AutoAdd": "Automatisch hinzufügen",
|
"AutoAdd": "Automatisch hinzufügen",
|
||||||
"WouldYouLikeToRestoreBackup": "Möchten Sie die Sicherung „{name}“ wiederherstellen?",
|
"WouldYouLikeToRestoreBackup": "Möchten Sie die Sicherung „{name}“ wiederherstellen?",
|
||||||
"Unmonitored": "Nicht beobachtet",
|
"Unmonitored": "Nicht beobachtet",
|
||||||
"Retention": "Aufbewahrung ( Retention )"
|
"Retention": "Aufbewahrung ( Retention )",
|
||||||
|
"ClickToChangeIndexerFlags": "Klicken, um Indexer-Flags zu ändern",
|
||||||
|
"BlocklistAndSearch": "Sperrliste und Suche",
|
||||||
|
"BlocklistAndSearchHint": "Starte Suche nach einer Alternative, falls es der Sperrliste hinzugefügt wurde",
|
||||||
|
"BlocklistAndSearchMultipleHint": "Starte Suchen nach einer Alternative, falls es der Sperrliste hinzugefügt wurde",
|
||||||
|
"BlocklistMultipleOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternativen zu suchen",
|
||||||
|
"BlocklistOnly": "Nur der Sperrliste hinzufügen",
|
||||||
|
"BlocklistOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternative zu suchen",
|
||||||
|
"ChangeCategory": "Kategorie wechseln"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@
|
|||||||
"ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.",
|
"ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.",
|
||||||
"ChownGroupHelpTextWarning": "This only works if the user running Readarr is the owner of the file. It's better to ensure the download client uses the same group as Readarr.",
|
"ChownGroupHelpTextWarning": "This only works if the user running Readarr is the owner of the file. It's better to ensure the download client uses the same group as Readarr.",
|
||||||
"Clear": "Clear",
|
"Clear": "Clear",
|
||||||
|
"ClickToChangeIndexerFlags": "Click to change indexer flags",
|
||||||
"ClickToChangeQuality": "Click to change quality",
|
"ClickToChangeQuality": "Click to change quality",
|
||||||
"ClickToChangeReleaseGroup": "Click to change release group",
|
"ClickToChangeReleaseGroup": "Click to change release group",
|
||||||
"ClientPriority": "Client Priority",
|
"ClientPriority": "Client Priority",
|
||||||
@@ -192,6 +193,7 @@
|
|||||||
"CustomFormatScore": "Custom Format Score",
|
"CustomFormatScore": "Custom Format Score",
|
||||||
"CustomFormatSettings": "Custom Format Settings",
|
"CustomFormatSettings": "Custom Format Settings",
|
||||||
"CustomFormats": "Custom Formats",
|
"CustomFormats": "Custom Formats",
|
||||||
|
"CustomFormatsSpecificationFlag": "Flag",
|
||||||
"CustomFormatsSpecificationRegularExpression": "Regular Expression",
|
"CustomFormatsSpecificationRegularExpression": "Regular Expression",
|
||||||
"CustomFormatsSpecificationRegularExpressionHelpText": "Custom Format RegEx is Case Insensitive",
|
"CustomFormatsSpecificationRegularExpressionHelpText": "Custom Format RegEx is Case Insensitive",
|
||||||
"CutoffFormatScoreHelpText": "Once this custom format score is reached Readarr will no longer grab book releases",
|
"CutoffFormatScoreHelpText": "Once this custom format score is reached Readarr will no longer grab book releases",
|
||||||
@@ -439,6 +441,7 @@
|
|||||||
"Indexer": "Indexer",
|
"Indexer": "Indexer",
|
||||||
"IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {0}.",
|
"IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {0}.",
|
||||||
"IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer",
|
"IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer",
|
||||||
|
"IndexerFlags": "Indexer Flags",
|
||||||
"IndexerIdHelpText": "Specify what indexer the profile applies to",
|
"IndexerIdHelpText": "Specify what indexer the profile applies to",
|
||||||
"IndexerIdHelpTextWarning": "Using a specific indexer with preferred words can lead to duplicate releases being grabbed",
|
"IndexerIdHelpTextWarning": "Using a specific indexer with preferred words can lead to duplicate releases being grabbed",
|
||||||
"IndexerJackettAll": "Indexers using the unsupported Jackett 'all' endpoint: {0}",
|
"IndexerJackettAll": "Indexers using the unsupported Jackett 'all' endpoint: {0}",
|
||||||
@@ -735,6 +738,7 @@
|
|||||||
"RefreshBook": "Refresh Book",
|
"RefreshBook": "Refresh Book",
|
||||||
"RefreshInformation": "Refresh information",
|
"RefreshInformation": "Refresh information",
|
||||||
"RefreshInformationAndScanDisk": "Refresh information and scan disk",
|
"RefreshInformationAndScanDisk": "Refresh information and scan disk",
|
||||||
|
"Rejections": "Rejections",
|
||||||
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid Readarr release branch, you will not receive updates",
|
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid Readarr release branch, you will not receive updates",
|
||||||
"ReleaseDate": "Release Date",
|
"ReleaseDate": "Release Date",
|
||||||
"ReleaseGroup": "Release Group",
|
"ReleaseGroup": "Release Group",
|
||||||
@@ -853,6 +857,7 @@
|
|||||||
"SelectBook": "Select Book",
|
"SelectBook": "Select Book",
|
||||||
"SelectDropdown": "Select...",
|
"SelectDropdown": "Select...",
|
||||||
"SelectEdition": "Select Edition",
|
"SelectEdition": "Select Edition",
|
||||||
|
"SelectIndexerFlags": "Select Indexer Flags",
|
||||||
"SelectQuality": "Select Quality",
|
"SelectQuality": "Select Quality",
|
||||||
"SelectReleaseGroup": "Select Release Group",
|
"SelectReleaseGroup": "Select Release Group",
|
||||||
"SelectedCountAuthorsSelectedInterp": "{0} Author(s) Selected",
|
"SelectedCountAuthorsSelectedInterp": "{0} Author(s) Selected",
|
||||||
@@ -862,6 +867,7 @@
|
|||||||
"Series": "Series",
|
"Series": "Series",
|
||||||
"SeriesNumber": "Series Number",
|
"SeriesNumber": "Series Number",
|
||||||
"SeriesTotal": "Series ({0})",
|
"SeriesTotal": "Series ({0})",
|
||||||
|
"SetIndexerFlags": "Set Indexer Flags",
|
||||||
"SetPermissions": "Set Permissions",
|
"SetPermissions": "Set Permissions",
|
||||||
"SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?",
|
"SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?",
|
||||||
"SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.",
|
"SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.",
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
"ApiKeyHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
"ApiKeyHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
||||||
"DeleteRootFolderMessageText": "¿Está seguro de querer eliminar la carpeta raíz '{0}'?",
|
"DeleteRootFolderMessageText": "¿Está seguro de querer eliminar la carpeta raíz '{0}'?",
|
||||||
"LoadingBooksFailed": "La carga de los archivos ha fallado",
|
"LoadingBooksFailed": "La carga de los archivos ha fallado",
|
||||||
"ProxyUsernameHelpText": "Tienes que introducir tu nombre de usuario y contraseña sólo si son requeridos. Si no, déjalos vacios.",
|
"ProxyUsernameHelpText": "Solo necesitas introducir un usuario y contraseña si se requiere alguno. De otra forma déjalos en blanco.",
|
||||||
"SslPortHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
"SslPortHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
||||||
"SslCertPathHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
"SslCertPathHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
||||||
"UnableToLoadMetadataProfiles": "No se pueden cargar los Perfiles de Retraso",
|
"UnableToLoadMetadataProfiles": "No se pueden cargar los Perfiles de Retraso",
|
||||||
"BookIsDownloading": "Le película está descargando",
|
"BookIsDownloading": "Le película está descargando",
|
||||||
"Group": "Grupo",
|
"Group": "Grupo",
|
||||||
"MIA": "MIA",
|
"MIA": "MIA",
|
||||||
"ShowDateAdded": "Mostrar Fecha de Añadido",
|
"ShowDateAdded": "Mostrar fecha de adición",
|
||||||
"Tags": "Etiquetas",
|
"Tags": "Etiquetas",
|
||||||
"60MinutesSixty": "60 Minutos: {0}",
|
"60MinutesSixty": "60 Minutos: {0}",
|
||||||
"APIKey": "Clave API",
|
"APIKey": "Clave API",
|
||||||
@@ -223,10 +223,10 @@
|
|||||||
"OnHealthIssueHelpText": "En Problema de Salud",
|
"OnHealthIssueHelpText": "En Problema de Salud",
|
||||||
"OnRenameHelpText": "Al Renombrar",
|
"OnRenameHelpText": "Al Renombrar",
|
||||||
"OnUpgradeHelpText": "Al Mejorar La Calidad",
|
"OnUpgradeHelpText": "Al Mejorar La Calidad",
|
||||||
"OpenBrowserOnStart": "Abrir navegador al arrancar",
|
"OpenBrowserOnStart": "Abrir navegador al inicio",
|
||||||
"Options": "Opciones",
|
"Options": "Opciones",
|
||||||
"Original": "Original",
|
"Original": "Original",
|
||||||
"Overview": "Resumen",
|
"Overview": "Vista general",
|
||||||
"PackageVersion": "Versión del paquete",
|
"PackageVersion": "Versión del paquete",
|
||||||
"PageSize": "Tamaño de Página",
|
"PageSize": "Tamaño de Página",
|
||||||
"PageSizeHelpText": "Número de elementos por página",
|
"PageSizeHelpText": "Número de elementos por página",
|
||||||
@@ -235,24 +235,24 @@
|
|||||||
"Permissions": "Permisos",
|
"Permissions": "Permisos",
|
||||||
"Port": "Puerto",
|
"Port": "Puerto",
|
||||||
"PortHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
"PortHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
||||||
"PortNumber": "Número de Puerto",
|
"PortNumber": "Número de puerto",
|
||||||
"PosterSize": "Tamaño del Poster",
|
"PosterSize": "Tamaño de póster",
|
||||||
"PreviewRename": "Previsualizar Renombrado",
|
"PreviewRename": "Previsualizar renombrado",
|
||||||
"Profiles": "Perfiles",
|
"Profiles": "Perfiles",
|
||||||
"Proper": "Apropiado",
|
"Proper": "Proper",
|
||||||
"PropersAndRepacks": "Propers y Repacks",
|
"PropersAndRepacks": "Propers y Repacks",
|
||||||
"Protocol": "Protocolo",
|
"Protocol": "Protocolo",
|
||||||
"ProtocolHelpText": "Elige qué protocolo(s) se usará y cual será el preferido cuando haya que elegir entre lanzamientos iguales",
|
"ProtocolHelpText": "Elige qué protocolo(s) usar y cuál se prefiere cuando se elige entre lanzamientos equivalentes",
|
||||||
"Proxy": "Proxy",
|
"Proxy": "Proxy",
|
||||||
"ProxyBypassFilterHelpText": "Usa ',' como separador, y '*.' como wildcard para subdominios",
|
"ProxyBypassFilterHelpText": "Usa ',' como separador, y '*.' como comodín para subdominios",
|
||||||
"ProxyPasswordHelpText": "Tienes que introducir tu nombre de usuario y contraseña sólo si son requeridos. Si no, déjalos vacios.",
|
"ProxyPasswordHelpText": "Solo necesitas introducir un usuario y contraseña si se requiere alguno. De otra forma déjalos en blanco.",
|
||||||
"ProxyType": "Tipo de Proxy",
|
"ProxyType": "Tipo de proxy",
|
||||||
"PublishedDate": "Fecha de Publicación",
|
"PublishedDate": "Fecha de publicación",
|
||||||
"Quality": "Calidad",
|
"Quality": "Calidad",
|
||||||
"QualityDefinitions": "Definiciones de Calidad",
|
"QualityDefinitions": "Definiciones de calidad",
|
||||||
"QualityProfile": "Perfil de Calidad",
|
"QualityProfile": "Perfil de calidad",
|
||||||
"QualityProfiles": "Perfiles de Calidad",
|
"QualityProfiles": "Perfiles de calidad",
|
||||||
"QualitySettings": "Ajustes de Calidad",
|
"QualitySettings": "Opciones de calidad",
|
||||||
"Queue": "Cola",
|
"Queue": "Cola",
|
||||||
"RSSSync": "Sincronizar RSS",
|
"RSSSync": "Sincronizar RSS",
|
||||||
"RSSSyncInterval": "Intervalo de Sincronización de RSS",
|
"RSSSyncInterval": "Intervalo de Sincronización de RSS",
|
||||||
@@ -264,85 +264,85 @@
|
|||||||
"RecycleBinCleanupDaysHelpText": "Ajustar a 0 para desactivar la limpieza automática",
|
"RecycleBinCleanupDaysHelpText": "Ajustar a 0 para desactivar la limpieza automática",
|
||||||
"RecycleBinCleanupDaysHelpTextWarning": "Los archivos en la papelera de reciclaje más antiguos que el número de días seleccionado serán limpiados automáticamente",
|
"RecycleBinCleanupDaysHelpTextWarning": "Los archivos en la papelera de reciclaje más antiguos que el número de días seleccionado serán limpiados automáticamente",
|
||||||
"RecycleBinHelpText": "Los archivos iran aquí una vez se hayan borrado en vez de ser borrados permanentemente",
|
"RecycleBinHelpText": "Los archivos iran aquí una vez se hayan borrado en vez de ser borrados permanentemente",
|
||||||
"RecyclingBin": "Papelera de Reciclaje",
|
"RecyclingBin": "Papelera de reciclaje",
|
||||||
"RecyclingBinCleanup": "Limpieza de Papelera de Reciclaje",
|
"RecyclingBinCleanup": "Limpieza de la papelera de reciclaje",
|
||||||
"Redownload": "Volver a descargar",
|
"Redownload": "Volver a descargar",
|
||||||
"Refresh": "Actualizar",
|
"Refresh": "Actualizar",
|
||||||
"RefreshInformationAndScanDisk": "Actualizar la información al escanear el disco",
|
"RefreshInformationAndScanDisk": "Actualizar la información al escanear el disco",
|
||||||
"ReleaseDate": "Fechas de Estreno",
|
"ReleaseDate": "Fechas de Estreno",
|
||||||
"ReleaseGroup": "Grupo de Estreno",
|
"ReleaseGroup": "Grupo de lanzamiento",
|
||||||
"ReleaseRejected": "Lanzamiento Rechazado",
|
"ReleaseRejected": "Lanzamiento rechazado",
|
||||||
"ReleaseWillBeProcessedInterp": "El lanzamiento será procesado {0}",
|
"ReleaseWillBeProcessedInterp": "El lanzamiento será procesado {0}",
|
||||||
"Reload": "Recargar",
|
"Reload": "Recargar",
|
||||||
"RemotePathMappings": "Mapeados de Rutas Remotas",
|
"RemotePathMappings": "Mapeos de ruta remota",
|
||||||
"Remove": "Eliminar",
|
"Remove": "Eliminar",
|
||||||
"RemoveCompletedDownloadsHelpText": "Eliminar las descargas ya importadas del historial del gestor de descargas",
|
"RemoveCompletedDownloadsHelpText": "Elimina las descargas importadas desde el historial del cliente de descarga",
|
||||||
"RemoveFailedDownloadsHelpText": "Eliminar descargas fallidas del historial del gestor de descargas",
|
"RemoveFailedDownloadsHelpText": "Eliminar descargas fallidas desde el historial del cliente de descarga",
|
||||||
"RemoveFilter": "Eliminar filtro",
|
"RemoveFilter": "Eliminar filtro",
|
||||||
"RemoveFromDownloadClient": "Eliminar del Gestor de Descargas",
|
"RemoveFromDownloadClient": "Eliminar del cliente de descarga",
|
||||||
"RemoveFromQueue": "Eliminar de la cola",
|
"RemoveFromQueue": "Eliminar de la cola",
|
||||||
"RemoveHelpTextWarning": "Eliminar borrará la descarga y el/los fichero(s) del gestor de descargas.",
|
"RemoveHelpTextWarning": "Eliminar borrará la descarga y el/los fichero(s) del gestor de descargas.",
|
||||||
"RemoveSelected": "Borrar Seleccionados",
|
"RemoveSelected": "Eliminar seleccionado",
|
||||||
"RemoveTagExistingTag": "Etiqueta existente",
|
"RemoveTagExistingTag": "Etiqueta existente",
|
||||||
"RemoveTagRemovingTag": "Eliminando etiqueta",
|
"RemoveTagRemovingTag": "Eliminando etiqueta",
|
||||||
"RemovedFromTaskQueue": "Eliminar de la cola de tareas",
|
"RemovedFromTaskQueue": "Eliminar de la cola de tareas",
|
||||||
"RenameBooksHelpText": "Radarr usará el nombre del archivo si el renombrado está deshabilitado",
|
"RenameBooksHelpText": "Radarr usará el nombre del archivo si el renombrado está deshabilitado",
|
||||||
"Reorder": "Reordenar",
|
"Reorder": "Reordenar",
|
||||||
"ReplaceIllegalCharacters": "Reemplazar Caracteres Ilegales",
|
"ReplaceIllegalCharacters": "Reemplazar caracteres ilegales",
|
||||||
"RequiredHelpText": "El comunicado debe contener al menos uno de estos términos (no distingue entre mayúsculas y minúsculas)",
|
"RequiredHelpText": "El comunicado debe contener al menos uno de estos términos (no distingue entre mayúsculas y minúsculas)",
|
||||||
"RequiredPlaceHolder": "Añadir nueva restricción",
|
"RequiredPlaceHolder": "Añadir nueva restricción",
|
||||||
"RescanAfterRefreshHelpTextWarning": "Radarr no detectará los cambios automáticamente en los ficheros si no se ajusta a 'Siempre'",
|
"RescanAfterRefreshHelpTextWarning": "Radarr no detectará los cambios automáticamente en los ficheros si no se ajusta a 'Siempre'",
|
||||||
"RescanAuthorFolderAfterRefresh": "Reescanear la Carpeta de Películas después de Actualizar",
|
"RescanAuthorFolderAfterRefresh": "Reescanear la Carpeta de Películas después de Actualizar",
|
||||||
"Reset": "Reiniciar",
|
"Reset": "Reiniciar",
|
||||||
"ResetAPIKey": "Reajustar API",
|
"ResetAPIKey": "Restablecer clave API",
|
||||||
"ResetAPIKeyMessageText": "¿Está seguro de que desea restablecer su clave API?",
|
"ResetAPIKeyMessageText": "¿Estás seguro que quieres restablecer tu clave API?",
|
||||||
"Restart": "Reiniciar",
|
"Restart": "Reiniciar",
|
||||||
"RestartNow": "Reiniciar Ahora",
|
"RestartNow": "Reiniciar ahora",
|
||||||
"RestartReadarr": "Reiniciar Radarr",
|
"RestartReadarr": "Reiniciar Radarr",
|
||||||
"Restore": "Restaurar",
|
"Restore": "Restaurar",
|
||||||
"RestoreBackup": "Recuperar Backup",
|
"RestoreBackup": "Restaurar copia de seguridad",
|
||||||
"Result": "Resultado",
|
"Result": "Resultado",
|
||||||
"Retention": "Retención",
|
"Retention": "Retención",
|
||||||
"RetentionHelpText": "Sólo Usenet: Ajustar a cero para retención ilimitada",
|
"RetentionHelpText": "Solo usenet: Establece a cero para establecer una retención ilimitada",
|
||||||
"RetryingDownloadInterp": "Re-intentando descarga {0} en {1}",
|
"RetryingDownloadInterp": "Re-intentando descarga {0} en {1}",
|
||||||
"RootFolder": "Carpeta de Origen",
|
"RootFolder": "Carpeta raíz",
|
||||||
"RootFolders": "Carpetas de Origen",
|
"RootFolders": "Carpetas raíz",
|
||||||
"RssSyncIntervalHelpText": "Intervalo en minutos. Ajustar a cero para inhabilitar (esto dentendrá toda captura de estrenos automática)",
|
"RssSyncIntervalHelpText": "Intervalo en minutos. Configurar a cero para deshabilitar (esto detendrá todas las capturas automáticas de lanzamientos)",
|
||||||
"SSLCertPassword": "Contraseña del Certificado SSL",
|
"SSLCertPassword": "Contraseña del Certificado SSL",
|
||||||
"SSLCertPath": "Ruta del Certificado SSL",
|
"SSLCertPath": "Ruta del Certificado SSL",
|
||||||
"SSLPort": "Puerto SSL",
|
"SSLPort": "Puerto SSL",
|
||||||
"Scheduled": "Programado",
|
"Scheduled": "Programado",
|
||||||
"ScriptPath": "Ruta del Script",
|
"ScriptPath": "Ruta del script",
|
||||||
"Search": "Buscar",
|
"Search": "Buscar",
|
||||||
"SearchAll": "Buscar Todas",
|
"SearchAll": "Buscar todo",
|
||||||
"SearchForMissing": "Buscar faltantes",
|
"SearchForMissing": "Buscar perdidos",
|
||||||
"SearchSelected": "Buscar Seleccionadas",
|
"SearchSelected": "Buscar seleccionados",
|
||||||
"Security": "Seguridad",
|
"Security": "Seguridad",
|
||||||
"SendAnonymousUsageData": "Enviar Datos de Uso Anónimamente",
|
"SendAnonymousUsageData": "Enviar datos de uso anónimos",
|
||||||
"SetPermissions": "Ajustar Permisos",
|
"SetPermissions": "Establecer permisos",
|
||||||
"SetPermissionsLinuxHelpText": "Debe chmod ser ejecutado una vez los archivos hayan sido importados/renombrados?",
|
"SetPermissionsLinuxHelpText": "¿Debería ejecutarse chmod cuando los archivos son importados/renombrados?",
|
||||||
"SetPermissionsLinuxHelpTextWarning": "Si no estas seguro de lo que hacen estos ajustes, no los modifiques.",
|
"SetPermissionsLinuxHelpTextWarning": "Si no estás seguro qué configuraciones hacer, no las cambies.",
|
||||||
"Settings": "Ajustes",
|
"Settings": "Ajustes",
|
||||||
"ShortDateFormat": "Formato Corto de Fecha",
|
"ShortDateFormat": "Formato de fecha breve",
|
||||||
"ShowCutoffUnmetIconHelpText": "Mostrar el icono para los ficheros cuando no se ha alcanzado el corte",
|
"ShowCutoffUnmetIconHelpText": "Mostrar el icono para los ficheros cuando no se ha alcanzado el corte",
|
||||||
"ShowMonitored": "Mostrar Monitoreadas",
|
"ShowMonitored": "Mostrar monitorizado",
|
||||||
"ShowMonitoredHelpText": "Mostrar el estado de monitoreo debajo del poster",
|
"ShowMonitoredHelpText": "Muestra el estado monitorizado bajo el póster",
|
||||||
"ShowPath": "Mostrar Ruta",
|
"ShowPath": "Mostrar ruta",
|
||||||
"ShowQualityProfile": "Mostrar Perfil de Calidad",
|
"ShowQualityProfile": "Mostrar perfil de calidad",
|
||||||
"ShowQualityProfileHelpText": "Mostrar el perfil de calidad debajo del poster",
|
"ShowQualityProfileHelpText": "Muestra el perfil de calidad bajo el póster",
|
||||||
"ShowRelativeDates": "Mostrar Fechas Relativas",
|
"ShowRelativeDates": "Mostrar fechas relativas",
|
||||||
"ShowRelativeDatesHelpText": "Mostrar fechas relativas (Hoy/Ayer/etc) o absolutas",
|
"ShowRelativeDatesHelpText": "Muestra fechas absolutas o relativas (Hoy/Ayer/etc)",
|
||||||
"ShowSearch": "Mostrar Búsqueda",
|
"ShowSearch": "Mostrar búsqueda",
|
||||||
"ShowSearchActionHelpText": "Mostrar botón de búsqueda al pasar el cursor por encima",
|
"ShowSearchActionHelpText": "Mostrar botón de búsqueda al pasar el cursor por encima",
|
||||||
"ShowSizeOnDisk": "Mostrar Tamaño en Disco",
|
"ShowSizeOnDisk": "Mostrar tamaño en disco",
|
||||||
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Mostrado sobre cada columna cuando la vista activa es semana",
|
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Mostrado sobre cada columna cuando la vista activa es semana",
|
||||||
"Size": " Tamaño",
|
"Size": " Tamaño",
|
||||||
"SkipFreeSpaceCheck": "Saltarse Comprobación de Espacio Disponible",
|
"SkipFreeSpaceCheck": "Saltar comprobación de espacio libre",
|
||||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Usar cuando Radarr no pueda detectar el espacio disponible en la carpeta de películas",
|
"SkipFreeSpaceCheckWhenImportingHelpText": "Usar cuando Radarr no pueda detectar el espacio disponible en la carpeta de películas",
|
||||||
"SorryThatAuthorCannotBeFound": "Lo siento, no he encontrado esa película.",
|
"SorryThatAuthorCannotBeFound": "Lo siento, no he encontrado esa película.",
|
||||||
"SorryThatBookCannotBeFound": "Lo siento, no he encontrado esa película.",
|
"SorryThatBookCannotBeFound": "Lo siento, no he encontrado esa película.",
|
||||||
"Source": "Fuente",
|
"Source": "Fuente",
|
||||||
"SourcePath": "Ruta de Origen",
|
"SourcePath": "Ruta de la fuente",
|
||||||
"SslCertPasswordHelpText": "Contraseña para el archivo pfx",
|
"SslCertPasswordHelpText": "Contraseña para el archivo pfx",
|
||||||
"SslCertPasswordHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
"SslCertPasswordHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
||||||
"SslCertPathHelpText": "Ruta al archivo pfx",
|
"SslCertPathHelpText": "Ruta al archivo pfx",
|
||||||
@@ -360,17 +360,17 @@
|
|||||||
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Se usará cuando se utilice la búsqueda interactiva",
|
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Se usará cuando se utilice la búsqueda interactiva",
|
||||||
"TagIsNotUsedAndCanBeDeleted": "La etiqueta no se usa y puede ser borrada",
|
"TagIsNotUsedAndCanBeDeleted": "La etiqueta no se usa y puede ser borrada",
|
||||||
"Tasks": "Tareas",
|
"Tasks": "Tareas",
|
||||||
"TestAll": "Testear Todo",
|
"TestAll": "Probar todo",
|
||||||
"TestAllClients": "Comprobar Todos los Gestores",
|
"TestAllClients": "Probar todos los clientes",
|
||||||
"TestAllIndexers": "Comprobar Todos los Indexers",
|
"TestAllIndexers": "Probar todos los indexadores",
|
||||||
"TestAllLists": "Comprobar Todas las Listas",
|
"TestAllLists": "Probar todas las listas",
|
||||||
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Se aplicará a todos los indexers, por favor sigue las reglas de los mismos",
|
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Se aplicará a todos los indexers, por favor sigue las reglas de los mismos",
|
||||||
"TimeFormat": "Formato de Hora",
|
"TimeFormat": "Formato de hora",
|
||||||
"Title": "Título",
|
"Title": "Título",
|
||||||
"TorrentDelay": "Retraso del Torrent",
|
"TorrentDelay": "Retraso de torrent",
|
||||||
"TorrentDelayHelpText": "Retraso en minutos a esperar antes de descargar un torrent",
|
"TorrentDelayHelpText": "Retraso en minutos a esperar antes de capturar un torrent",
|
||||||
"Torrents": "Torrents",
|
"Torrents": "Torrents",
|
||||||
"TotalFileSize": "Tamaño Total del Archivo",
|
"TotalFileSize": "Tamaño total de archivo",
|
||||||
"UILanguage": "Lenguaje de UI",
|
"UILanguage": "Lenguaje de UI",
|
||||||
"UILanguageHelpText": "Lenguaje que Radarr usara para el UI",
|
"UILanguageHelpText": "Lenguaje que Radarr usara para el UI",
|
||||||
"UILanguageHelpTextWarning": "Recargar el Navegador",
|
"UILanguageHelpTextWarning": "Recargar el Navegador",
|
||||||
@@ -385,7 +385,7 @@
|
|||||||
"UnableToAddANewQualityProfilePleaseTryAgain": "No se ha podido añadir un nuevo perfil de calidad, prueba otra vez.",
|
"UnableToAddANewQualityProfilePleaseTryAgain": "No se ha podido añadir un nuevo perfil de calidad, prueba otra vez.",
|
||||||
"UnableToAddANewRemotePathMappingPleaseTryAgain": "No se ha podido añadir una nueva ruta remota, prueba otra vez.",
|
"UnableToAddANewRemotePathMappingPleaseTryAgain": "No se ha podido añadir una nueva ruta remota, prueba otra vez.",
|
||||||
"UnableToAddANewRootFolderPleaseTryAgain": "No se ha podido añadir un nuevo formato propio, prueba otra vez.",
|
"UnableToAddANewRootFolderPleaseTryAgain": "No se ha podido añadir un nuevo formato propio, prueba otra vez.",
|
||||||
"UnableToLoadBackups": "No se han podido cargar las copias de seguridad",
|
"UnableToLoadBackups": "No se pudo cargar las copias de seguridad",
|
||||||
"UnableToLoadDelayProfiles": "No se pueden cargar los Perfiles de Retraso",
|
"UnableToLoadDelayProfiles": "No se pueden cargar los Perfiles de Retraso",
|
||||||
"UnableToLoadDownloadClientOptions": "No se han podido cargar las opciones del gestor de descargas",
|
"UnableToLoadDownloadClientOptions": "No se han podido cargar las opciones del gestor de descargas",
|
||||||
"UnableToLoadDownloadClients": "No se puden cargar los gestores de descargas",
|
"UnableToLoadDownloadClients": "No se puden cargar los gestores de descargas",
|
||||||
@@ -408,27 +408,27 @@
|
|||||||
"UnableToLoadTags": "No se pueden cargar las Etiquetas",
|
"UnableToLoadTags": "No se pueden cargar las Etiquetas",
|
||||||
"UnableToLoadTheCalendar": "No se ha podido cargar el calendario",
|
"UnableToLoadTheCalendar": "No se ha podido cargar el calendario",
|
||||||
"UnableToLoadUISettings": "No se han podido cargar los ajustes de UI",
|
"UnableToLoadUISettings": "No se han podido cargar los ajustes de UI",
|
||||||
"Ungroup": "Desagrupar",
|
"Ungroup": "Sin agrupar",
|
||||||
"Unmonitored": "Sin monitorizar",
|
"Unmonitored": "Sin monitorizar",
|
||||||
"UnmonitoredHelpText": "Incluir las peliculas no monitoreadas en el feed de iCal",
|
"UnmonitoredHelpText": "Incluir las peliculas no monitoreadas en el feed de iCal",
|
||||||
"UpdateAll": "Actualizar Todo",
|
"UpdateAll": "Actualizar Todo",
|
||||||
"UpdateAutomaticallyHelpText": "Descargar e instalar actualizaciones automáticamente. Se podrán instalar desde Sistema: Actualizaciones también",
|
"UpdateAutomaticallyHelpText": "Descargar e instalar actualizaciones automáticamente. Todavía puedes instalar desde Sistema: Actualizaciones",
|
||||||
"UpdateMechanismHelpText": "Usar el actualizador de Radarr o un script",
|
"UpdateMechanismHelpText": "Usar el actualizador de Radarr o un script",
|
||||||
"UpdateScriptPathHelpText": "Ruta del script propio que toma el paquete de actualización y se encarga del proceso de actualización restante",
|
"UpdateScriptPathHelpText": "Ruta a un script personalizado que toma un paquete de actualización extraído y gestiona el resto del proceso de actualización",
|
||||||
"Updates": "Actualizaciones",
|
"Updates": "Actualizaciones",
|
||||||
"UpgradeAllowedHelpText": "Si está desactivado las calidades no serán actualizadas",
|
"UpgradeAllowedHelpText": "Si está desactivado las calidades no serán actualizadas",
|
||||||
"Uptime": "Tiempo de actividad",
|
"Uptime": "Tiempo de actividad",
|
||||||
"UrlBaseHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
"UrlBaseHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
||||||
"UseHardlinksInsteadOfCopy": "Usar Hardlinks en vez de Copia",
|
"UseHardlinksInsteadOfCopy": "Utilizar enlaces directos en lugar de copiar",
|
||||||
"UseProxy": "Usar el Proxy",
|
"UseProxy": "Usar proxy",
|
||||||
"Usenet": "Usenet",
|
"Usenet": "Usenet",
|
||||||
"UsenetDelay": "Retraso de Usenet",
|
"UsenetDelay": "Retraso de usenet",
|
||||||
"UsenetDelayHelpText": "Retraso en minutos a esperar antes de descargar un lanzamiento de Usenet",
|
"UsenetDelayHelpText": "Retraso en minutos a esperar antes de capturar un lanzamiento desde usenet",
|
||||||
"Username": "Nombre de usuario",
|
"Username": "Usuario",
|
||||||
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Qué rama usar para actualizar Radarr",
|
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Qué rama usar para actualizar Radarr",
|
||||||
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Rama usada por el mecanismo de actualización externo",
|
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Rama usada por el mecanismo de actualización externo",
|
||||||
"Version": "Versión",
|
"Version": "Versión",
|
||||||
"WeekColumnHeader": "Encabezado de la columna semanal",
|
"WeekColumnHeader": "Cabecera de columna de semana",
|
||||||
"Year": "Año",
|
"Year": "Año",
|
||||||
"YesCancel": "Sí, Cancelar",
|
"YesCancel": "Sí, Cancelar",
|
||||||
"20MinutesTwenty": "20 Minutos: {0}",
|
"20MinutesTwenty": "20 Minutos: {0}",
|
||||||
@@ -437,16 +437,16 @@
|
|||||||
"ReplaceIllegalCharactersHelpText": "Reemplazar caracteres ilegales. Si está desactivado, Radarr los eliminará si no",
|
"ReplaceIllegalCharactersHelpText": "Reemplazar caracteres ilegales. Si está desactivado, Radarr los eliminará si no",
|
||||||
"Actions": "Acciones",
|
"Actions": "Acciones",
|
||||||
"Today": "Hoy",
|
"Today": "Hoy",
|
||||||
"ReleaseTitle": "Título del Estreno",
|
"ReleaseTitle": "Título de lanzamiento",
|
||||||
"Progress": "Progreso",
|
"Progress": "Progreso",
|
||||||
"Tomorrow": "mañana",
|
"Tomorrow": "Mañana",
|
||||||
"OutputPath": "Ruta de Output",
|
"OutputPath": "Ruta de salida",
|
||||||
"BookAvailableButMissing": "Película Disponible pero Ausente",
|
"BookAvailableButMissing": "Película Disponible pero Ausente",
|
||||||
"NotAvailable": "No Disponible",
|
"NotAvailable": "No Disponible",
|
||||||
"NotMonitored": "No Monitoreadas",
|
"NotMonitored": "No Monitoreadas",
|
||||||
"ShowBookTitleHelpText": "Mostrar el título de la película debajo del poster",
|
"ShowBookTitleHelpText": "Mostrar el título de la película debajo del poster",
|
||||||
"ShowReleaseDate": "Mostrar fecha de lanzamiento",
|
"ShowReleaseDate": "Mostrar fecha de lanzamiento",
|
||||||
"ShowTitle": "Mostrar Título",
|
"ShowTitle": "Mostrar título",
|
||||||
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "Se eliminará la carpeta de películas '{0}' y todo su contenido.",
|
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "Se eliminará la carpeta de películas '{0}' y todo su contenido.",
|
||||||
"Component": "Componente",
|
"Component": "Componente",
|
||||||
"RemoveFromBlocklist": "Eliminar de lista de bloqueados",
|
"RemoveFromBlocklist": "Eliminar de lista de bloqueados",
|
||||||
@@ -457,13 +457,13 @@
|
|||||||
"Blocklist": "Lista de bloqueos",
|
"Blocklist": "Lista de bloqueos",
|
||||||
"BlocklistRelease": "Lista de bloqueos de lanzamiento",
|
"BlocklistRelease": "Lista de bloqueos de lanzamiento",
|
||||||
"CreateEmptyAuthorFolders": "Crear carpetas de películas vacías",
|
"CreateEmptyAuthorFolders": "Crear carpetas de películas vacías",
|
||||||
"SelectAll": "Seleccionar Todas",
|
"SelectAll": "Seleccionar todo",
|
||||||
"SelectedCountBooksSelectedInterp": "{0} Película(s) Seleccionada(s)",
|
"SelectedCountBooksSelectedInterp": "{0} Película(s) Seleccionada(s)",
|
||||||
"ThisCannotBeCancelled": "Esto no puede ser cancelado una vez iniciado sin deshabilitar todos sus indexadores.",
|
"ThisCannotBeCancelled": "Esto no puede ser cancelado una vez iniciado sin deshabilitar todos sus indexadores.",
|
||||||
"All": "Todo",
|
"All": "Todo",
|
||||||
"RescanAfterRefreshHelpText": "Reescanear la carpeta de películas después de actualizar la película",
|
"RescanAfterRefreshHelpText": "Reescanear la carpeta de películas después de actualizar la película",
|
||||||
"ShowUnknownAuthorItems": "Mostrar Elementos Desconocidos",
|
"ShowUnknownAuthorItems": "Mostrar Elementos Desconocidos",
|
||||||
"UnselectAll": "Deseleccionar Todo",
|
"UnselectAll": "Desmarcar todo",
|
||||||
"UpdateSelected": "Actualizar Seleccionadas",
|
"UpdateSelected": "Actualizar Seleccionadas",
|
||||||
"Wanted": "Buscado",
|
"Wanted": "Buscado",
|
||||||
"AllAuthorBooks": "Todos los libros del autor",
|
"AllAuthorBooks": "Todos los libros del autor",
|
||||||
@@ -485,9 +485,9 @@
|
|||||||
"Yesterday": "Ayer",
|
"Yesterday": "Ayer",
|
||||||
"UpdateAvailable": "La nueva actualización está disponible",
|
"UpdateAvailable": "La nueva actualización está disponible",
|
||||||
"Duration": "Duración",
|
"Duration": "Duración",
|
||||||
"AppDataLocationHealthCheckMessage": "No será posible actualizar para prevenir la eliminación de AppData al Actualizar",
|
"AppDataLocationHealthCheckMessage": "No será posible actualizar para evitar la eliminación de AppData al actualizar",
|
||||||
"Lists": "Listas",
|
"Lists": "Listas",
|
||||||
"SizeLimit": "Tamaño límite",
|
"SizeLimit": "Límite de tamaño",
|
||||||
"IndexerJackettAll": "Indexadores que utilizan el Endpoint Jackett 'all' no están soportados: {0}",
|
"IndexerJackettAll": "Indexadores que utilizan el Endpoint Jackett 'all' no están soportados: {0}",
|
||||||
"RemotePathMappingCheckLocalFolderMissing": "El cliente de descarga remota {0} coloca las descargas en {1} pero este directorio no parece existir. Probablemente falta o el mapeo de la ruta remota es incorrecto.",
|
"RemotePathMappingCheckLocalFolderMissing": "El cliente de descarga remota {0} coloca las descargas en {1} pero este directorio no parece existir. Probablemente falta o el mapeo de la ruta remota es incorrecto.",
|
||||||
"RemotePathMappingCheckLocalWrongOSPath": "El cliente de descarga local {0} coloca las descargas en {1} pero ésta no es una ruta válida {2}. Revise la configuración de su cliente de descarga.",
|
"RemotePathMappingCheckLocalWrongOSPath": "El cliente de descarga local {0} coloca las descargas en {1} pero ésta no es una ruta válida {2}. Revise la configuración de su cliente de descarga.",
|
||||||
@@ -498,7 +498,7 @@
|
|||||||
"FileWasDeletedByViaUI": "El archivo se eliminó a través de la interfaz de usuario",
|
"FileWasDeletedByViaUI": "El archivo se eliminó a través de la interfaz de usuario",
|
||||||
"AllowFingerprinting": "Permitir impresión digital",
|
"AllowFingerprinting": "Permitir impresión digital",
|
||||||
"DownloadClientsSettingsSummary": "Clientes de descarga, manejo de descarga y mapeo de rutas remotas",
|
"DownloadClientsSettingsSummary": "Clientes de descarga, manejo de descarga y mapeo de rutas remotas",
|
||||||
"OnHealthIssue": "En Problema de Salud",
|
"OnHealthIssue": "Al haber un problema de salud",
|
||||||
"DeleteBookFileMessageText": "¿Seguro que quieres eliminar {0}?",
|
"DeleteBookFileMessageText": "¿Seguro que quieres eliminar {0}?",
|
||||||
"HealthNoIssues": "No hay problemas con tu configuración",
|
"HealthNoIssues": "No hay problemas con tu configuración",
|
||||||
"ImportListStatusCheckAllClientMessage": "Las listas no están disponibles debido a errores",
|
"ImportListStatusCheckAllClientMessage": "Las listas no están disponibles debido a errores",
|
||||||
@@ -537,9 +537,9 @@
|
|||||||
"OnBookFileDeleteForUpgrade": "En archivo de película Eliminar para actualizar",
|
"OnBookFileDeleteForUpgrade": "En archivo de película Eliminar para actualizar",
|
||||||
"OnBookFileDeleteForUpgradeHelpText": "En archivo de película Eliminar para actualizar",
|
"OnBookFileDeleteForUpgradeHelpText": "En archivo de película Eliminar para actualizar",
|
||||||
"OnBookFileDeleteHelpText": "Al eliminar archivo de película",
|
"OnBookFileDeleteHelpText": "Al eliminar archivo de película",
|
||||||
"OnGrab": "Al Capturar",
|
"OnGrab": "Al capturar",
|
||||||
"OnRename": "Al Renombrar",
|
"OnRename": "Al renombrar",
|
||||||
"OnUpgrade": "Al Mejorar La Calidad",
|
"OnUpgrade": "Al actualizar",
|
||||||
"ProxyCheckBadRequestMessage": "Fallo al comprobar el proxy. StatusCode: {0}",
|
"ProxyCheckBadRequestMessage": "Fallo al comprobar el proxy. StatusCode: {0}",
|
||||||
"ProxyCheckFailedToTestMessage": "Fallo al comprobar el proxy: {0}",
|
"ProxyCheckFailedToTestMessage": "Fallo al comprobar el proxy: {0}",
|
||||||
"QualitySettingsSummary": "Tamaños de calidad y nombrado",
|
"QualitySettingsSummary": "Tamaños de calidad y nombrado",
|
||||||
@@ -574,10 +574,10 @@
|
|||||||
"WriteTagsNo": "Nunca",
|
"WriteTagsNo": "Nunca",
|
||||||
"FileWasDeletedByUpgrade": "Se eliminó el archivo para importar una actualización",
|
"FileWasDeletedByUpgrade": "Se eliminó el archivo para importar una actualización",
|
||||||
"IndexersSettingsSummary": "Indexadores y restricciones de lanzamiento",
|
"IndexersSettingsSummary": "Indexadores y restricciones de lanzamiento",
|
||||||
"RestartRequiredHelpTextWarning": "Requiere reiniciar para que surta efecto",
|
"RestartRequiredHelpTextWarning": "Requiere reiniciar para que tenga efecto",
|
||||||
"AddList": "Añadir Lista",
|
"AddList": "Añadir Lista",
|
||||||
"RenameFiles": "Renombrar Archivos",
|
"RenameFiles": "Renombrar archivos",
|
||||||
"Test": "Test",
|
"Test": "Prueba",
|
||||||
"InstanceName": "Nombre de la Instancia",
|
"InstanceName": "Nombre de la Instancia",
|
||||||
"InstanceNameHelpText": "Nombre de la instancia en la pestaña y para la aplicación Syslog",
|
"InstanceNameHelpText": "Nombre de la instancia en la pestaña y para la aplicación Syslog",
|
||||||
"Database": "Base de datos",
|
"Database": "Base de datos",
|
||||||
@@ -587,7 +587,7 @@
|
|||||||
"ClickToChangeReleaseGroup": "Clic para cambiar el grupo de lanzamiento",
|
"ClickToChangeReleaseGroup": "Clic para cambiar el grupo de lanzamiento",
|
||||||
"HardlinkCopyFiles": "Enlace permanente/Copiar archivos",
|
"HardlinkCopyFiles": "Enlace permanente/Copiar archivos",
|
||||||
"MoveFiles": "Mover archivos",
|
"MoveFiles": "Mover archivos",
|
||||||
"OnApplicationUpdate": "Al Actualizar La Aplicación",
|
"OnApplicationUpdate": "Al actualizar la aplicación",
|
||||||
"OnApplicationUpdateHelpText": "Al Actualizar La Aplicación",
|
"OnApplicationUpdateHelpText": "Al Actualizar La Aplicación",
|
||||||
"BypassIfHighestQuality": "Pasar sí es la calidad más alta",
|
"BypassIfHighestQuality": "Pasar sí es la calidad más alta",
|
||||||
"CustomFormatScore": "Puntuación de Formato personalizado",
|
"CustomFormatScore": "Puntuación de Formato personalizado",
|
||||||
@@ -602,17 +602,17 @@
|
|||||||
"IncludeCustomFormatWhenRenamingHelpText": "Incluir en el formato de renombrado {Formatos Propios}",
|
"IncludeCustomFormatWhenRenamingHelpText": "Incluir en el formato de renombrado {Formatos Propios}",
|
||||||
"MinFormatScoreHelpText": "Puntuación mínima del formato propio permitida para descargar",
|
"MinFormatScoreHelpText": "Puntuación mínima del formato propio permitida para descargar",
|
||||||
"NegateHelpText": "Si se activa, el formato propio no se aplicará si esta condición {0} se iguala.",
|
"NegateHelpText": "Si se activa, el formato propio no se aplicará si esta condición {0} se iguala.",
|
||||||
"ResetDefinitionTitlesHelpText": "Restablecer los títulos y valores de las definiciones",
|
"ResetDefinitionTitlesHelpText": "Restablecer títulos de definición también como valores",
|
||||||
"ResetDefinitions": "Restablecer definiciones",
|
"ResetDefinitions": "Restablecer definiciones",
|
||||||
"UnableToLoadCustomFormats": "No se pueden cargar los Formatos Propios",
|
"UnableToLoadCustomFormats": "No se pueden cargar los Formatos Propios",
|
||||||
"Theme": "Tema",
|
"Theme": "Tema",
|
||||||
"ThemeHelpText": "Cambia el tema de la interfaz de usuario de la aplicación. El tema \"automático\" utilizará el tema de tu sistema operativo para establecer el modo claro u oscuro. Inspirado por Theme.Park",
|
"ThemeHelpText": "Cambiar el tema de la interfaz de la aplicación, el tema 'Auto' usará el tema de tu sistema para establecer el modo luminoso u oscuro. Inspirado por Theme.Park",
|
||||||
"CustomFormatSettings": "Ajustes de Formatos Propios",
|
"CustomFormatSettings": "Ajustes de Formatos Propios",
|
||||||
"CutoffFormatScoreHelpText": "Una vez alcanzada esta puntuación del formato propio Radarr dejará de descargar películas",
|
"CutoffFormatScoreHelpText": "Una vez alcanzada esta puntuación del formato propio Radarr dejará de descargar películas",
|
||||||
"DeleteCustomFormatMessageText": "Seguro que quieres eliminar el indexer '{name}'?",
|
"DeleteCustomFormatMessageText": "Seguro que quieres eliminar el indexer '{name}'?",
|
||||||
"ExportCustomFormat": "Exportar formato personalizado",
|
"ExportCustomFormat": "Exportar formato personalizado",
|
||||||
"ResetTitles": "Restablecer títulos",
|
"ResetTitles": "Restablecer títulos",
|
||||||
"UpgradesAllowed": "Mejoras permitidas",
|
"UpgradesAllowed": "Actualizaciones permitidas",
|
||||||
"EnableRssHelpText": "Se utilizará cuando Radarr busque periódicamente publicaciones a través de RSS Sync",
|
"EnableRssHelpText": "Se utilizará cuando Radarr busque periódicamente publicaciones a través de RSS Sync",
|
||||||
"IndexerTagsHelpText": "Solo utilizar este indexador para películas que coincidan con al menos una etiqueta. Déjelo en blanco para utilizarlo con todas las películas.",
|
"IndexerTagsHelpText": "Solo utilizar este indexador para películas que coincidan con al menos una etiqueta. Déjelo en blanco para utilizarlo con todas las películas.",
|
||||||
"ImportListMissingRoot": "Falta la capeta raíz para las listas: {0}",
|
"ImportListMissingRoot": "Falta la capeta raíz para las listas: {0}",
|
||||||
@@ -620,32 +620,32 @@
|
|||||||
"IndexerDownloadClientHelpText": "Especifica qué cliente de descarga es usado para capturas desde este indexador",
|
"IndexerDownloadClientHelpText": "Especifica qué cliente de descarga es usado para capturas desde este indexador",
|
||||||
"HiddenClickToShow": "Oculto, click para mostrar",
|
"HiddenClickToShow": "Oculto, click para mostrar",
|
||||||
"HideAdvanced": "Ocultar avanzado",
|
"HideAdvanced": "Ocultar avanzado",
|
||||||
"ShowAdvanced": "Mostrar Avanzado",
|
"ShowAdvanced": "Mostrar avanzado",
|
||||||
"ShownClickToHide": "Mostrado, clic para ocultar",
|
"ShownClickToHide": "Mostrado, haz clic para ocultar",
|
||||||
"ReplaceWithDash": "Reemplazar con Dash",
|
"ReplaceWithDash": "Reemplazar con guion",
|
||||||
"ReplaceWithSpaceDash": "Reemplazar con Space Dash",
|
"ReplaceWithSpaceDash": "Reemplazar por barra espaciadora",
|
||||||
"ReplaceWithSpaceDashSpace": "Reemplazar con Space Dash Space",
|
"ReplaceWithSpaceDashSpace": "Reemplazar por espacio en la barra espaciadora",
|
||||||
"DeleteRemotePathMapping": "Borrar mapeo de ruta remota",
|
"DeleteRemotePathMapping": "Borrar mapeo de ruta remota",
|
||||||
"BlocklistReleases": "Lista de bloqueos de lanzamientos",
|
"BlocklistReleases": "Lista de bloqueos de lanzamientos",
|
||||||
"DeleteConditionMessageText": "Seguro que quieres eliminar la etiqueta '{0}'?",
|
"DeleteConditionMessageText": "Seguro que quieres eliminar la etiqueta '{0}'?",
|
||||||
"Negated": "Anulado",
|
"Negated": "Anulado",
|
||||||
"RemoveSelectedItem": "Eliminar el elemento seleccionado",
|
"RemoveSelectedItem": "Eliminar elemento seleccionado",
|
||||||
"RemoveSelectedItemBlocklistMessageText": "¿Está seguro de que desea eliminar los elementos seleccionados de la lista negra?",
|
"RemoveSelectedItemBlocklistMessageText": "¿Está seguro de que desea eliminar los elementos seleccionados de la lista negra?",
|
||||||
"RemoveSelectedItemQueueMessageText": "¿Está seguro de que desea eliminar el {0} elemento {1} de la cola?",
|
"RemoveSelectedItemQueueMessageText": "¿Estás seguro que quieres eliminar 1 elemento de la cola?",
|
||||||
"RemoveSelectedItems": "Eliminar los elementos seleccionados",
|
"RemoveSelectedItems": "Eliminar elementos seleccionados",
|
||||||
"RemoveSelectedItemsQueueMessageText": "¿Estás seguro de que quieres eliminar {0} elementos de la cola?",
|
"RemoveSelectedItemsQueueMessageText": "¿Estás seguro de que quieres eliminar {0} elementos de la cola?",
|
||||||
"Required": "Necesario",
|
"Required": "Solicitado",
|
||||||
"ResetQualityDefinitions": "Restablecer definiciones de calidad",
|
"ResetQualityDefinitions": "Restablecer definiciones de calidad",
|
||||||
"ResetQualityDefinitionsMessageText": "¿Está seguro de que desea restablecer las definiciones de calidad?",
|
"ResetQualityDefinitionsMessageText": "¿Estás seguro que quieres restablecer las definiciones de calidad?",
|
||||||
"BlocklistReleaseHelpText": "Evita que Radarr vuelva a capturar esta película automáticamente",
|
"BlocklistReleaseHelpText": "Evita que Radarr vuelva a capturar esta película automáticamente",
|
||||||
"NoEventsFound": "Ningún evento encontrado",
|
"NoEventsFound": "Ningún evento encontrado",
|
||||||
"ApplyTagsHelpTextHowToApplyAuthors": "Cómo añadir etiquetas a las películas seleccionadas",
|
"ApplyTagsHelpTextHowToApplyAuthors": "Cómo añadir etiquetas a las películas seleccionadas",
|
||||||
"DeleteSelectedIndexersMessageText": "¿Está seguro de querer eliminar {count} indexador(es) seleccionado(s)?",
|
"DeleteSelectedIndexersMessageText": "¿Está seguro de querer eliminar {count} indexador(es) seleccionado(s)?",
|
||||||
"Yes": "Sí",
|
"Yes": "Sí",
|
||||||
"RedownloadFailed": "La descarga ha fallado",
|
"RedownloadFailed": "La descarga ha fallado",
|
||||||
"RemoveCompleted": "Eliminación completada",
|
"RemoveCompleted": "Eliminar completado",
|
||||||
"RemoveDownloadsAlert": "Los ajustes de eliminación se han trasladado a los ajustes individuales del cliente de descarga en la tabla anterior.",
|
"RemoveDownloadsAlert": "Las opciones de Eliminar fueron movidas a las opciones del cliente de descarga individual en la table anterior.",
|
||||||
"RemoveFailed": "La eliminación falló",
|
"RemoveFailed": "Fallo al eliminar",
|
||||||
"ApplyTagsHelpTextAdd": "Añadir: Añade las etiquetas a la lista de etiquetas existente",
|
"ApplyTagsHelpTextAdd": "Añadir: Añade las etiquetas a la lista de etiquetas existente",
|
||||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Cómo añadir etiquetas a los clientes de descargas seleccionados",
|
"ApplyTagsHelpTextHowToApplyDownloadClients": "Cómo añadir etiquetas a los clientes de descargas seleccionados",
|
||||||
"ApplyTagsHelpTextHowToApplyImportLists": "Cómo añadir etiquetas a las listas de importación seleccionadas",
|
"ApplyTagsHelpTextHowToApplyImportLists": "Cómo añadir etiquetas a las listas de importación seleccionadas",
|
||||||
@@ -661,7 +661,7 @@
|
|||||||
"No": "No",
|
"No": "No",
|
||||||
"NoChange": "Sin cambio",
|
"NoChange": "Sin cambio",
|
||||||
"RemovingTag": "Eliminando etiqueta",
|
"RemovingTag": "Eliminando etiqueta",
|
||||||
"SetTags": "Poner Etiquetas",
|
"SetTags": "Establecer etiquetas",
|
||||||
"DeleteRemotePathMappingMessageText": "¿Está seguro de querer eliminar esta asignación de ruta remota?",
|
"DeleteRemotePathMappingMessageText": "¿Está seguro de querer eliminar esta asignación de ruta remota?",
|
||||||
"ApplicationURL": "URL de la aplicación",
|
"ApplicationURL": "URL de la aplicación",
|
||||||
"ApplicationUrlHelpText": "La URL externa de la aplicación incluyendo http(s)://, puerto y URL base",
|
"ApplicationUrlHelpText": "La URL externa de la aplicación incluyendo http(s)://, puerto y URL base",
|
||||||
@@ -699,8 +699,8 @@
|
|||||||
"ConnectionLostToBackend": "{appName} ha perdido su conexión con el backend y tendrá que ser recargado para recuperar su funcionalidad.",
|
"ConnectionLostToBackend": "{appName} ha perdido su conexión con el backend y tendrá que ser recargado para recuperar su funcionalidad.",
|
||||||
"NotificationStatusSingleClientHealthCheckMessage": "Listas no disponibles debido a errores: {0}",
|
"NotificationStatusSingleClientHealthCheckMessage": "Listas no disponibles debido a errores: {0}",
|
||||||
"NotificationStatusAllClientHealthCheckMessage": "Las notificaciones no están disponibles debido a fallos",
|
"NotificationStatusAllClientHealthCheckMessage": "Las notificaciones no están disponibles debido a fallos",
|
||||||
"ReleaseProfiles": "perfil de lanzamiento",
|
"ReleaseProfiles": "Perfiles de lanzamiento",
|
||||||
"Small": "Pequeña",
|
"Small": "Pequeño",
|
||||||
"DeleteImportList": "Eliminar Lista(s) de Importación",
|
"DeleteImportList": "Eliminar Lista(s) de Importación",
|
||||||
"Large": "Grande",
|
"Large": "Grande",
|
||||||
"Library": "Biblioteca",
|
"Library": "Biblioteca",
|
||||||
@@ -801,5 +801,49 @@
|
|||||||
"MediaManagementSettingsSummary": "Nombrado, opciones de gestión de archivos y carpetas raíz",
|
"MediaManagementSettingsSummary": "Nombrado, opciones de gestión de archivos y carpetas raíz",
|
||||||
"MonitoringOptions": "Opciones de monitorización",
|
"MonitoringOptions": "Opciones de monitorización",
|
||||||
"NoImportListsFound": "Ninguna lista de importación encontrada",
|
"NoImportListsFound": "Ninguna lista de importación encontrada",
|
||||||
"Monitoring": "Monitorizando"
|
"Monitoring": "Monitorizando",
|
||||||
|
"NoMissingItems": "No hay elementos faltantes",
|
||||||
|
"DefaultMetadataProfileIdHelpText": "Perfil de metadatos predeterminado para los artistas detectados en esta carpeta",
|
||||||
|
"MetadataProfileIdHelpText": "Los elementos de la lista del Perfil de Calidad se añadirán con",
|
||||||
|
"DefaultQualityProfileIdHelpText": "Perfil de calidad predeterminado para los artistas detectados en esta carpeta",
|
||||||
|
"ContinuingAllBooksDownloaded": "Continúa (Todas las pistas descargadas)",
|
||||||
|
"DataListMonitorAll": "Supervisar los artistas y todos los álbumes de cada artista incluido en la lista de importación",
|
||||||
|
"MetadataSettingsSummary": "Crea archivos de metadatos cuando los episodios son importados o las series son refrescadas",
|
||||||
|
"MonitoredAuthorIsUnmonitored": "El artista no está vigilado",
|
||||||
|
"SearchForAllCutoffUnmetBooks": "Buscar todos los episodios en Umbrales no alcanzados",
|
||||||
|
"ConsoleLogLevel": "Nivel de Registro de la Consola",
|
||||||
|
"DataMissingBooks": "Monitoriza episodios que no tienen archivos o que no se han emitido aún",
|
||||||
|
"EnabledHelpText": "Señalar para habilitar el perfil de lanzamiento",
|
||||||
|
"FilterAnalyticsEvents": "Filtrar Eventos Analíticos",
|
||||||
|
"FilterSentryEventsHelpText": "Filtrar eventos de error de usuario conocidos para que no se envíen como Análisis",
|
||||||
|
"RootFolderPathHelpText": "Los elementos de la lista de carpetas raíz se añadirán a",
|
||||||
|
"StatusEndedDeceased": "Fallecido",
|
||||||
|
"LogRotateHelpText": "Número máximo de archivos de registro que se guardan en la carpeta de registros",
|
||||||
|
"LogRotation": "Rotación de Registros",
|
||||||
|
"QualityProfileIdHelpText": "Los elementos de la lista del Perfil de Calidad se añadirán con",
|
||||||
|
"SelectDropdown": "Seleccionar...",
|
||||||
|
"CollapseMultipleBooksHelpText": "Colapsar varios álbumes que salen el mismo día",
|
||||||
|
"ContinuingNoAdditionalBooksAreExpected": "No se esperan álbumes adicionales",
|
||||||
|
"DefaultMonitorOptionHelpText": "Qué álbumes se deben supervisar en la adición inicial para los artistas detectados en esta carpeta",
|
||||||
|
"CustomFilter": "Filtros personalizados",
|
||||||
|
"LabelIsRequired": "Se requiere etiqueta",
|
||||||
|
"RemoveQueueItemConfirmation": "¿Estás seguro que quieres eliminar '{sourceTitle}' de la cola?",
|
||||||
|
"SelectQuality": "Seleccionar calidad",
|
||||||
|
"SelectReleaseGroup": "Seleccionar grupo de lanzamiento",
|
||||||
|
"ThereWasAnErrorLoadingThisItem": "Hubo un error cargando este elemento",
|
||||||
|
"ThereWasAnErrorLoadingThisPage": "Hubo un error cargando esta página",
|
||||||
|
"SourceTitle": "Título de la fuente",
|
||||||
|
"ShowBanners": "Mostrar banners",
|
||||||
|
"SearchMonitored": "Buscar monitorizados",
|
||||||
|
"Other": "Otro",
|
||||||
|
"RemoveCompletedDownloads": "Eliminar descargas completadas",
|
||||||
|
"RemoveFailedDownloads": "Eliminar descargas fallidas",
|
||||||
|
"SkipRedownload": "Saltar redescarga",
|
||||||
|
"SmartReplace": "Reemplazo inteligente",
|
||||||
|
"RemoveQueueItemRemovalMethod": "Método de eliminación",
|
||||||
|
"RemoveFromDownloadClientHint": "Elimina la descarga y archivo(s) del cliente de descarga",
|
||||||
|
"RemoveMultipleFromDownloadClientHint": "Elimina descargas y archivos del cliente de descarga",
|
||||||
|
"RemoveQueueItem": "Eliminar - {sourceTitle}",
|
||||||
|
"RemoveQueueItemRemovalMethodHelpTextWarning": "'Eliminar del cliente de descarga' eliminará la descarga y el archivo(s) del cliente de descarga.",
|
||||||
|
"RemoveQueueItemsRemovalMethodHelpTextWarning": "'Eliminar del cliente de descarga' eliminará las descargas y los archivos del cliente de descarga."
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user