mirror of
https://github.com/Radarr/Radarr.git
synced 2025-04-24 06:27:08 -04:00
Convert History to TypeScript
(cherry picked from commit 824ed0a36931ce7aae9aa544a7baf0738dae568c) Closes #10230 Closes #10390 Closes #10247
This commit is contained in:
parent
13f10906f1
commit
6747b74271
47 changed files with 923 additions and 1178 deletions
|
@ -1,354 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||
import Link from 'Components/Link/Link';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import formatAge from 'Utilities/Number/formatAge';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './HistoryDetails.css';
|
||||
|
||||
function HistoryDetails(props) {
|
||||
const {
|
||||
eventType,
|
||||
sourceTitle,
|
||||
data,
|
||||
downloadId,
|
||||
shortDateFormat,
|
||||
timeFormat
|
||||
} = props;
|
||||
|
||||
if (eventType === 'grabbed') {
|
||||
const {
|
||||
indexer,
|
||||
releaseGroup,
|
||||
movieMatchType,
|
||||
customFormatScore,
|
||||
nzbInfoUrl,
|
||||
downloadClient,
|
||||
downloadClientName,
|
||||
age,
|
||||
ageHours,
|
||||
ageMinutes,
|
||||
publishedDate
|
||||
} = data;
|
||||
|
||||
const downloadClientNameInfo = downloadClientName ?? downloadClient;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
indexer ?
|
||||
<DescriptionListItem
|
||||
title={translate('Indexer')}
|
||||
data={indexer}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
releaseGroup ?
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ReleaseGroup')}
|
||||
data={releaseGroup}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
customFormatScore && customFormatScore !== '0' ?
|
||||
<DescriptionListItem
|
||||
title={translate('CustomFormatScore')}
|
||||
data={formatCustomFormatScore(customFormatScore)}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
movieMatchType ?
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('MovieMatchType')}
|
||||
data={movieMatchType}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
nzbInfoUrl ?
|
||||
<span>
|
||||
<DescriptionListItemTitle>
|
||||
{translate('InfoUrl')}
|
||||
</DescriptionListItemTitle>
|
||||
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
</span> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
downloadClientNameInfo ?
|
||||
<DescriptionListItem
|
||||
title={translate('DownloadClient')}
|
||||
data={downloadClientNameInfo}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
downloadId ?
|
||||
<DescriptionListItem
|
||||
title={translate('GrabId')}
|
||||
data={downloadId}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
indexer ?
|
||||
<DescriptionListItem
|
||||
title={translate('AgeWhenGrabbed')}
|
||||
data={formatAge(age, ageHours, ageMinutes)}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
publishedDate ?
|
||||
<DescriptionListItem
|
||||
title={translate('PublishedDate')}
|
||||
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFailed') {
|
||||
const {
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
downloadId ?
|
||||
<DescriptionListItem
|
||||
title={translate('GrabId')}
|
||||
data={downloadId}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
message ?
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
data={message}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFolderImported') {
|
||||
const {
|
||||
customFormatScore,
|
||||
droppedPath,
|
||||
importedPath
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
droppedPath ?
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Source')}
|
||||
data={droppedPath}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
importedPath ?
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ImportedTo')}
|
||||
data={importedPath}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
customFormatScore && customFormatScore !== '0' ?
|
||||
<DescriptionListItem
|
||||
title={translate('CustomFormatScore')}
|
||||
data={formatCustomFormatScore(customFormatScore)}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileDeleted') {
|
||||
const {
|
||||
reason,
|
||||
customFormatScore
|
||||
} = data;
|
||||
|
||||
let reasonMessage = '';
|
||||
|
||||
switch (reason) {
|
||||
case 'Manual':
|
||||
reasonMessage = translate('DeletedReasonManual');
|
||||
break;
|
||||
case 'MissingFromDisk':
|
||||
reasonMessage = translate('DeletedReasonMissingFromDisk');
|
||||
break;
|
||||
case 'Upgrade':
|
||||
reasonMessage = translate('DeletedReasonUpgrade');
|
||||
break;
|
||||
default:
|
||||
reasonMessage = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('Reason')}
|
||||
data={reasonMessage}
|
||||
/>
|
||||
|
||||
{
|
||||
customFormatScore && customFormatScore !== '0' ?
|
||||
<DescriptionListItem
|
||||
title={translate('CustomFormatScore')}
|
||||
data={formatCustomFormatScore(customFormatScore)}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileRenamed') {
|
||||
const {
|
||||
sourcePath,
|
||||
sourceRelativePath,
|
||||
path,
|
||||
relativePath
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('SourcePath')}
|
||||
data={sourcePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('SourceRelativePath')}
|
||||
data={sourceRelativePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationPath')}
|
||||
data={path}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationRelativePath')}
|
||||
data={relativePath}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadIgnored') {
|
||||
const {
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
downloadId ?
|
||||
<DescriptionListItem
|
||||
title={translate('GrabId')}
|
||||
data={downloadId}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
message ?
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
data={message}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryDetails.propTypes = {
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
downloadId: PropTypes.string,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default HistoryDetails;
|
287
frontend/src/Activity/History/Details/HistoryDetails.tsx
Normal file
287
frontend/src/Activity/History/Details/HistoryDetails.tsx
Normal file
|
@ -0,0 +1,287 @@
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||
import Link from 'Components/Link/Link';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import {
|
||||
DownloadFailedHistory,
|
||||
DownloadFolderImportedHistory,
|
||||
DownloadIgnoredHistory,
|
||||
GrabbedHistoryData,
|
||||
HistoryData,
|
||||
HistoryEventType,
|
||||
MovieFileDeletedHistory,
|
||||
MovieFileRenamedHistory,
|
||||
} from 'typings/History';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import formatAge from 'Utilities/Number/formatAge';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './HistoryDetails.css';
|
||||
|
||||
interface HistoryDetailsProps {
|
||||
eventType: HistoryEventType;
|
||||
sourceTitle: string;
|
||||
data: HistoryData;
|
||||
downloadId?: string;
|
||||
}
|
||||
|
||||
function HistoryDetails(props: HistoryDetailsProps) {
|
||||
const { eventType, sourceTitle, data, downloadId } = props;
|
||||
|
||||
const { shortDateFormat, timeFormat } = useSelector(
|
||||
createUISettingsSelector()
|
||||
);
|
||||
|
||||
if (eventType === 'grabbed') {
|
||||
const {
|
||||
indexer,
|
||||
releaseGroup,
|
||||
movieMatchType,
|
||||
customFormatScore,
|
||||
nzbInfoUrl,
|
||||
downloadClient,
|
||||
downloadClientName,
|
||||
age,
|
||||
ageHours,
|
||||
ageMinutes,
|
||||
publishedDate,
|
||||
} = data as GrabbedHistoryData;
|
||||
|
||||
const downloadClientNameInfo = downloadClientName ?? downloadClient;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{indexer ? (
|
||||
<DescriptionListItem title={translate('Indexer')} data={indexer} />
|
||||
) : null}
|
||||
|
||||
{releaseGroup ? (
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ReleaseGroup')}
|
||||
data={releaseGroup}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{customFormatScore && customFormatScore !== '0' ? (
|
||||
<DescriptionListItem
|
||||
title={translate('CustomFormatScore')}
|
||||
data={formatCustomFormatScore(parseInt(customFormatScore))}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{movieMatchType ? (
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('MovieMatchType')}
|
||||
data={movieMatchType}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{nzbInfoUrl ? (
|
||||
<span>
|
||||
<DescriptionListItemTitle>
|
||||
{translate('InfoUrl')}
|
||||
</DescriptionListItemTitle>
|
||||
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{downloadClientNameInfo ? (
|
||||
<DescriptionListItem
|
||||
title={translate('DownloadClient')}
|
||||
data={downloadClientNameInfo}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{downloadId ? (
|
||||
<DescriptionListItem title={translate('GrabId')} data={downloadId} />
|
||||
) : null}
|
||||
|
||||
{age || ageHours || ageMinutes ? (
|
||||
<DescriptionListItem
|
||||
title={translate('AgeWhenGrabbed')}
|
||||
data={formatAge(age, ageHours, ageMinutes)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{publishedDate ? (
|
||||
<DescriptionListItem
|
||||
title={translate('PublishedDate')}
|
||||
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, {
|
||||
includeSeconds: true,
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFailed') {
|
||||
const { message } = data as DownloadFailedHistory;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{downloadId ? (
|
||||
<DescriptionListItem title={translate('GrabId')} data={downloadId} />
|
||||
) : null}
|
||||
|
||||
{message ? (
|
||||
<DescriptionListItem title={translate('Message')} data={message} />
|
||||
) : null}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFolderImported') {
|
||||
const { customFormatScore, droppedPath, importedPath } =
|
||||
data as DownloadFolderImportedHistory;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{droppedPath ? (
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Source')}
|
||||
data={droppedPath}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{importedPath ? (
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ImportedTo')}
|
||||
data={importedPath}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{customFormatScore && customFormatScore !== '0' ? (
|
||||
<DescriptionListItem
|
||||
title={translate('CustomFormatScore')}
|
||||
data={formatCustomFormatScore(parseInt(customFormatScore))}
|
||||
/>
|
||||
) : null}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileDeleted') {
|
||||
const { reason, customFormatScore } = data as MovieFileDeletedHistory;
|
||||
|
||||
let reasonMessage = '';
|
||||
|
||||
switch (reason) {
|
||||
case 'Manual':
|
||||
reasonMessage = translate('DeletedReasonManual');
|
||||
break;
|
||||
case 'MissingFromDisk':
|
||||
reasonMessage = translate('DeletedReasonMovieMissingFromDisk');
|
||||
break;
|
||||
case 'Upgrade':
|
||||
reasonMessage = translate('DeletedReasonUpgrade');
|
||||
break;
|
||||
default:
|
||||
reasonMessage = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem title={translate('Name')} data={sourceTitle} />
|
||||
|
||||
<DescriptionListItem title={translate('Reason')} data={reasonMessage} />
|
||||
|
||||
{customFormatScore && customFormatScore !== '0' ? (
|
||||
<DescriptionListItem
|
||||
title={translate('CustomFormatScore')}
|
||||
data={formatCustomFormatScore(parseInt(customFormatScore))}
|
||||
/>
|
||||
) : null}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileRenamed') {
|
||||
const { sourcePath, sourceRelativePath, path, relativePath } =
|
||||
data as MovieFileRenamedHistory;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('SourcePath')}
|
||||
data={sourcePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('SourceRelativePath')}
|
||||
data={sourceRelativePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem title={translate('DestinationPath')} data={path} />
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationRelativePath')}
|
||||
data={relativePath}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadIgnored') {
|
||||
const { message } = data as DownloadIgnoredHistory;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{downloadId ? (
|
||||
<DescriptionListItem title={translate('GrabId')} data={downloadId} />
|
||||
) : null}
|
||||
|
||||
{message ? (
|
||||
<DescriptionListItem title={translate('Message')} data={message} />
|
||||
) : null}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
export default HistoryDetails;
|
|
@ -1,18 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import HistoryDetails from './HistoryDetails';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createUISettingsSelector(),
|
||||
(uiSettings) => {
|
||||
return {
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(HistoryDetails);
|
|
@ -1,4 +1,3 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
|
@ -8,11 +7,12 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { HistoryData, HistoryEventType } from 'typings/History';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryDetails from './HistoryDetails';
|
||||
import styles from './HistoryDetailsModal.css';
|
||||
|
||||
function getHeaderTitle(eventType) {
|
||||
function getHeaderTitle(eventType: HistoryEventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return translate('Grabbed');
|
||||
|
@ -31,29 +31,33 @@ function getHeaderTitle(eventType) {
|
|||
}
|
||||
}
|
||||
|
||||
function HistoryDetailsModal(props) {
|
||||
interface HistoryDetailsModalProps {
|
||||
isOpen: boolean;
|
||||
eventType: HistoryEventType;
|
||||
sourceTitle: string;
|
||||
data: HistoryData;
|
||||
downloadId?: string;
|
||||
isMarkingAsFailed: boolean;
|
||||
onMarkAsFailedPress: () => void;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function HistoryDetailsModal(props: HistoryDetailsModalProps) {
|
||||
const {
|
||||
isOpen,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
data,
|
||||
downloadId,
|
||||
isMarkingAsFailed,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
isMarkingAsFailed = false,
|
||||
onMarkAsFailedPress,
|
||||
onModalClose
|
||||
onModalClose,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{getHeaderTitle(eventType)}
|
||||
</ModalHeader>
|
||||
<ModalHeader>{getHeaderTitle(eventType)}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<HistoryDetails
|
||||
|
@ -61,50 +65,26 @@ function HistoryDetailsModal(props) {
|
|||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
downloadId={downloadId}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{
|
||||
eventType === 'grabbed' &&
|
||||
<SpinnerButton
|
||||
className={styles.markAsFailedButton}
|
||||
kind={kinds.DANGER}
|
||||
isSpinning={isMarkingAsFailed}
|
||||
onPress={onMarkAsFailedPress}
|
||||
>
|
||||
{translate('MarkAsFailed')}
|
||||
</SpinnerButton>
|
||||
}
|
||||
{eventType === 'grabbed' && (
|
||||
<SpinnerButton
|
||||
className={styles.markAsFailedButton}
|
||||
kind={kinds.DANGER}
|
||||
isSpinning={isMarkingAsFailed}
|
||||
onPress={onMarkAsFailedPress}
|
||||
>
|
||||
{translate('MarkAsFailed')}
|
||||
</SpinnerButton>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
<Button onPress={onModalClose}>{translate('Close')}</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryDetailsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
downloadId: PropTypes.string,
|
||||
isMarkingAsFailed: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
HistoryDetailsModal.defaultProps = {
|
||||
isMarkingAsFailed: false
|
||||
};
|
||||
|
||||
export default HistoryDetailsModal;
|
|
@ -1,158 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryFilterModal from './HistoryFilterModal';
|
||||
import HistoryRowConnector from './HistoryRowConnector';
|
||||
|
||||
class History extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
isMoviesFetching,
|
||||
isMoviesPopulated,
|
||||
moviesError,
|
||||
items,
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
totalRecords,
|
||||
onFilterSelect,
|
||||
onFirstPagePress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const isFetchingAny = isFetching || isMoviesFetching;
|
||||
const isAllPopulated = isPopulated && (isMoviesPopulated || !items.length);
|
||||
const hasError = error || moviesError;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('History')}>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('Refresh')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isFetching}
|
||||
onPress={onFirstPagePress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={HistoryFilterModal}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
{
|
||||
isFetchingAny && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetchingAny && hasError &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('HistoryLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
// If history isPopulated and it's empty show no history found and don't
|
||||
// wait for the episodes to populate because they are never coming.
|
||||
|
||||
isPopulated && !hasError && !items.length &&
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoHistoryFound')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !!items.length &&
|
||||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<HistoryRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<TablePager
|
||||
totalRecords={totalRecords}
|
||||
isFetching={isFetching}
|
||||
onFirstPagePress={onFirstPagePress}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
History.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
isMoviesFetching: PropTypes.bool.isRequired,
|
||||
isMoviesPopulated: PropTypes.bool.isRequired,
|
||||
moviesError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onFirstPagePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default History;
|
216
frontend/src/Activity/History/History.tsx
Normal file
216
frontend/src/Activity/History/History.tsx
Normal file
|
@ -0,0 +1,216 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import usePaging from 'Components/Table/usePaging';
|
||||
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import createMoviesFetchingSelector from 'Movie/createMoviesFetchingSelector';
|
||||
import {
|
||||
clearHistory,
|
||||
fetchHistory,
|
||||
gotoHistoryPage,
|
||||
setHistoryFilter,
|
||||
setHistorySort,
|
||||
setHistoryTableOption,
|
||||
} from 'Store/Actions/historyActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import {
|
||||
registerPagePopulator,
|
||||
unregisterPagePopulator,
|
||||
} from 'Utilities/pagePopulator';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryFilterModal from './HistoryFilterModal';
|
||||
import HistoryRow from './HistoryRow';
|
||||
|
||||
function History() {
|
||||
const requestCurrentPage = useCurrentPage();
|
||||
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
totalRecords,
|
||||
} = useSelector((state: AppState) => state.history);
|
||||
|
||||
const { isMoviesFetching, isMoviesPopulated, moviesError } = useSelector(
|
||||
createMoviesFetchingSelector()
|
||||
);
|
||||
const customFilters = useSelector(createCustomFiltersSelector('history'));
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isFetchingAny = isFetching || isMoviesFetching;
|
||||
const isAllPopulated = isPopulated && (isMoviesPopulated || !items.length);
|
||||
const hasError = error || moviesError;
|
||||
|
||||
const {
|
||||
handleFirstPagePress,
|
||||
handlePreviousPagePress,
|
||||
handleNextPagePress,
|
||||
handleLastPagePress,
|
||||
handlePageSelect,
|
||||
} = usePaging({
|
||||
page,
|
||||
totalPages,
|
||||
gotoPage: gotoHistoryPage,
|
||||
});
|
||||
|
||||
const handleFilterSelect = useCallback(
|
||||
(selectedFilterKey: string) => {
|
||||
dispatch(setHistoryFilter({ selectedFilterKey }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleSortPress = useCallback(
|
||||
(sortKey: string) => {
|
||||
dispatch(setHistorySort({ sortKey }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleTableOptionChange = useCallback(
|
||||
(payload: TableOptionsChangePayload) => {
|
||||
dispatch(setHistoryTableOption(payload));
|
||||
|
||||
if (payload.pageSize) {
|
||||
dispatch(gotoHistoryPage({ page: 1 }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (requestCurrentPage) {
|
||||
dispatch(fetchHistory());
|
||||
} else {
|
||||
dispatch(gotoHistoryPage({ page: 1 }));
|
||||
}
|
||||
|
||||
return () => {
|
||||
dispatch(clearHistory());
|
||||
};
|
||||
}, [requestCurrentPage, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const repopulate = () => {
|
||||
dispatch(fetchHistory());
|
||||
};
|
||||
|
||||
registerPagePopulator(repopulate);
|
||||
|
||||
return () => {
|
||||
unregisterPagePopulator(repopulate);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<PageContent title={translate('History')}>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('Refresh')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isFetching}
|
||||
onPress={handleFirstPagePress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={HistoryFilterModal}
|
||||
onFilterSelect={handleFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
{isFetchingAny && !isAllPopulated ? <LoadingIndicator /> : null}
|
||||
|
||||
{!isFetchingAny && hasError ? (
|
||||
<Alert kind={kinds.DANGER}>{translate('HistoryLoadError')}</Alert>
|
||||
) : null}
|
||||
|
||||
{
|
||||
// If history isPopulated and it's empty show no history found and don't
|
||||
// wait for the movies to populate because they are never coming.
|
||||
|
||||
isPopulated && !hasError && !items.length ? (
|
||||
<Alert kind={kinds.INFO}>{translate('NoHistoryFound')}</Alert>
|
||||
) : null
|
||||
}
|
||||
|
||||
{isAllPopulated && !hasError && items.length ? (
|
||||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
onSortPress={handleSortPress}
|
||||
>
|
||||
<TableBody>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<HistoryRow key={item.id} columns={columns} {...item} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<TablePager
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
totalRecords={totalRecords}
|
||||
isFetching={isFetching}
|
||||
onFirstPagePress={handleFirstPagePress}
|
||||
onPreviousPagePress={handlePreviousPagePress}
|
||||
onNextPagePress={handleNextPagePress}
|
||||
onLastPagePress={handleLastPagePress}
|
||||
onPageSelect={handlePageSelect}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default History;
|
|
@ -1,141 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as historyActions from 'Store/Actions/historyActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import History from './History';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.history,
|
||||
(state) => state.movies,
|
||||
createCustomFiltersSelector('history'),
|
||||
(history, movies, customFilters) => {
|
||||
return {
|
||||
isMoviesFetching: movies.isFetching,
|
||||
isMoviesPopulated: movies.isPopulated,
|
||||
moviesError: movies.error,
|
||||
customFilters,
|
||||
...history
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
...historyActions
|
||||
};
|
||||
|
||||
class HistoryConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchHistory,
|
||||
gotoHistoryFirstPage
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchHistory();
|
||||
} else {
|
||||
gotoHistoryFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
this.props.clearHistory();
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
repopulate = () => {
|
||||
this.props.fetchHistory();
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onFirstPagePress = () => {
|
||||
this.props.gotoHistoryFirstPage();
|
||||
};
|
||||
|
||||
onPreviousPagePress = () => {
|
||||
this.props.gotoHistoryPreviousPage();
|
||||
};
|
||||
|
||||
onNextPagePress = () => {
|
||||
this.props.gotoHistoryNextPage();
|
||||
};
|
||||
|
||||
onLastPagePress = () => {
|
||||
this.props.gotoHistoryLastPage();
|
||||
};
|
||||
|
||||
onPageSelect = (page) => {
|
||||
this.props.gotoHistoryPage({ page });
|
||||
};
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setHistorySort({ sortKey });
|
||||
};
|
||||
|
||||
onFilterSelect = (selectedFilterKey) => {
|
||||
this.props.setHistoryFilter({ selectedFilterKey });
|
||||
};
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setHistoryTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoHistoryFirstPage();
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<History
|
||||
onFirstPagePress={this.onFirstPagePress}
|
||||
onPreviousPagePress={this.onPreviousPagePress}
|
||||
onNextPagePress={this.onNextPagePress}
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onSortPress={this.onSortPress}
|
||||
onFilterSelect={this.onFilterSelect}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchHistory: PropTypes.func.isRequired,
|
||||
gotoHistoryFirstPage: PropTypes.func.isRequired,
|
||||
gotoHistoryPreviousPage: PropTypes.func.isRequired,
|
||||
gotoHistoryNextPage: PropTypes.func.isRequired,
|
||||
gotoHistoryLastPage: PropTypes.func.isRequired,
|
||||
gotoHistoryPage: PropTypes.func.isRequired,
|
||||
setHistorySort: PropTypes.func.isRequired,
|
||||
setHistoryFilter: PropTypes.func.isRequired,
|
||||
setHistoryTableOption: PropTypes.func.isRequired,
|
||||
clearHistory: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withCurrentPage(
|
||||
connect(createMapStateToProps, mapDispatchToProps)(HistoryConnector)
|
||||
);
|
|
@ -1,12 +1,17 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import {
|
||||
GrabbedHistoryData,
|
||||
HistoryData,
|
||||
HistoryEventType,
|
||||
MovieFileDeletedHistory,
|
||||
} from 'typings/History';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './HistoryEventTypeCell.css';
|
||||
|
||||
function getIconName(eventType, data) {
|
||||
function getIconName(eventType: HistoryEventType, data: HistoryData) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return icons.DOWNLOADING;
|
||||
|
@ -17,7 +22,9 @@ function getIconName(eventType, data) {
|
|||
case 'downloadFailed':
|
||||
return icons.DOWNLOADING;
|
||||
case 'movieFileDeleted':
|
||||
return data.reason === 'MissingFromDisk' ? icons.FILE_MISSING : icons.DELETE;
|
||||
return (data as MovieFileDeletedHistory).reason === 'MissingFromDisk'
|
||||
? icons.FILE_MISSING
|
||||
: icons.DELETE;
|
||||
case 'movieFileRenamed':
|
||||
return icons.ORGANIZE;
|
||||
case 'downloadIgnored':
|
||||
|
@ -27,7 +34,7 @@ function getIconName(eventType, data) {
|
|||
}
|
||||
}
|
||||
|
||||
function getIconKind(eventType) {
|
||||
function getIconKind(eventType: HistoryEventType) {
|
||||
switch (eventType) {
|
||||
case 'downloadFailed':
|
||||
return kinds.DANGER;
|
||||
|
@ -36,52 +43,47 @@ function getIconKind(eventType) {
|
|||
}
|
||||
}
|
||||
|
||||
function getTooltip(eventType, data) {
|
||||
function getTooltip(eventType: HistoryEventType, data: HistoryData) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return translate('MovieGrabbedHistoryTooltip', { indexer: data.indexer, downloadClient: data.downloadClient });
|
||||
return translate('MovieGrabbedTooltip', {
|
||||
indexer: (data as GrabbedHistoryData).indexer,
|
||||
downloadClient: (data as GrabbedHistoryData).downloadClient,
|
||||
});
|
||||
case 'movieFolderImported':
|
||||
return translate('MovieFolderImportedTooltip');
|
||||
case 'downloadFolderImported':
|
||||
return translate('MovieImportedTooltip');
|
||||
case 'downloadFailed':
|
||||
return translate('MovieDownloadFailedTooltip');
|
||||
return translate('DownloadFailedMovieTooltip');
|
||||
case 'movieFileDeleted':
|
||||
return data.reason === 'MissingFromDisk' ? translate('MovieFileMissingTooltip') : translate('MovieFileDeletedTooltip');
|
||||
return (data as MovieFileDeletedHistory).reason === 'MissingFromDisk'
|
||||
? translate('MovieFileMissingTooltip')
|
||||
: translate('MovieFileDeletedTooltip');
|
||||
case 'movieFileRenamed':
|
||||
return translate('MovieFileRenamedTooltip');
|
||||
case 'downloadIgnored':
|
||||
return translate('MovieDownloadIgnoredTooltip');
|
||||
return translate('DownloadIgnoredMovieTooltip');
|
||||
default:
|
||||
return translate('UnknownEventTooltip');
|
||||
}
|
||||
}
|
||||
|
||||
function HistoryEventTypeCell({ eventType, data }) {
|
||||
interface HistoryEventTypeCellProps {
|
||||
eventType: HistoryEventType;
|
||||
data: HistoryData;
|
||||
}
|
||||
|
||||
function HistoryEventTypeCell({ eventType, data }: HistoryEventTypeCellProps) {
|
||||
const iconName = getIconName(eventType, data);
|
||||
const iconKind = getIconKind(eventType);
|
||||
const tooltip = getTooltip(eventType, data);
|
||||
|
||||
return (
|
||||
<TableRowCell
|
||||
className={styles.cell}
|
||||
title={tooltip}
|
||||
>
|
||||
<Icon
|
||||
name={iconName}
|
||||
kind={iconKind}
|
||||
/>
|
||||
<TableRowCell className={styles.cell} title={tooltip}>
|
||||
<Icon name={iconName} kind={iconKind} />
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryEventTypeCell.propTypes = {
|
||||
eventType: PropTypes.string.isRequired,
|
||||
data: PropTypes.object
|
||||
};
|
||||
|
||||
HistoryEventTypeCell.defaultProps = {
|
||||
data: {}
|
||||
};
|
||||
|
||||
export default HistoryEventTypeCell;
|
|
@ -1,277 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, tooltipPositions } from 'Helpers/Props';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguages from 'Movie/MovieLanguages';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||
import styles from './HistoryRow.css';
|
||||
|
||||
class HistoryRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isDetailsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
prevProps.isMarkingAsFailed &&
|
||||
!this.props.isMarkingAsFailed &&
|
||||
!this.props.markAsFailedError
|
||||
) {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onDetailsPress = () => {
|
||||
this.setState({ isDetailsModalOpen: true });
|
||||
};
|
||||
|
||||
onDetailsModalClose = () => {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
movie,
|
||||
quality,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
languages,
|
||||
qualityCutoffNotMet,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
date,
|
||||
data,
|
||||
downloadId,
|
||||
isMarkingAsFailed,
|
||||
columns,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
if (!movie) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'eventType') {
|
||||
return (
|
||||
<HistoryEventTypeCell
|
||||
key={name}
|
||||
eventType={eventType}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'movieMetadata.sortTitle') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieTitleLink
|
||||
titleSlug={movie.titleSlug}
|
||||
title={movie.title}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'languages') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieLanguages
|
||||
languages={languages}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'quality') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieQuality
|
||||
quality={quality}
|
||||
isCutoffMet={qualityCutoffNotMet}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'customFormats') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieFormats
|
||||
formats={customFormats}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'date') {
|
||||
return (
|
||||
<RelativeDateCell
|
||||
key={name}
|
||||
date={date}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'downloadClient') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.downloadClient}
|
||||
>
|
||||
{data.downloadClient}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'indexer') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.indexer}
|
||||
>
|
||||
{data.indexer}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'customFormatScore') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.customFormatScore}
|
||||
>
|
||||
<Tooltip
|
||||
anchor={formatCustomFormatScore(
|
||||
customFormatScore,
|
||||
customFormats.length
|
||||
)}
|
||||
tooltip={<MovieFormats formats={customFormats} />}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseGroup') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.releaseGroup}
|
||||
>
|
||||
{data.releaseGroup}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'sourceTitle') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
>
|
||||
{sourceTitle}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'details') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.details}
|
||||
>
|
||||
<div className={styles.actionContents}>
|
||||
<IconButton
|
||||
name={icons.INFO}
|
||||
onPress={this.onDetailsPress}
|
||||
/>
|
||||
</div>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
|
||||
<HistoryDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
downloadId={downloadId}
|
||||
isMarkingAsFailed={isMarkingAsFailed}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HistoryRow.propTypes = {
|
||||
movieId: PropTypes.number,
|
||||
movie: PropTypes.object.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
quality: PropTypes.object.isRequired,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||
customFormatScore: PropTypes.number.isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
date: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
downloadId: PropTypes.string,
|
||||
isMarkingAsFailed: PropTypes.bool,
|
||||
markAsFailedError: PropTypes.object,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
HistoryRow.defaultProps = {
|
||||
customFormats: []
|
||||
};
|
||||
|
||||
export default HistoryRow;
|
224
frontend/src/Activity/History/HistoryRow.tsx
Normal file
224
frontend/src/Activity/History/HistoryRow.tsx
Normal file
|
@ -0,0 +1,224 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import Column from 'Components/Table/Column';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { icons, tooltipPositions } from 'Helpers/Props';
|
||||
import Language from 'Language/Language';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguages from 'Movie/MovieLanguages';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import useMovie from 'Movie/useMovie';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import { HistoryData, HistoryEventType } from 'typings/History';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||
import styles from './HistoryRow.css';
|
||||
|
||||
interface HistoryRowProps {
|
||||
id: number;
|
||||
movieId: number;
|
||||
languages: Language[];
|
||||
quality: QualityModel;
|
||||
customFormats?: CustomFormat[];
|
||||
customFormatScore: number;
|
||||
qualityCutoffNotMet: boolean;
|
||||
eventType: HistoryEventType;
|
||||
sourceTitle: string;
|
||||
date: string;
|
||||
data: HistoryData;
|
||||
downloadId?: string;
|
||||
isMarkingAsFailed?: boolean;
|
||||
markAsFailedError?: object;
|
||||
columns: Column[];
|
||||
}
|
||||
|
||||
function HistoryRow(props: HistoryRowProps) {
|
||||
const {
|
||||
id,
|
||||
movieId,
|
||||
languages,
|
||||
quality,
|
||||
customFormats = [],
|
||||
customFormatScore,
|
||||
qualityCutoffNotMet,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
date,
|
||||
data,
|
||||
downloadId,
|
||||
isMarkingAsFailed = false,
|
||||
markAsFailedError,
|
||||
columns,
|
||||
} = props;
|
||||
|
||||
const wasMarkingAsFailed = usePrevious(isMarkingAsFailed);
|
||||
const dispatch = useDispatch();
|
||||
const movie = useMovie(movieId);
|
||||
|
||||
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
|
||||
|
||||
const handleDetailsPress = useCallback(() => {
|
||||
setIsDetailsModalOpen(true);
|
||||
}, [setIsDetailsModalOpen]);
|
||||
|
||||
const handleDetailsModalClose = useCallback(() => {
|
||||
setIsDetailsModalOpen(false);
|
||||
}, [setIsDetailsModalOpen]);
|
||||
|
||||
const handleMarkAsFailedPress = useCallback(() => {
|
||||
dispatch(markAsFailed({ id }));
|
||||
}, [id, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasMarkingAsFailed && !isMarkingAsFailed && !markAsFailedError) {
|
||||
setIsDetailsModalOpen(false);
|
||||
dispatch(fetchHistory());
|
||||
}
|
||||
}, [
|
||||
wasMarkingAsFailed,
|
||||
isMarkingAsFailed,
|
||||
markAsFailedError,
|
||||
setIsDetailsModalOpen,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
if (!movie) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{columns.map((column) => {
|
||||
const { name, isVisible } = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'eventType') {
|
||||
return (
|
||||
<HistoryEventTypeCell
|
||||
key={name}
|
||||
eventType={eventType}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'movieMetadata.sortTitle') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieTitleLink titleSlug={movie.titleSlug} title={movie.title} />
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'languages') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieLanguages languages={languages} />
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'quality') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieQuality
|
||||
quality={quality}
|
||||
isCutoffNotMet={qualityCutoffNotMet}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'customFormats') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieFormats formats={customFormats} />
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'date') {
|
||||
return <RelativeDateCell key={name} date={date} />;
|
||||
}
|
||||
|
||||
if (name === 'downloadClient') {
|
||||
return (
|
||||
<TableRowCell key={name} className={styles.downloadClient}>
|
||||
{'downloadClient' in data ? data.downloadClient : ''}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'indexer') {
|
||||
return (
|
||||
<TableRowCell key={name} className={styles.indexer}>
|
||||
{'indexer' in data ? data.indexer : ''}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'customFormatScore') {
|
||||
return (
|
||||
<TableRowCell key={name} className={styles.customFormatScore}>
|
||||
<Tooltip
|
||||
anchor={formatCustomFormatScore(
|
||||
customFormatScore,
|
||||
customFormats.length
|
||||
)}
|
||||
tooltip={<MovieFormats formats={customFormats} />}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseGroup') {
|
||||
return (
|
||||
<TableRowCell key={name} className={styles.releaseGroup}>
|
||||
{'releaseGroup' in data ? data.releaseGroup : ''}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'sourceTitle') {
|
||||
return <TableRowCell key={name}>{sourceTitle}</TableRowCell>;
|
||||
}
|
||||
|
||||
if (name === 'details') {
|
||||
return (
|
||||
<TableRowCell key={name} className={styles.details}>
|
||||
<IconButton name={icons.INFO} onPress={handleDetailsPress} />
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
|
||||
<HistoryDetailsModal
|
||||
isOpen={isDetailsModalOpen}
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
downloadId={downloadId}
|
||||
isMarkingAsFailed={isMarkingAsFailed}
|
||||
onMarkAsFailedPress={handleMarkAsFailedPress}
|
||||
onModalClose={handleDetailsModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export default HistoryRow;
|
|
@ -1,73 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
|
||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import HistoryRow from './HistoryRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createMovieSelector(),
|
||||
createUISettingsSelector(),
|
||||
(movie, uiSettings) => {
|
||||
return {
|
||||
movie,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchHistory,
|
||||
markAsFailed
|
||||
};
|
||||
|
||||
class HistoryRowConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
prevProps.isMarkingAsFailed &&
|
||||
!this.props.isMarkingAsFailed &&
|
||||
!this.props.markAsFailedError
|
||||
) {
|
||||
this.props.fetchHistory();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMarkAsFailedPress = () => {
|
||||
this.props.markAsFailed({ id: this.props.id });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<HistoryRow
|
||||
{...this.props}
|
||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HistoryRowConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
isMarkingAsFailed: PropTypes.bool,
|
||||
markAsFailedError: PropTypes.object,
|
||||
fetchHistory: PropTypes.func.isRequired,
|
||||
markAsFailed: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(HistoryRowConnector);
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import Blocklist from 'Activity/Blocklist/Blocklist';
|
||||
import HistoryConnector from 'Activity/History/HistoryConnector';
|
||||
import History from 'Activity/History/History';
|
||||
import Queue from 'Activity/Queue/Queue';
|
||||
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
||||
import ImportMovies from 'AddMovie/ImportMovie/ImportMovies';
|
||||
|
@ -79,7 +79,7 @@ function AppRoutes() {
|
|||
Activity
|
||||
*/}
|
||||
|
||||
<Route path="/activity/history" component={HistoryConnector} />
|
||||
<Route path="/activity/history" component={History} />
|
||||
|
||||
<Route path="/activity/queue" component={Queue} />
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
PagedAppSectionState,
|
||||
TableAppSectionState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import History from 'typings/History';
|
||||
|
||||
interface HistoryAppState
|
||||
extends AppSectionState<History>,
|
||||
AppSectionFilterState<History> {}
|
||||
AppSectionFilterState<History>,
|
||||
PagedAppSectionState,
|
||||
TableAppSectionState {}
|
||||
|
||||
export default HistoryAppState;
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
function selectUniqueIds(items, idProp) {
|
||||
const ids = _.reduce(items, (result, item) => {
|
||||
if (item[idProp]) {
|
||||
result.push(item[idProp]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
return _.uniq(ids);
|
||||
}
|
||||
|
||||
export default selectUniqueIds;
|
13
frontend/src/Utilities/Object/selectUniqueIds.ts
Normal file
13
frontend/src/Utilities/Object/selectUniqueIds.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import KeysMatching from 'typings/Helpers/KeysMatching';
|
||||
|
||||
function selectUniqueIds<T, K>(items: T[], idProp: KeysMatching<T, K>) {
|
||||
return items.reduce((acc: K[], item) => {
|
||||
if (item[idProp] && acc.indexOf(item[idProp] as K) === -1) {
|
||||
acc.push(item[idProp] as K);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export default selectUniqueIds;
|
|
@ -1,4 +1,4 @@
|
|||
type KeysMatching<T, V> = {
|
||||
export type KeysMatching<T, V> = {
|
||||
[K in keyof T]-?: T[K] extends V ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
|
|
|
@ -11,6 +11,61 @@ export type HistoryEventType =
|
|||
| 'movieFileRenamed'
|
||||
| 'downloadIgnored';
|
||||
|
||||
export interface GrabbedHistoryData {
|
||||
indexer: string;
|
||||
nzbInfoUrl: string;
|
||||
releaseGroup: string;
|
||||
age: string;
|
||||
ageHours: string;
|
||||
ageMinutes: string;
|
||||
publishedDate: string;
|
||||
downloadClient: string;
|
||||
downloadClientName: string;
|
||||
size: string;
|
||||
downloadUrl: string;
|
||||
guid: string;
|
||||
tmdbId: string;
|
||||
protocol: string;
|
||||
customFormatScore?: string;
|
||||
movieMatchType: string;
|
||||
releaseSource: string;
|
||||
indexerFlags: string;
|
||||
}
|
||||
|
||||
export interface DownloadFailedHistory {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface DownloadFolderImportedHistory {
|
||||
customFormatScore?: string;
|
||||
droppedPath: string;
|
||||
importedPath: string;
|
||||
}
|
||||
|
||||
export interface MovieFileDeletedHistory {
|
||||
customFormatScore?: string;
|
||||
reason: 'Manual' | 'MissingFromDisk' | 'Upgrade';
|
||||
}
|
||||
|
||||
export interface MovieFileRenamedHistory {
|
||||
sourcePath: string;
|
||||
sourceRelativePath: string;
|
||||
path: string;
|
||||
relativePath: string;
|
||||
}
|
||||
|
||||
export interface DownloadIgnoredHistory {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type HistoryData =
|
||||
| GrabbedHistoryData
|
||||
| DownloadFailedHistory
|
||||
| DownloadFolderImportedHistory
|
||||
| MovieFileDeletedHistory
|
||||
| MovieFileRenamedHistory
|
||||
| DownloadIgnoredHistory;
|
||||
|
||||
export default interface History {
|
||||
movieId: number;
|
||||
sourceTitle: string;
|
||||
|
@ -22,6 +77,6 @@ export default interface History {
|
|||
date: string;
|
||||
downloadId: string;
|
||||
eventType: HistoryEventType;
|
||||
data: unknown;
|
||||
data: HistoryData;
|
||||
id: number;
|
||||
}
|
||||
|
|
|
@ -1048,7 +1048,7 @@
|
|||
"DeleteConditionMessageText": "هل أنت متأكد من أنك تريد حذف ملف تعريف الجودة {0}",
|
||||
"AddAutoTagError": "غير قادر على إضافة قائمة جديدة ، يرجى المحاولة مرة أخرى.",
|
||||
"ConditionUsingRegularExpressions": "يتطابق هذا الشرط مع استخدام التعبيرات العادية. لاحظ أن الأحرف {0} لها معاني خاصة وتحتاج إلى الهروب بعلامة {1}",
|
||||
"DeletedReasonMissingFromDisk": "لم يتمكن Whisparr من العثور على الملف على القرص لذا تمت إزالته",
|
||||
"DeletedReasonMovieMissingFromDisk": "لم يتمكن {appName} من العثور على الملف على القرص لذا تمت إزالته",
|
||||
"MovieFileDeleted": "عند حذف ملف الفيلم",
|
||||
"DeleteCustomFormatMessageText": "هل أنت متأكد أنك تريد حذف العلامة \"{0}\"؟",
|
||||
"DeleteFormatMessageText": "هل أنت متأكد أنك تريد حذف العلامة \"{0}\"؟",
|
||||
|
|
|
@ -1046,7 +1046,7 @@
|
|||
"RemoveSelectedItemsQueueMessageText": "Наистина ли искате да премахнете {0} елемент {1} от опашката?",
|
||||
"ApplyTagsHelpTextHowToApplyMovies": "Как да приложите тагове към избраните филми",
|
||||
"MovieSearchResultsLoadError": "Не могат да се заредят резултати за това търсене на филм. Опитайте отново по-късно",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr не можа да намери файла на диска, така че той беше премахнат",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} не можа да намери файла на диска, така че той беше премахнат",
|
||||
"IMDbId": "Идентификатор на TMDb",
|
||||
"NotificationsSimplepushSettingsEvent": "Събития",
|
||||
"MovieIsNotMonitored": "Филмът не се следи",
|
||||
|
|
|
@ -1112,7 +1112,7 @@
|
|||
"ConnectionLostReconnect": "{appName} intentarà connectar-se automàticament, o podeu fer clic a recarregar.",
|
||||
"ConnectionLostToBackend": "{appName} ha perdut la connexió amb el backend i s'haurà de tornar a carregar per a restaurar la funcionalitat.",
|
||||
"DelayingDownloadUntil": "S'està retardant la baixada fins al {date} a les {time}",
|
||||
"DeletedReasonMissingFromDisk": "{appName} no ha pogut trobar el fitxer al disc, de manera que el fitxer es desenllaçarà de la pel·lícula a la base de dades",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} no ha pogut trobar el fitxer al disc, de manera que el fitxer es desenllaçarà de la pel·lícula a la base de dades",
|
||||
"DeletedReasonUpgrade": "S'ha suprimit el fitxer per a importar una versió millorada",
|
||||
"HistoryLoadError": "No es pot carregar l'historial",
|
||||
"MovieFileDeleted": "S'ha suprimit el fitxer de pel·lícula",
|
||||
|
@ -1200,7 +1200,7 @@
|
|||
"InteractiveSearchResultsFailedErrorMessage": "La cerca ha fallat per {message}. Actualitza la informació de la pel·lícula i verifica que hi hagi la informació necessària abans de tornar a cercar.",
|
||||
"LogFilesLocation": "Els fitxers de registre es troben a: {location}",
|
||||
"ManageDownloadClients": "Gestiona els clients de descàrrega",
|
||||
"MovieGrabbedHistoryTooltip": "Pel·lícula captura de {indexer} i enviada a {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Pel·lícula captura de {indexer} i enviada a {downloadClient}",
|
||||
"MovieFolderImportedTooltip": "Pel·lícula importada des de la carpeta de pel·lícules",
|
||||
"FullColorEvents": "Esdeveniments a tot color",
|
||||
"FullColorEventsHelpText": "Estil alterat per a pintar tot l'esdeveniment amb el color d'estat, en lloc de només la vora esquerra. No s'aplica a l'Agenda",
|
||||
|
@ -1240,8 +1240,8 @@
|
|||
"InfoUrl": "URL d'informació",
|
||||
"InvalidUILanguage": "La vostra IU està configurada en un idioma no vàlid, corregiu-lo i deseu la configuració",
|
||||
"ManageImportLists": "Gestiona les llistes d'importació",
|
||||
"MovieDownloadFailedTooltip": "La baixada de la pel·lícula ha fallat",
|
||||
"MovieDownloadIgnoredTooltip": "S'ha ignorat la pel·lícula baixada",
|
||||
"DownloadFailedMovieTooltip": "La baixada de la pel·lícula ha fallat",
|
||||
"DownloadIgnoredMovieTooltip": "S'ha ignorat la pel·lícula baixada",
|
||||
"MovieFileRenamed": "S'ha canviat el nom del fitxer de pel·lícula",
|
||||
"MovieImportedTooltip": "La pel·lícula s'ha baixat correctament i s'ha recollit del client de descàrrega",
|
||||
"EnableProfile": "Activa el perfil",
|
||||
|
|
|
@ -1090,7 +1090,7 @@
|
|||
"BlocklistReleaseHelpText": "Zabránit {appName} v opětovném sebrání tohoto vydání pomocí RSS nebo automatického vyhledávání",
|
||||
"CustomFormatJson": "Vlastní JSON formát",
|
||||
"DeleteQualityProfileMessageText": "Opravdu chcete smazat profil kvality '{name}'?",
|
||||
"DeletedReasonMissingFromDisk": "{appName}u se nepodařilo najít soubor na disku, proto byl soubor odpojen od filmu v databázi",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName}u se nepodařilo najít soubor na disku, proto byl soubor odpojen od filmu v databázi",
|
||||
"DeletedReasonUpgrade": "Soubor byl odstraněn pro import lepší verze",
|
||||
"DeleteSelectedMovieFilesHelpText": "Opravdu chcete smazat vybrané soubory filmů?",
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Klient stahování {downloadClientName} je nastaven na odstranění dokončených stahování. To může vést k tomu, že stahování budou z klienta odstraněna dříve, než je bude moci importovat {appName}.",
|
||||
|
|
|
@ -1067,7 +1067,7 @@
|
|||
"MovieIsNotMonitored": "Film overvåges",
|
||||
"DeleteReleaseProfile": "Slet forsinkelsesprofil",
|
||||
"DeleteReleaseProfileMessageText": "Er du sikker på, at du vil slette kvalitetsprofilen {0}",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr kunne ikke finde filen på disken, så den blev fjernet",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} kunne ikke finde filen på disken, så den blev fjernet",
|
||||
"ReleaseProfilesLoadError": "Kunne ikke indlæse forsinkelsesprofiler",
|
||||
"SearchOnAddCollectionHelpText": "Søg efter film på denne liste, når du føjes til {appName}",
|
||||
"EditConnectionImplementation": "Tilføj forbindelse - {implementationName}",
|
||||
|
|
|
@ -1443,7 +1443,7 @@
|
|||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
|
||||
"ListQualityProfileHelpText": "Qualitätsprofil mit dem Listemelemente hinzugefügt werden sollen",
|
||||
"DeletedReasonMissingFromDisk": "{appName} konnte die Datei auf der Festplatte nicht finden, daher wurde die Verknüpfung der Datei mit der Episode in der Datenbank aufgehoben",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} konnte die Datei auf der Festplatte nicht finden, daher wurde die Verknüpfung der Datei mit der Episode in der Datenbank aufgehoben",
|
||||
"ReleaseProfileTagMovieHelpText": "Veröffentlichungsprofile gelten für Künstler mit mindestens einem passenden Tag. Leer lassen, damit es für alle Künstler gilt",
|
||||
"SearchForAllMissingMoviesConfirmationCount": "Bist du dir sicher, dass du nach allen '{0}' fehlenden Alben suchen willst?",
|
||||
"SearchForCutoffUnmetMovies": "Suche nach allen abgeschnittenen unerfüllten Büchern",
|
||||
|
|
|
@ -1192,7 +1192,7 @@
|
|||
"ConditionUsingRegularExpressions": "Αυτή η συνθήκη ταιριάζει με τη χρήση τυπικών εκφράσεων. Λάβετε υπόψη ότι οι χαρακτήρες {0} έχουν ειδικές έννοιες και χρειάζονται διαφυγή με {1}",
|
||||
"DeleteSpecificationHelpText": "Είστε σίγουροι πως θέλετε να διαγράψετε τη συνθήκη '{name}';",
|
||||
"ApplyTagsHelpTextHowToApplyMovies": "Πώς να εφαρμόσετε ετικέτες στις επιλεγμένες ταινίες",
|
||||
"DeletedReasonMissingFromDisk": "Ο Whisparr δεν μπόρεσε να βρει το αρχείο στο δίσκο και έτσι καταργήθηκε",
|
||||
"DeletedReasonMovieMissingFromDisk": "Ο {appName} δεν μπόρεσε να βρει το αρχείο στο δίσκο και έτσι καταργήθηκε",
|
||||
"ReleaseProfileTagMovieHelpText": "Τα προφίλ κυκλοφορίας θα ισχύουν για καλλιτέχνες με τουλάχιστον μία αντίστοιχη ετικέτα. Αφήστε το κενό για να εφαρμοστεί σε όλους τους καλλιτέχνες",
|
||||
"DownloadClientSettingsRecentPriority": "Προτεραιότητα πελάτη",
|
||||
"MovieIsNotMonitored": "Η ταινία παρακολουθείται",
|
||||
|
|
|
@ -365,7 +365,7 @@
|
|||
"Deleted": "Deleted",
|
||||
"DeletedMovieDescription": "Movie was deleted from TMDb",
|
||||
"DeletedReasonManual": "File was deleted using {appName}, either manually or by another tool through the API",
|
||||
"DeletedReasonMissingFromDisk": "{appName} was unable to find the file on disk so the file was unlinked from the movie in the database",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} was unable to find the file on disk so the file was unlinked from the movie in the database",
|
||||
"DeletedReasonUpgrade": "File was deleted to import an upgrade",
|
||||
"Destination": "Destination",
|
||||
"DestinationPath": "Destination Path",
|
||||
|
@ -541,7 +541,9 @@
|
|||
"DownloadClientsLoadError": "Unable to load download clients",
|
||||
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
|
||||
"DownloadFailed": "Download failed",
|
||||
"DownloadFailedMovieTooltip": "Movie download failed",
|
||||
"DownloadIgnored": "Download Ignored",
|
||||
"DownloadIgnoredMovieTooltip": "Movie Download Ignored",
|
||||
"DownloadPropersAndRepacks": "Propers and Repacks",
|
||||
"DownloadPropersAndRepacksHelpText": "Whether or not to automatically upgrade to Propers/Repacks",
|
||||
"DownloadPropersAndRepacksHelpTextCustomFormat": "Use 'Do not Prefer' to sort by custom format score over Propers/Repacks",
|
||||
|
@ -962,8 +964,6 @@
|
|||
"MovieCollectionRootFolderMissingRootHealthCheckMessage": "Missing root folder for movie collection: {rootFolderInfo}",
|
||||
"MovieDetailsNextMovie": "Movie Details: Next Movie",
|
||||
"MovieDetailsPreviousMovie": "Movie Details: Previous Movie",
|
||||
"MovieDownloadFailedTooltip": "Movie download failed",
|
||||
"MovieDownloadIgnoredTooltip": "Movie Download Ignored",
|
||||
"MovieDownloaded": "Movie Downloaded",
|
||||
"MovieEditor": "Movie Editor",
|
||||
"MovieExcludedFromAutomaticAdd": "Movie Excluded From Automatic Add",
|
||||
|
@ -978,7 +978,7 @@
|
|||
"MovieFolderFormatHelpText": "Used when adding a new movie or moving movies via the movie editor",
|
||||
"MovieFolderImportedTooltip": "Movie imported from movie folder",
|
||||
"MovieFootNote": "Optionally control truncation to a maximum number of bytes including ellipsis (`...`). Truncating from the end (e.g. `{Movie Title:30}`) or the beginning (e.g. `{Movie Title:-30}`) are both supported.",
|
||||
"MovieGrabbedHistoryTooltip": "Movie grabbed from {indexer} and sent to {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Movie grabbed from {indexer} and sent to {downloadClient}",
|
||||
"MovieID": "Movie ID",
|
||||
"MovieImported": "Movie Imported",
|
||||
"MovieImportedTooltip": "Movie downloaded successfully and picked up from download client",
|
||||
|
|
|
@ -1157,7 +1157,7 @@
|
|||
"DeleteSelectedIndexersMessageText": "¿Estás seguro que quieres eliminar {count} indexador(es) seleccionado(s)?",
|
||||
"DisabledForLocalAddresses": "Deshabilitada para direcciones locales",
|
||||
"DeletedReasonManual": "El archivo fue eliminado usando {appName}, o bien manualmente o por otra herramienta a través de la API",
|
||||
"DeletedReasonMissingFromDisk": "{appName} no ha podido encontrar el archivo en el disco, por lo que se ha desvinculado de la película en la base de datos",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} no ha podido encontrar el archivo en el disco, por lo que se ha desvinculado de la película en la base de datos",
|
||||
"DeletedReasonUpgrade": "Se ha borrado el archivo para importar una versión mejorada",
|
||||
"DeleteSelectedMovieFilesHelpText": "¿Está seguro de que desea eliminar los archivos de película seleccionados?",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirma la nueva contraseña",
|
||||
|
@ -1222,13 +1222,13 @@
|
|||
"MovieFileRenamedTooltip": "Archivo de película renombrado",
|
||||
"MovieFolderImportedTooltip": "Película importada de la carpeta de películas",
|
||||
"InteractiveSearchModalHeader": "Búsqueda interactiva",
|
||||
"MovieDownloadIgnoredTooltip": "Descarga de la película ignorada",
|
||||
"MovieDownloadFailedTooltip": "No se ha podido descargar la película",
|
||||
"DownloadIgnoredMovieTooltip": "Descarga de la película ignorada",
|
||||
"DownloadFailedMovieTooltip": "No se ha podido descargar la película",
|
||||
"MovieFileDeletedTooltip": "Archivo de película eliminado",
|
||||
"MovieFileRenamed": "Archivo de película renombrado",
|
||||
"LanguagesLoadError": "No es posible cargar los idiomas",
|
||||
"DownloadClientQbittorrentSettingsContentLayout": "Diseño del contenido",
|
||||
"MovieGrabbedHistoryTooltip": "Película capturada de {indexer} y enviada a {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Película capturada de {indexer} y enviada a {downloadClient}",
|
||||
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Si usar el diseño de contenido configurado de qBittorrent, el diseño original del torrent o siempre crear una subcarpeta (qBittorrent 4.3.2+)",
|
||||
"MovieImported": "Película importada",
|
||||
"MovieImportedTooltip": "Película descargada correctamente y obtenida del cliente de descargas",
|
||||
|
|
|
@ -1216,7 +1216,7 @@
|
|||
"DeletedReasonManual": "Tiedosto poistettiin käyttöliittymän kautta",
|
||||
"DeletedReasonUpgrade": "Tiedosto poistettiin päivitetyn version tuomiseksi",
|
||||
"InteractiveImportNoMovie": "Elokuva on valittava jokaiselle valitulle tiedostolle.",
|
||||
"MovieGrabbedHistoryTooltip": "Elokuva kaapattiin lähteestä {indexer} ja välitettiin lataajalle {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Elokuva kaapattiin lähteestä {indexer} ja välitettiin lataajalle {downloadClient}",
|
||||
"CloneCondition": "Monista ehto",
|
||||
"AutomaticAdd": "Automaattinen lisäys",
|
||||
"AutoTagging": "Automaattinen tunnistemerkintä",
|
||||
|
@ -1245,7 +1245,7 @@
|
|||
"InteractiveImportNoFilesFound": "Valitusta kansiosta ei löytynyt videotiedostoja.",
|
||||
"ManualGrab": "Manuaalinen kaappaus",
|
||||
"Complete": "Kokonaiset",
|
||||
"DeletedReasonMissingFromDisk": "{appName} ei löytänyt tiedostoa levyltä, joten sen kytkös kirjaston elokuvaan poistettiin.",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} ei löytänyt tiedostoa levyltä, joten sen kytkös kirjaston elokuvaan poistettiin.",
|
||||
"ShowImdbRatingHelpText": "Näytä IMDb-arvio julisteen alla.",
|
||||
"ShowRottenTomatoesRating": "Näytä Tomato-arvio",
|
||||
"ShowRottenTomatoesRatingHelpText": "Näytä Tomato-arvio julisteen alla.",
|
||||
|
@ -1415,7 +1415,7 @@
|
|||
"CustomFormatsSpecificationRegularExpression": "Säännöllinen lauseke",
|
||||
"False": "Epätosi",
|
||||
"CustomFormatsSpecificationRegularExpressionHelpText": "Mukautetun muodon säännöllisen lausekkeen kirjainkokoa ei huomioida.",
|
||||
"MovieDownloadIgnoredTooltip": "Elokuvalataus ohitettiin",
|
||||
"DownloadIgnoredMovieTooltip": "Elokuvalataus ohitettiin",
|
||||
"MovieFolderImportedTooltip": "Elokuva tuotiin elokuvakansiosta",
|
||||
"True": "Tosi",
|
||||
"SkipRedownloadHelpText": "Estää {appName}ia lataamasta kohteelle vaihtoehtoista julkaisua.",
|
||||
|
@ -1460,7 +1460,7 @@
|
|||
"NotificationsValidationUnableToConnectToService": "Palvelua {serviceName} ei tavoiteta.",
|
||||
"NotificationsValidationUnableToSendTestMessage": "Testiviestin lähetys ei onnistu: {exceptionMessage}",
|
||||
"NotificationsEmailSettingsUseEncryptionHelpText": "Määrittää suositaanko salausta, jos se on määritetty palvelimelle, käytetäänkö aina SSL- (vain portti 465) tai StartTLS-salausta (kaikki muut portit), voi käytetäänkö salausta lainkaan.",
|
||||
"MovieDownloadFailedTooltip": "Elokuvan lataus epäonnistui",
|
||||
"DownloadFailedMovieTooltip": "Elokuvan lataus epäonnistui",
|
||||
"MovieFileDeleted": "Elokuvatiedosto poistettiin",
|
||||
"MovieFileDeletedTooltip": "Elokuvatiedosto poistettiin",
|
||||
"ThereWasAnErrorLoadingThisItem": "Virhe ladattaessa kohdetta",
|
||||
|
|
|
@ -1216,7 +1216,7 @@
|
|||
"AutoTaggingLoadError": "Impossible de charger le marquage automatique",
|
||||
"DelayingDownloadUntil": "Retarder le téléchargement jusqu'au {date} à {time}",
|
||||
"DeleteSelectedMovieFilesHelpText": "Voulez-vous vraiment supprimer les fichiers vidéo sélectionnés ?",
|
||||
"DeletedReasonMissingFromDisk": "{appName} n'a pas pu trouver le fichier sur le disque, il a donc été supprimé dans la base de données",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} n'a pas pu trouver le fichier sur le disque, il a donc été supprimé dans la base de données",
|
||||
"DeletedReasonUpgrade": "Le fichier a été supprimé pour importer une mise à niveau",
|
||||
"OrganizeLoadError": "Erreur lors du chargement des aperçus",
|
||||
"EditImportListImplementation": "Modifier la liste d'importation - {implementationName}",
|
||||
|
@ -1285,7 +1285,7 @@
|
|||
"MovieFileRenamed": "Fichier vidéo renommé",
|
||||
"MovieFileRenamedTooltip": "Fichier vidéo renommé",
|
||||
"MovieFolderImportedTooltip": "Film importé du dossier de film",
|
||||
"MovieGrabbedHistoryTooltip": "Film récupéré de {indexer} et envoyé à {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Film récupéré de {indexer} et envoyé à {downloadClient}",
|
||||
"RemoveQueueItem": "Retirer - {sourceTitle}",
|
||||
"OverrideGrabModalTitle": "Remplacer et récupérer - {title}",
|
||||
"RemoveTagsAutomaticallyHelpText": "Supprimez automatiquement les étiquettes si les conditions ne sont pas remplies",
|
||||
|
@ -1296,8 +1296,8 @@
|
|||
"SetReleaseGroupModalTitle": "{modalTitle} – Définir le groupe de versions",
|
||||
"CountImportListsSelected": "{count} liste(s) d'importation sélectionnée(s)",
|
||||
"InteractiveSearchResultsFailedErrorMessage": "La recherche a échoué car il s'agit d'un {message}. Essayez d'actualiser les informations sur le film et vérifiez que les informations nécessaires sont présentes avant de lancer une nouvelle recherche.",
|
||||
"MovieDownloadFailedTooltip": "Le téléchargement du film a échoué",
|
||||
"MovieDownloadIgnoredTooltip": "Téléchargement de film ignoré",
|
||||
"DownloadFailedMovieTooltip": "Le téléchargement du film a échoué",
|
||||
"DownloadIgnoredMovieTooltip": "Téléchargement de film ignoré",
|
||||
"SkipRedownloadHelpText": "Empêche {appName} d'essayer de télécharger une version alternative pour cet élément",
|
||||
"QueueFilterHasNoItems": "Le filtre de file d'attente sélectionné ne contient aucun élément",
|
||||
"EnableProfile": "Activer profil",
|
||||
|
|
|
@ -1100,7 +1100,7 @@
|
|||
"NotificationsSimplepushSettingsEvent": "אירועים",
|
||||
"RemotePathMappingCheckFilesLocalWrongOSPath": "אתה משתמש בדוקר; קליינט ההורדות {downloadClientName} שם הורדות ב-{path} אבל הנתיב לא תקין {osName}. בחן מחדש את ניתוב התיקיות והגדרות קליינט ההורדות.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "אתה משתמש בדוקר; קליינט ההורדות {downloadClientName} שם הורדות ב-{path} אבל הנתיב לא תקין {osName}. בחן מחדש את ניתוב התיקיות והגדרות קליינט ההורדות.",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr לא הצליח למצוא את הקובץ בדיסק ולכן הוא הוסר",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} לא הצליח למצוא את הקובץ בדיסק ולכן הוא הוסר",
|
||||
"AddDelayProfileError": "לא ניתן להוסיף פרופיל איכות חדש, נסה שוב.",
|
||||
"MovieFileDeletedTooltip": "במחיקת קובץ הסרט",
|
||||
"DeleteMovieFolders": "מחק את תיקיית הסרטים",
|
||||
|
|
|
@ -1047,7 +1047,7 @@
|
|||
"MovieIsNotMonitored": "मूवी अनमनी है",
|
||||
"DownloadClientSettingsRecentPriority": "ग्राहक प्राथमिकता",
|
||||
"DeleteSelectedMovieFilesHelpText": "क्या आप वाकई चयनित मूवी फ़ाइलों को हटाना चाहते हैं?",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr डिस्क पर फ़ाइल खोजने में असमर्थ था इसलिए इसे हटा दिया गया था",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} डिस्क पर फ़ाइल खोजने में असमर्थ था इसलिए इसे हटा दिया गया था",
|
||||
"MovieFileDeleted": "मूवी फ़ाइल डिलीट पर",
|
||||
"DeleteCustomFormatMessageText": "क्या आप वाकई '{0}' टैग हटाना चाहते हैं?",
|
||||
"DeleteFormatMessageText": "क्या आप वाकई '{0}' टैग हटाना चाहते हैं?",
|
||||
|
|
|
@ -1151,7 +1151,7 @@
|
|||
"DeleteAutoTag": "Automatikus címke törlése",
|
||||
"DeleteSelectedDownloadClients": "Letöltési kliens(ek) törlése",
|
||||
"DeleteSelectedIndexersMessageText": "Biztosan törölni szeretne {count} kiválasztott indexelőt?",
|
||||
"DeletedReasonMissingFromDisk": "A(z) {appName} nem találta a fájlt a lemezen, ezért a fájlt leválasztották a filmről az adatbázisban",
|
||||
"DeletedReasonMovieMissingFromDisk": "A(z) {appName} nem találta a fájlt a lemezen, ezért a fájlt leválasztották a filmről az adatbázisban",
|
||||
"EditIndexerImplementation": "Indexelő szerkesztése – {implementationName}",
|
||||
"FailedToFetchUpdates": "Nem sikerült lekérni a frissítéseket",
|
||||
"FormatAgeDay": "nap",
|
||||
|
@ -1418,7 +1418,7 @@
|
|||
"DownloadClientSettingsOlderPriorityMovieHelpText": "Elsőbbség a 14 nappal ezelőtt sugárzott epizódok megragadásánál",
|
||||
"MovieImportedTooltip": "Az epizód letöltése sikeresen megtörtént, és a letöltés kliensből lett letöltve",
|
||||
"SearchForCutoffUnmetMoviesConfirmationCount": "Biztosan megkeresi az összes {totalRecords} hiányzó epizódot?",
|
||||
"MovieGrabbedHistoryTooltip": "Az epizódot letöltötte a(z) {indexer} és elküldte a(z) {downloadClient} számára",
|
||||
"MovieGrabbedTooltip": "Az epizódot letöltötte a(z) {indexer} és elküldte a(z) {downloadClient} számára",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Választható hely a letöltések elhelyezéséhez, hagyja üresen az alapértelmezett Aria2 hely használatához",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Választható hely a letöltések elhelyezéséhez, hagyja üresen az alapértelmezett Aria2 hely használatához",
|
||||
"MovieFileDeletedTooltip": "A filmfájl törléséhez",
|
||||
|
|
|
@ -1055,7 +1055,7 @@
|
|||
"NotificationsSimplepushSettingsEvent": "Viðburðir",
|
||||
"DeleteSelectedMovieFilesHelpText": "Ertu viss um að þú viljir eyða völdum kvikmyndaskrám?",
|
||||
"AutoRedownloadFailed": "Niðurhal mistókst",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr gat ekki fundið skrána á disknum svo hún var fjarlægð",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} gat ekki fundið skrána á disknum svo hún var fjarlægð",
|
||||
"DownloadClientSettingsRecentPriority": "Forgangur viðskiptavinar",
|
||||
"AddDelayProfileError": "Ekki er hægt að bæta við nýjum gæðaprófíl, reyndu aftur.",
|
||||
"MovieFileDeletedTooltip": "Á Eyða kvikmyndaskrá",
|
||||
|
|
|
@ -1162,7 +1162,7 @@
|
|||
"ReleaseProfilesLoadError": "Non riesco a caricare i profili di ritardo",
|
||||
"ReleaseGroups": "Gruppo Release",
|
||||
"DeleteReleaseProfileMessageText": "Sicuro di voler cancellare il profilo di qualità {0}",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr non è riuscito a trovare il file sul disco, quindi è stato rimosso",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} non è riuscito a trovare il file sul disco, quindi è stato rimosso",
|
||||
"DeleteQualityProfileMessageText": "Sicuro di voler cancellare il profilo di qualità '{name}'?",
|
||||
"RemotePathMappingCheckFilesLocalWrongOSPath": "Stai utilizzando docker; Il client di download {downloadClientName} riporta files in {path} ma questo non è un percorso valido {osName}. Controlla la mappa dei percorsi remoti e le impostazioni del client di download.",
|
||||
"AutoTaggingNegateHelpText": "Se selezionato, il formato personalizzato non verrà applicato a questa {0} condizione.",
|
||||
|
@ -1231,7 +1231,7 @@
|
|||
"ManageIndexers": "Gestisci Indicizzatori",
|
||||
"ManageLists": "Gestisci Liste",
|
||||
"Menu": "Menu",
|
||||
"MovieDownloadFailedTooltip": "Download del film fallito",
|
||||
"DownloadFailedMovieTooltip": "Download del film fallito",
|
||||
"Never": "Mai",
|
||||
"NotificationsSettingsWebhookMethod": "Metodo",
|
||||
"NotificationsTraktSettingsAuthenticateWithTrakt": "Autentica con Trakt",
|
||||
|
@ -1284,7 +1284,7 @@
|
|||
"Letterboxd": "Letterboxd",
|
||||
"MissingLoadError": "Errore caricando elementi mancanti",
|
||||
"MonitorCollection": "Monitora Collezione",
|
||||
"MovieDownloadIgnoredTooltip": "Download del Film Ignorato",
|
||||
"DownloadIgnoredMovieTooltip": "Download del Film Ignorato",
|
||||
"MovieIsNotAvailable": "Film non disponibile",
|
||||
"MovieIsPopular": "Film Popolare su TMDb",
|
||||
"MovieIsTrending": "Film in Tendenza su TMDb",
|
||||
|
|
|
@ -1049,7 +1049,7 @@
|
|||
"MovieFileDeleted": "ムービーファイルの削除について",
|
||||
"SearchOnAddCollectionHelpText": "{appName}に追加されたら、このリストで映画を検索します",
|
||||
"MovieIsNotMonitored": "映画は監視されていません",
|
||||
"DeletedReasonMissingFromDisk": "Whisparrはディスク上でファイルを見つけることができなかったため、削除されました",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName}はディスク上でファイルを見つけることができなかったため、削除されました",
|
||||
"IMDbId": "TMDbID",
|
||||
"DeleteSelectedMovieFilesHelpText": "選択したムービーファイルを削除してもよろしいですか?",
|
||||
"NotificationsSimplepushSettingsEvent": "イベント",
|
||||
|
|
|
@ -1039,7 +1039,7 @@
|
|||
"RemoveSelectedBlocklistMessageText": "블랙리스트에서 선택한 항목을 제거 하시겠습니까?",
|
||||
"Yes": "예",
|
||||
"ApplyTagsHelpTextHowToApplyMovies": "선택한 동영상에 태그를 적용하는 방법",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr가 디스크에서 파일을 찾을 수 없어 제거되었습니다.",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName}가 디스크에서 파일을 찾을 수 없어 제거되었습니다.",
|
||||
"ShowUnknownMovieItemsHelpText": "대기열에 영화가없는 항목을 표시합니다. 여기에는 제거 된 영화 또는 {appName} 카테고리의 다른 항목이 포함될 수 있습니다.",
|
||||
"DeleteSelectedMovieFilesHelpText": "선택한 동영상 파일을 삭제 하시겠습니까?",
|
||||
"DownloadClientSettingsRecentPriority": "클라이언트 우선 순위",
|
||||
|
|
|
@ -1185,7 +1185,7 @@
|
|||
"FormatAgeMinute": "minuten",
|
||||
"EditConnectionImplementation": "Voeg connectie toe - {implementationName}",
|
||||
"FormatAgeHour": "Uren",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr kon het bestand niet vinden op de schijf dus werd het verwijderd",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} kon het bestand niet vinden op de schijf dus werd het verwijderd",
|
||||
"DeleteReleaseProfile": "Verwijder Vertragingsprofiel",
|
||||
"DeleteReleaseProfileMessageText": "Bent u zeker dat u het kwaliteitsprofiel {0} wilt verwijderen",
|
||||
"DownloadClientSettingsRecentPriority": "Client Prioriteit",
|
||||
|
|
|
@ -1151,7 +1151,7 @@
|
|||
"MovieSearchResultsLoadError": "Nie można załadować wyników dla tego wyszukiwania filmów. Spróbuj ponownie później",
|
||||
"ShowUnknownMovieItemsHelpText": "Pokaż elementy bez filmu w kolejce. Może to obejmować usunięte filmy lub cokolwiek innego w kategorii Lidarr",
|
||||
"EditConnectionImplementation": "Dodaj Connection - {implementationName}",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr nie mógł znaleźć pliku na dysku, więc został usunięty",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} nie mógł znaleźć pliku na dysku, więc został usunięty",
|
||||
"DeleteSelectedMovieFilesHelpText": "Czy na pewno chcesz usunąć wybrane pliki filmowe?",
|
||||
"IMDbId": "Identyfikator TMDb",
|
||||
"AddDelayProfileError": "Nie można dodać nowego profilu opóźnienia, spróbuj później.",
|
||||
|
|
|
@ -1135,7 +1135,7 @@
|
|||
"DeleteSelectedImportListsMessageText": "Tem a certeza de que pretende eliminar a(s) lista(s) de {count} importação selecionada(s)?",
|
||||
"DeleteSelectedMovieFilesHelpText": "Tem a certeza de que pretende apagar os ficheiros de filmes seleccionados?",
|
||||
"DeletedReasonManual": "O ficheiro foi eliminado através da IU",
|
||||
"DeletedReasonMissingFromDisk": "O {appName} não conseguiu encontrar o ficheiro no disco, pelo que o ficheiro foi desvinculado do filme na base de dados",
|
||||
"DeletedReasonMovieMissingFromDisk": "O {appName} não conseguiu encontrar o ficheiro no disco, pelo que o ficheiro foi desvinculado do filme na base de dados",
|
||||
"DeletedReasonUpgrade": "O ficheiro foi eliminado para importar uma atualização",
|
||||
"DisabledForLocalAddresses": "Desativado para Endereços Locais",
|
||||
"DownloadClientsLoadError": "Não foi possível carregar os clientes de transferências",
|
||||
|
|
|
@ -1238,8 +1238,8 @@
|
|||
"UnknownEventTooltip": "Evento desconhecido",
|
||||
"DeletedReasonManual": "O arquivo foi excluído usando {appName} manualmente ou por outra ferramenta por meio da API",
|
||||
"FormatAgeDay": "dia",
|
||||
"MovieDownloadFailedTooltip": "Falha no download do filme",
|
||||
"MovieDownloadIgnoredTooltip": "Download do Filme Ignorado",
|
||||
"DownloadFailedMovieTooltip": "Falha no download do filme",
|
||||
"DownloadIgnoredMovieTooltip": "Download do Filme Ignorado",
|
||||
"NoHistoryFound": "Nenhum histórico encontrado",
|
||||
"QueueLoadError": "Falha ao carregar a fila",
|
||||
"BlocklistLoadError": "Não foi possível carregar a lista de bloqueio",
|
||||
|
@ -1252,7 +1252,7 @@
|
|||
"HistoryLoadError": "Não foi possível carregar o histórico",
|
||||
"InfoUrl": "URL da info",
|
||||
"MovieImported": "Filme Importado",
|
||||
"MovieGrabbedHistoryTooltip": "Filme obtido de {indexer} e enviado para {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Filme obtido de {indexer} e enviado para {downloadClient}",
|
||||
"MovieImportedTooltip": "Filme baixado com sucesso e obtido no cliente de download",
|
||||
"PendingDownloadClientUnavailable": "Pendente - O cliente de download não está disponível",
|
||||
"FormatAgeDays": "dias",
|
||||
|
@ -1279,7 +1279,7 @@
|
|||
"TablePageSize": "Tamanho da Página",
|
||||
"TablePageSizeHelpText": "Número de itens a serem exibidos em cada página",
|
||||
"TablePageSizeMinimum": "O tamanho da página precisa ser de pelo menos {minimumValue}",
|
||||
"DeletedReasonMissingFromDisk": "O {appName} não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do filme no banco de dados",
|
||||
"DeletedReasonMovieMissingFromDisk": "O {appName} não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do filme no banco de dados",
|
||||
"FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}",
|
||||
"RemoveSelectedBlocklistMessageText": "Tem certeza de que deseja remover os itens selecionados da lista de bloqueio?",
|
||||
"ShowUnknownMovieItemsHelpText": "Mostrar itens sem filme na fila. Isso pode incluir filmes removidos ou qualquer outra coisa na categoria do {appName}",
|
||||
|
|
|
@ -1035,14 +1035,14 @@
|
|||
"MovieFileRenamed": "Fișier de film redenumit",
|
||||
"MovieFileDeletedTooltip": "Fișier de film șters",
|
||||
"MovieFileDeleted": "Fișier de film șters",
|
||||
"MovieDownloadIgnoredTooltip": "Descărcare film ignorată",
|
||||
"DownloadIgnoredMovieTooltip": "Descărcare film ignorată",
|
||||
"MovieImported": "Film importat",
|
||||
"MovieGrabbedHistoryTooltip": "Film preluat de la {indexer} și trimis către {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Film preluat de la {indexer} și trimis către {downloadClient}",
|
||||
"FormatAgeMinutes": "minute",
|
||||
"UnknownEventTooltip": "Eveniment necunoscut",
|
||||
"TablePageSize": "Mărimea Paginii",
|
||||
"MovieFileRenamedTooltip": "Fișier de film redenumit",
|
||||
"MovieDownloadFailedTooltip": "Descărcarea filmului a eșuat",
|
||||
"DownloadFailedMovieTooltip": "Descărcarea filmului a eșuat",
|
||||
"FullColorEvents": "Evenimente pline de culoare",
|
||||
"BlocklistReleaseHelpText": "Împiedică {appName} să apuce automat această versiune din nou",
|
||||
"BlocklistLoadError": "Imposibil de încărcat lista neagră",
|
||||
|
@ -1070,7 +1070,7 @@
|
|||
"ReleaseProfilesLoadError": "Nu se pot încărca profilurile",
|
||||
"PendingDownloadClientUnavailable": "În așteptare - Clientul de descărcare nu este disponibil",
|
||||
"MovieImportedTooltip": "Filmul a fost descărcat cu succes și preluat de la clientul de descărcare",
|
||||
"DeletedReasonMissingFromDisk": "{appName} nu a putut găsi fișierul de pe disc, așa că a fost eliminat",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} nu a putut găsi fișierul de pe disc, așa că a fost eliminat",
|
||||
"AddConnection": "Adăugați conexiune",
|
||||
"AddConnectionImplementation": "Adăugați conexiune - {implementationName}",
|
||||
"AddDownloadClientImplementation": "Adăugați client de descărcare - {implementationName}",
|
||||
|
|
|
@ -1263,7 +1263,7 @@
|
|||
"Label": "Метка",
|
||||
"DelayProfileMovieTagsHelpText": "Применимо к фильмам с хотя бы одним подходящим тегом",
|
||||
"Release": "Релиз",
|
||||
"DeletedReasonMissingFromDisk": "{appName} не смог найти файл на диске, поэтому файл был откреплён от фильма в базе данных",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} не смог найти файл на диске, поэтому файл был откреплён от фильма в базе данных",
|
||||
"MovieIsNotMonitored": "Фильм не отслеживается",
|
||||
"MovieFileDeleted": "При удалении файла фильма",
|
||||
"DeleteReleaseProfile": "Удалить профиль релиза",
|
||||
|
@ -1641,7 +1641,7 @@
|
|||
"EditionFootNote": "При необходимости можно управлять обрезкой до максимального количества байтов, включая многоточие (`...`). Поддерживается обрезка как с конца (например, `{Series Title:30}`), так и с начала (например, `{Series Title:-30}`).",
|
||||
"NotificationsCustomScriptValidationFileDoesNotExist": "Файл не существует",
|
||||
"NotificationsGotifySettingsServerHelpText": "URL-адрес сервера Gotify, включая http(s):// и порт, если необходимо",
|
||||
"MovieGrabbedHistoryTooltip": "Эпизод получен из {indexer} и отправлен в {downloadClient}",
|
||||
"MovieGrabbedTooltip": "Эпизод получен из {indexer} и отправлен в {downloadClient}",
|
||||
"NotificationsPlexValidationNoMovieLibraryFound": "Требуется хотя бы одна библиотека c сериалами",
|
||||
"SearchForAllMissingMovies": "Искать все недостающие эпизоды",
|
||||
"SearchForAllMissingMoviesConfirmationCount": "Вы уверены, что хотите найти все ({totalRecords}) недостающие эпизоды ?",
|
||||
|
|
|
@ -1069,7 +1069,7 @@
|
|||
"ConditionUsingRegularExpressions": "Detta villkor matchar användningen av reguljära uttryck. Observera att tecknen {0} har speciella betydelser och behöver fly med ett {1}",
|
||||
"MovieFileDeleted": "På filmfil Ta bort",
|
||||
"DeleteCustomFormatMessageText": "Är du säker på att du vill radera taggen '{0}'?",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr kunde inte hitta filen på disken så den togs bort",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} kunde inte hitta filen på disken så den togs bort",
|
||||
"DeleteSelectedMovieFilesHelpText": "Är du säker på att du vill radera de markerade filmfilerna?",
|
||||
"MovieIsNotMonitored": "FIlmen är bevakad",
|
||||
"SearchForAllMissingMovies": "Sök efter alla saknade böcker",
|
||||
|
|
|
@ -1052,7 +1052,7 @@
|
|||
"IMDbId": "รหัส TMDb",
|
||||
"MovieFileDeleted": "บน Movie File Delete",
|
||||
"AddDelayProfileError": "ไม่สามารถเพิ่มโปรไฟล์คุณภาพใหม่ได้โปรดลองอีกครั้ง",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr ไม่พบไฟล์บนดิสก์ดังนั้นจึงถูกลบออก",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} ไม่พบไฟล์บนดิสก์ดังนั้นจึงถูกลบออก",
|
||||
"DownloadClientSettingsRecentPriority": "ลำดับความสำคัญของลูกค้า",
|
||||
"DeleteSelectedMovieFilesHelpText": "แน่ใจไหมว่าต้องการลบไฟล์ภาพยนตร์ที่เลือก",
|
||||
"ShowUnknownMovieItemsHelpText": "แสดงรายการที่ไม่มีภาพยนตร์อยู่ในคิว ซึ่งอาจรวมถึงภาพยนตร์ที่ถูกนำออกหรือสิ่งอื่นใดในหมวดหมู่ของ Lidarr",
|
||||
|
|
|
@ -1114,7 +1114,7 @@
|
|||
"DeleteRootFolderMessageText": "'{path}' kök klasörünü silmek istediğinizden emin misiniz?",
|
||||
"DeleteSelectedIndexersMessageText": "Seçilen {count} dizinleyiciyi silmek istediğinizden emin misiniz?",
|
||||
"Destination": "Hedef",
|
||||
"DeletedReasonMissingFromDisk": "{appName} dosyayı diskte bulamadığından dosyanın veritabanındaki filmle bağlantısı kaldırıldı",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} dosyayı diskte bulamadığından dosyanın veritabanındaki filmle bağlantısı kaldırıldı",
|
||||
"DownloadClientDelugeTorrentStateError": "Deluge bir hata bildiriyor",
|
||||
"DownloadClientDelugeValidationLabelPluginInactive": "Etiket eklentisi etkinleştirilmedi",
|
||||
"DownloadClientDelugeValidationLabelPluginInactiveDetail": "Kategorileri kullanmak için {clientName} uygulamasında Etiket eklentisini etkinleştirmiş olmanız gerekir.",
|
||||
|
@ -1287,8 +1287,8 @@
|
|||
"ListRefreshInterval": "Liste Yenileme Aralığı",
|
||||
"InteractiveImportNoImportMode": "Bir içe aktarma modu seçilmelidir",
|
||||
"MonitorCollection": "Takip Etme Koleksiyonu",
|
||||
"MovieDownloadFailedTooltip": "Film indirilemedi",
|
||||
"MovieDownloadIgnoredTooltip": "Film İndirme Yoksayıldı",
|
||||
"DownloadFailedMovieTooltip": "Film indirilemedi",
|
||||
"DownloadIgnoredMovieTooltip": "Film İndirme Yoksayıldı",
|
||||
"NotificationsAppriseSettingsServerUrlHelpText": "Gerekiyorsa http(s):// ve bağlantı noktasını içeren Apprise sunucu URL'si",
|
||||
"NotificationsAppriseSettingsTags": "Apprise Etiketler",
|
||||
"NotificationsEmailSettingsUseEncryption": "Şifreleme Kullan",
|
||||
|
@ -1341,7 +1341,7 @@
|
|||
"IndexerDownloadClientHealthCheckMessage": "Geçersiz indirme istemcilerine sahip dizinleyiciler: {indexerNames}.",
|
||||
"MovieCollectionRootFolderMissingRootHealthCheckMessage": "Film koleksiyonu için eksik kök klasör: {rootFolderInfo}",
|
||||
"NoImportListsFound": "İçe aktarma listesi bulunamadı",
|
||||
"MovieGrabbedHistoryTooltip": "Film {indexer}'dan alındı ve {downloadClient}'a gönderildi",
|
||||
"MovieGrabbedTooltip": "Film {indexer}'dan alındı ve {downloadClient}'a gönderildi",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Arızalar nedeniyle tüm bildirimler kullanılamıyor",
|
||||
"FormatAgeHour": "saat",
|
||||
"GrabId": "ID'den Yakala",
|
||||
|
|
|
@ -1103,7 +1103,7 @@
|
|||
"AddConnection": "Додати Підключення",
|
||||
"TablePageSizeHelpText": "Кількість елементів для показу на кожній сторінці",
|
||||
"ConnectionLostToBackend": "{appName} втратив з’єднання з серверною частиною, і його потрібно перезавантажити, щоб відновити роботу.",
|
||||
"DeletedReasonMissingFromDisk": "{appName} не зміг знайти файл на диску, тому файл було від’єднано від фільму в базі даних",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} не зміг знайти файл на диску, тому файл було від’єднано від фільму в базі даних",
|
||||
"FormatAgeHours": "Години",
|
||||
"FormatAgeMinute": "Хвилин",
|
||||
"FormatAgeMinutes": "Хвилин",
|
||||
|
|
|
@ -1046,7 +1046,7 @@
|
|||
"DeleteSelectedMovieFilesHelpText": "Bạn có chắc chắn muốn xóa các tệp phim đã chọn không?",
|
||||
"DeleteCustomFormatMessageText": "Bạn có chắc chắn muốn xóa thẻ '{0}' không?",
|
||||
"DeleteQualityProfileMessageText": "Bạn có chắc chắn muốn xóa cấu hình chất lượng không {0}",
|
||||
"DeletedReasonMissingFromDisk": "Whisparr không thể tìm thấy tệp trên đĩa nên nó đã bị xóa",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} không thể tìm thấy tệp trên đĩa nên nó đã bị xóa",
|
||||
"MovieIsNotMonitored": "Phim không được giám sát",
|
||||
"SearchOnAddCollectionHelpText": "Tìm kiếm phim trong danh sách này khi được thêm vào {appName}",
|
||||
"DeleteFormatMessageText": "Bạn có chắc chắn muốn xóa thẻ '{0}' không?",
|
||||
|
|
|
@ -1205,8 +1205,8 @@
|
|||
"InfoUrl": "信息 URL",
|
||||
"InvalidUILanguage": "您的UI设置为无效语言,请纠正并保存设置",
|
||||
"LanguagesLoadError": "无法加载语言",
|
||||
"MovieDownloadFailedTooltip": "电影下载失败",
|
||||
"MovieDownloadIgnoredTooltip": "电影下载被忽略",
|
||||
"DownloadFailedMovieTooltip": "电影下载失败",
|
||||
"DownloadIgnoredMovieTooltip": "电影下载被忽略",
|
||||
"MovieFileDeleted": "电影文件已删除",
|
||||
"MovieFileRenamed": "电影文件已重命名",
|
||||
"MovieFileRenamedTooltip": "电影文件已重命名",
|
||||
|
@ -1217,7 +1217,7 @@
|
|||
"AppUpdatedVersion": "{appName} 已经更新到版本 {version} ,重新加载 {appName} 使更新生效",
|
||||
"ConnectionLostReconnect": "{appName} 将会尝试自动连接,您也可以点击下方的重新加载。",
|
||||
"ConnectionLostToBackend": "{appName}失去了与后端的连接,需要重新加载以恢复功能。",
|
||||
"DeletedReasonMissingFromDisk": "{appName} 无法在磁盘上找到该文件,因此该文件已与数据库中的电影解除链接",
|
||||
"DeletedReasonMovieMissingFromDisk": "{appName} 无法在磁盘上找到该文件,因此该文件已与数据库中的电影解除链接",
|
||||
"EditDownloadClientImplementation": "编辑下载客户端- {implementationName}",
|
||||
"EditIndexerImplementation": "编辑索引器- {implementationName}",
|
||||
"GrabId": "抓取ID",
|
||||
|
@ -1225,7 +1225,7 @@
|
|||
"IMDbId": "IMDb Id",
|
||||
"InteractiveImportNoMovie": "每个选中的文件必须选择对应的电影",
|
||||
"MovieFileDeletedTooltip": "电影文件已删除",
|
||||
"MovieGrabbedHistoryTooltip": "从 {indexer} 抓取电影并发送到 {downloadClient}",
|
||||
"MovieGrabbedTooltip": "从 {indexer} 抓取电影并发送到 {downloadClient}",
|
||||
"IndexerDownloadClientHealthCheckMessage": "使用无效下载客户端的索引器:{indexerNames}。",
|
||||
"FullColorEvents": "全彩事件",
|
||||
"InteractiveImportNoFilesFound": "在所选文件夹中找不到视频文件",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue