mirror of
https://github.com/Radarr/Radarr.git
synced 2024-10-05 15:47:20 +02:00
New: Added UI for parsing release names
(cherry picked from commit 85e285598106346099ceae676599c5cb4b789c92)
This commit is contained in:
parent
5f70581a59
commit
faaef80a80
@ -13,7 +13,7 @@ import Switch from 'Components/Router/Switch';
|
|||||||
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
|
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
|
||||||
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
|
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
|
||||||
import MovieIndex from 'Movie/Index/MovieIndex';
|
import MovieIndex from 'Movie/Index/MovieIndex';
|
||||||
import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector';
|
import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage';
|
||||||
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
||||||
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
||||||
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
|
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
|
||||||
@ -148,7 +148,7 @@ function AppRoutes(props) {
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/settings/customformats"
|
path="/settings/customformats"
|
||||||
component={CustomFormatSettingsConnector}
|
component={CustomFormatSettingsPage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||||
import MovieFilesAppState from './MovieFilesAppState';
|
import MovieFilesAppState from './MovieFilesAppState';
|
||||||
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
|
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
|
||||||
|
import ParseAppState from './ParseAppState';
|
||||||
import QueueAppState from './QueueAppState';
|
import QueueAppState from './QueueAppState';
|
||||||
import SettingsAppState from './SettingsAppState';
|
import SettingsAppState from './SettingsAppState';
|
||||||
import TagsAppState from './TagsAppState';
|
import TagsAppState from './TagsAppState';
|
||||||
@ -41,6 +42,7 @@ interface AppState {
|
|||||||
movieFiles: MovieFilesAppState;
|
movieFiles: MovieFilesAppState;
|
||||||
interactiveImport: InteractiveImportAppState;
|
interactiveImport: InteractiveImportAppState;
|
||||||
movieIndex: MovieIndexAppState;
|
movieIndex: MovieIndexAppState;
|
||||||
|
parse: ParseAppState;
|
||||||
settings: SettingsAppState;
|
settings: SettingsAppState;
|
||||||
movies: MoviesAppState;
|
movies: MoviesAppState;
|
||||||
tags: TagsAppState;
|
tags: TagsAppState;
|
||||||
|
34
frontend/src/App/State/ParseAppState.ts
Normal file
34
frontend/src/App/State/ParseAppState.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import ModelBase from 'App/ModelBase';
|
||||||
|
import { AppSectionItemState } from 'App/State/AppSectionState';
|
||||||
|
import Language from 'Language/Language';
|
||||||
|
import Movie from 'Movie/Movie';
|
||||||
|
import { QualityModel } from 'Quality/Quality';
|
||||||
|
import CustomFormat from 'typings/CustomFormat';
|
||||||
|
|
||||||
|
export interface ParsedMovieInfo {
|
||||||
|
releaseTitle: string;
|
||||||
|
originalTitle: string;
|
||||||
|
movieTitle: string;
|
||||||
|
movieTitles: string[];
|
||||||
|
year: number;
|
||||||
|
quality: QualityModel;
|
||||||
|
languages: Language[];
|
||||||
|
releaseHash: string;
|
||||||
|
releaseGroup?: string;
|
||||||
|
edition?: string;
|
||||||
|
tmdbId?: number;
|
||||||
|
imdbId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParseModel extends ModelBase {
|
||||||
|
title: string;
|
||||||
|
parsedMovieInfo: ParsedMovieInfo;
|
||||||
|
movie?: Movie;
|
||||||
|
languages?: Language[];
|
||||||
|
customFormats?: CustomFormat[];
|
||||||
|
customFormatScore?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseAppState = AppSectionItemState<ParseModel>;
|
||||||
|
|
||||||
|
export default ParseAppState;
|
@ -5,8 +5,8 @@ import { isLocked } from 'Utilities/scrollLock';
|
|||||||
import styles from './PageContentBody.css';
|
import styles from './PageContentBody.css';
|
||||||
|
|
||||||
interface PageContentBodyProps {
|
interface PageContentBodyProps {
|
||||||
className: string;
|
className?: string;
|
||||||
innerClassName: string;
|
innerClassName?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
initialScrollTop?: number;
|
initialScrollTop?: number;
|
||||||
onScroll?: (payload: OnScroll) => void;
|
onScroll?: (payload: OnScroll) => void;
|
||||||
|
@ -34,6 +34,7 @@ import {
|
|||||||
faBug as fasBug,
|
faBug as fasBug,
|
||||||
faBuilding as fasBuilding,
|
faBuilding as fasBuilding,
|
||||||
faBullhorn as fasBullhorn,
|
faBullhorn as fasBullhorn,
|
||||||
|
faCalculator as fasCalculator,
|
||||||
faCalendarAlt as fasCalendarAlt,
|
faCalendarAlt as fasCalendarAlt,
|
||||||
faCaretDown as fasCaretDown,
|
faCaretDown as fasCaretDown,
|
||||||
faCheck as fasCheck,
|
faCheck as fasCheck,
|
||||||
@ -189,6 +190,7 @@ export const PAGE_PREVIOUS = fasBackward;
|
|||||||
export const PAGE_NEXT = fasForward;
|
export const PAGE_NEXT = fasForward;
|
||||||
export const PAGE_LAST = fasFastForward;
|
export const PAGE_LAST = fasFastForward;
|
||||||
export const PARENT = fasLevelUpAlt;
|
export const PARENT = fasLevelUpAlt;
|
||||||
|
export const PARSE = fasCalculator;
|
||||||
export const PAUSED = fasPause;
|
export const PAUSED = fasPause;
|
||||||
export const PENDING = farClock;
|
export const PENDING = farClock;
|
||||||
export const PLAY = fasPlay;
|
export const PLAY = fasPlay;
|
||||||
|
45
frontend/src/Parse/Parse.css
Normal file
45
frontend/src/Parse/Parse.css
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
.inputContainer {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputIconContainer {
|
||||||
|
width: 58px;
|
||||||
|
height: 46px;
|
||||||
|
border: 1px solid var(--inputBorderColor);
|
||||||
|
border-right: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
background-color: var(--inputIconContainerBackgroundColor);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
composes: input from '~Components/Form/TextInput.css';
|
||||||
|
|
||||||
|
height: 46px;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearButton {
|
||||||
|
border: 1px solid var(--inputBorderColor);
|
||||||
|
border-left: none;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: $largeFontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
12
frontend/src/Parse/Parse.css.d.ts
vendored
Normal file
12
frontend/src/Parse/Parse.css.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'clearButton': string;
|
||||||
|
'helpText': string;
|
||||||
|
'input': string;
|
||||||
|
'inputContainer': string;
|
||||||
|
'inputIconContainer': string;
|
||||||
|
'message': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
111
frontend/src/Parse/Parse.tsx
Normal file
111
frontend/src/Parse/Parse.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import TextInput from 'Components/Form/TextInput';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import PageContent from 'Components/Page/PageContent';
|
||||||
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import { clear, fetch } from 'Store/Actions/parseActions';
|
||||||
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
|
import ParseResult from './ParseResult';
|
||||||
|
import parseStateSelector from './parseStateSelector';
|
||||||
|
import styles from './Parse.css';
|
||||||
|
|
||||||
|
function Parse() {
|
||||||
|
const { isFetching, error, item } = useSelector(parseStateSelector());
|
||||||
|
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onInputChange = useCallback(
|
||||||
|
({ value }: { value: string }) => {
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
|
||||||
|
setTitle(value);
|
||||||
|
|
||||||
|
if (trimmedValue === '') {
|
||||||
|
dispatch(clear());
|
||||||
|
} else {
|
||||||
|
dispatch(fetch({ title: trimmedValue }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setTitle, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClearPress = useCallback(() => {
|
||||||
|
setTitle('');
|
||||||
|
dispatch(clear());
|
||||||
|
}, [setTitle, dispatch]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
return () => {
|
||||||
|
dispatch(clear());
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContent title="Parse">
|
||||||
|
<PageContentBody>
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<div className={styles.inputIconContainer}>
|
||||||
|
<Icon name={icons.PARSE} size={20} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
className={styles.input}
|
||||||
|
name="title"
|
||||||
|
value={title}
|
||||||
|
placeholder="eg. Movie.Title.2020.720p.HDTV-RlsGroup"
|
||||||
|
autoFocus={true}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button className={styles.clearButton} onPress={onClearPress}>
|
||||||
|
<Icon name={icons.REMOVE} size={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isFetching ? <LoadingIndicator /> : null}
|
||||||
|
|
||||||
|
{!isFetching && !!error ? (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<div className={styles.helpText}>
|
||||||
|
Error parsing, please try again.
|
||||||
|
</div>
|
||||||
|
<div>{getErrorMessage(error)}</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!isFetching && title && !error && !item.parsedMovieInfo ? (
|
||||||
|
<div className={styles.message}>
|
||||||
|
Unable to parse the provided title, please try again.
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!isFetching && !error && item.parsedMovieInfo ? (
|
||||||
|
<ParseResult item={item} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{title ? null : (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<div className={styles.helpText}>
|
||||||
|
Enter a release title in the input above
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Radarr will attempt to parse the title and show you details about
|
||||||
|
it
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</PageContentBody>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Parse;
|
20
frontend/src/Parse/ParseModal.tsx
Normal file
20
frontend/src/Parse/ParseModal.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import ParseModalContent from './ParseModalContent';
|
||||||
|
|
||||||
|
interface ParseModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onModalClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseModal(props: ParseModalProps) {
|
||||||
|
const { isOpen, onModalClose } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||||
|
<ParseModalContent onModalClose={onModalClose} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseModal;
|
45
frontend/src/Parse/ParseModalContent.css
Normal file
45
frontend/src/Parse/ParseModalContent.css
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
.inputContainer {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputIconContainer {
|
||||||
|
width: 58px;
|
||||||
|
height: 46px;
|
||||||
|
border: 1px solid var(--inputBorderColor);
|
||||||
|
border-right: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
background-color: var(--inputIconContainerBackgroundColor);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
composes: input from '~Components/Form/TextInput.css';
|
||||||
|
|
||||||
|
height: 46px;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearButton {
|
||||||
|
border: 1px solid var(--inputBorderColor);
|
||||||
|
border-left: none;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: $largeFontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
12
frontend/src/Parse/ParseModalContent.css.d.ts
vendored
Normal file
12
frontend/src/Parse/ParseModalContent.css.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'clearButton': string;
|
||||||
|
'helpText': string;
|
||||||
|
'input': string;
|
||||||
|
'inputContainer': string;
|
||||||
|
'inputIconContainer': string;
|
||||||
|
'message': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
125
frontend/src/Parse/ParseModalContent.tsx
Normal file
125
frontend/src/Parse/ParseModalContent.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import TextInput from 'Components/Form/TextInput';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import { clear, fetch } from 'Store/Actions/parseActions';
|
||||||
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import ParseResult from './ParseResult';
|
||||||
|
import parseStateSelector from './parseStateSelector';
|
||||||
|
import styles from './ParseModalContent.css';
|
||||||
|
|
||||||
|
interface ParseModalContentProps {
|
||||||
|
onModalClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseModalContent(props: ParseModalContentProps) {
|
||||||
|
const { onModalClose } = props;
|
||||||
|
const { isFetching, error, item } = useSelector(parseStateSelector());
|
||||||
|
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onInputChange = useCallback(
|
||||||
|
({ value }: { value: string }) => {
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
|
||||||
|
setTitle(value);
|
||||||
|
|
||||||
|
if (trimmedValue === '') {
|
||||||
|
dispatch(clear());
|
||||||
|
} else {
|
||||||
|
dispatch(fetch({ title: trimmedValue }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setTitle, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClearPress = useCallback(() => {
|
||||||
|
setTitle('');
|
||||||
|
dispatch(clear());
|
||||||
|
}, [setTitle, dispatch]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
return () => {
|
||||||
|
dispatch(clear());
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>{translate('TestParsing')}</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<div className={styles.inputIconContainer}>
|
||||||
|
<Icon name={icons.PARSE} size={20} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
className={styles.input}
|
||||||
|
name="title"
|
||||||
|
value={title}
|
||||||
|
placeholder="eg. Movie.Title.2020.720p.HDTV-RlsGroup"
|
||||||
|
autoFocus={true}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button className={styles.clearButton} onPress={onClearPress}>
|
||||||
|
<Icon name={icons.REMOVE} size={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isFetching ? <LoadingIndicator /> : null}
|
||||||
|
|
||||||
|
{!isFetching && !!error ? (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<div className={styles.helpText}>
|
||||||
|
Error parsing, please try again.
|
||||||
|
</div>
|
||||||
|
<div>{getErrorMessage(error)}</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!isFetching && title && !error && !item.parsedMovieInfo ? (
|
||||||
|
<div className={styles.message}>
|
||||||
|
Unable to parse the provided title, please try again.
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!isFetching && !error && item.parsedMovieInfo ? (
|
||||||
|
<ParseResult item={item} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{title ? null : (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<div className={styles.helpText}>
|
||||||
|
Enter a release title in the input above
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Radarr will attempt to parse the title and show you details about
|
||||||
|
it
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>{translate('Close')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseModalContent;
|
8
frontend/src/Parse/ParseResult.css
Normal file
8
frontend/src/Parse/ParseResult.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex: 0 0 50%;
|
||||||
|
}
|
8
frontend/src/Parse/ParseResult.css.d.ts
vendored
Normal file
8
frontend/src/Parse/ParseResult.css.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'column': string;
|
||||||
|
'container': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
161
frontend/src/Parse/ParseResult.tsx
Normal file
161
frontend/src/Parse/ParseResult.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ParseModel } from 'App/State/ParseAppState';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
|
import MovieFormats from 'Movie/MovieFormats';
|
||||||
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import ParseResultItem from './ParseResultItem';
|
||||||
|
import styles from './ParseResult.css';
|
||||||
|
|
||||||
|
interface ParseResultProps {
|
||||||
|
item: ParseModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseResult(props: ParseResultProps) {
|
||||||
|
const { item } = props;
|
||||||
|
const {
|
||||||
|
customFormats,
|
||||||
|
customFormatScore,
|
||||||
|
languages,
|
||||||
|
parsedMovieInfo,
|
||||||
|
movie,
|
||||||
|
} = item;
|
||||||
|
|
||||||
|
const {
|
||||||
|
releaseTitle,
|
||||||
|
movieTitle,
|
||||||
|
movieTitles,
|
||||||
|
year,
|
||||||
|
edition,
|
||||||
|
releaseGroup,
|
||||||
|
releaseHash,
|
||||||
|
quality,
|
||||||
|
tmdbId,
|
||||||
|
imdbId,
|
||||||
|
} = parsedMovieInfo;
|
||||||
|
|
||||||
|
const finalLanguages = languages ?? parsedMovieInfo.languages;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FieldSet legend={translate('Release')}>
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('ReleaseTitle')}
|
||||||
|
data={releaseTitle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem title={translate('MovieTitle')} data={movieTitle} />
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Year')}
|
||||||
|
data={year > 0 ? year : '-'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Edition')}
|
||||||
|
data={edition ? edition : '-'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('AllTitles')}
|
||||||
|
data={movieTitles?.length > 0 ? movieTitles.join(', ') : '-'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('ReleaseGroup')}
|
||||||
|
data={releaseGroup ?? '-'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('ReleaseHash')}
|
||||||
|
data={releaseHash ? releaseHash : '-'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{tmdbId ? (
|
||||||
|
<ParseResultItem title={translate('TmdbId')} data={tmdbId} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{imdbId ? (
|
||||||
|
<ParseResultItem title={translate('ImdbId')} data={imdbId} />
|
||||||
|
) : null}
|
||||||
|
</FieldSet>
|
||||||
|
|
||||||
|
<FieldSet legend={translate('Quality')}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.column}>
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Quality')}
|
||||||
|
data={quality.quality.name}
|
||||||
|
/>
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Proper')}
|
||||||
|
data={
|
||||||
|
quality.revision.version > 1 && !quality.revision.isRepack
|
||||||
|
? 'True'
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Repack')}
|
||||||
|
data={quality.revision.isRepack ? 'True' : '-'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.column}>
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Version')}
|
||||||
|
data={
|
||||||
|
quality.revision.version > 1 ? quality.revision.version : '-'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Real')}
|
||||||
|
data={quality.revision.real ? 'True' : '-'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FieldSet>
|
||||||
|
|
||||||
|
<FieldSet legend={translate('Languages')}>
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('Languages')}
|
||||||
|
data={finalLanguages.map((l) => l.name).join(', ')}
|
||||||
|
/>
|
||||||
|
</FieldSet>
|
||||||
|
|
||||||
|
<FieldSet legend={translate('Details')}>
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('MatchedToMovie')}
|
||||||
|
data={
|
||||||
|
movie ? (
|
||||||
|
<MovieTitleLink titleSlug={movie.titleSlug} title={movie.title} />
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{movie && movie.originalLanguage ? (
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('OriginalLanguage')}
|
||||||
|
data={movie.originalLanguage.name}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('CustomFormats')}
|
||||||
|
data={<MovieFormats formats={customFormats} />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseResultItem
|
||||||
|
title={translate('CustomFormatScore')}
|
||||||
|
data={customFormatScore}
|
||||||
|
/>
|
||||||
|
</FieldSet>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseResult;
|
21
frontend/src/Parse/ParseResultItem.css
Normal file
21
frontend/src/Parse/ParseResultItem.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-right: 20px;
|
||||||
|
width: 250px;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpointSmall) {
|
||||||
|
.item {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
8
frontend/src/Parse/ParseResultItem.css.d.ts
vendored
Normal file
8
frontend/src/Parse/ParseResultItem.css.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'item': string;
|
||||||
|
'title': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
20
frontend/src/Parse/ParseResultItem.tsx
Normal file
20
frontend/src/Parse/ParseResultItem.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import styles from './ParseResultItem.css';
|
||||||
|
|
||||||
|
interface ParseResultItemProps {
|
||||||
|
title: string;
|
||||||
|
data: string | number | ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseResultItem(props: ParseResultItemProps) {
|
||||||
|
const { title, data } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.item}>
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
<div>{data}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseResultItem;
|
31
frontend/src/Parse/ParseToolbarButton.tsx
Normal file
31
frontend/src/Parse/ParseToolbarButton.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React, { Fragment, useCallback, useState } from 'react';
|
||||||
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import ParseModal from 'Parse/ParseModal';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
function ParseToolbarButton() {
|
||||||
|
const [isParseModalOpen, setIsParseModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const onOpenParseModalPress = useCallback(() => {
|
||||||
|
setIsParseModalOpen(true);
|
||||||
|
}, [setIsParseModalOpen]);
|
||||||
|
|
||||||
|
const onParseModalClose = useCallback(() => {
|
||||||
|
setIsParseModalOpen(false);
|
||||||
|
}, [setIsParseModalOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('TestParsing')}
|
||||||
|
iconName={icons.PARSE}
|
||||||
|
onPress={onOpenParseModalPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ParseModal isOpen={isParseModalOpen} onModalClose={onParseModalClose} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseToolbarButton;
|
12
frontend/src/Parse/parseStateSelector.ts
Normal file
12
frontend/src/Parse/parseStateSelector.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import ParseAppState from 'App/State/ParseAppState';
|
||||||
|
|
||||||
|
export default function parseStateSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.parse,
|
||||||
|
(parse: ParseAppState) => {
|
||||||
|
return parse;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { DndProvider } from 'react-dnd';
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
import PageContent from 'Components/Page/PageContent';
|
||||||
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
|
import ParseToolbarButton from 'Parse/ParseToolbarButton';
|
||||||
|
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||||
|
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
|
||||||
|
|
||||||
|
function CustomFormatSettingsPage() {
|
||||||
|
return (
|
||||||
|
<PageContent title="Custom Format Settings">
|
||||||
|
<SettingsToolbarConnector
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
showSave={false}
|
||||||
|
additionalButtons={
|
||||||
|
<Fragment>
|
||||||
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
|
<ParseToolbarButton />
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PageContentBody>
|
||||||
|
{/* TODO: Upgrade react-dnd to get typings, we're 2 major versions behind */}
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<CustomFormatsConnector />
|
||||||
|
</DndProvider>
|
||||||
|
</PageContentBody>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomFormatSettingsPage;
|
@ -134,6 +134,7 @@ const historyShape = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SettingsToolbarConnector.propTypes = {
|
SettingsToolbarConnector.propTypes = {
|
||||||
|
showSave: PropTypes.bool,
|
||||||
hasPendingChanges: PropTypes.bool.isRequired,
|
hasPendingChanges: PropTypes.bool.isRequired,
|
||||||
history: PropTypes.shape(historyShape).isRequired,
|
history: PropTypes.shape(historyShape).isRequired,
|
||||||
onSavePress: PropTypes.func,
|
onSavePress: PropTypes.func,
|
||||||
|
@ -19,6 +19,7 @@ import * as movieHistory from './movieHistoryActions';
|
|||||||
import * as movieIndex from './movieIndexActions';
|
import * as movieIndex from './movieIndexActions';
|
||||||
import * as oAuth from './oAuthActions';
|
import * as oAuth from './oAuthActions';
|
||||||
import * as organizePreview from './organizePreviewActions';
|
import * as organizePreview from './organizePreviewActions';
|
||||||
|
import * as parse from './parseActions';
|
||||||
import * as paths from './pathActions';
|
import * as paths from './pathActions';
|
||||||
import * as providerOptions from './providerOptionActions';
|
import * as providerOptions from './providerOptionActions';
|
||||||
import * as queue from './queueActions';
|
import * as queue from './queueActions';
|
||||||
@ -44,6 +45,7 @@ export default [
|
|||||||
interactiveImportActions,
|
interactiveImportActions,
|
||||||
oAuth,
|
oAuth,
|
||||||
organizePreview,
|
organizePreview,
|
||||||
|
parse,
|
||||||
paths,
|
paths,
|
||||||
providerOptions,
|
providerOptions,
|
||||||
queue,
|
queue,
|
||||||
|
111
frontend/src/Store/Actions/parseActions.ts
Normal file
111
frontend/src/Store/Actions/parseActions.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { createAction } from 'redux-actions';
|
||||||
|
import { batchActions } from 'redux-batched-actions';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
|
import { set, update } from './baseActions';
|
||||||
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
import createClearReducer from './Creators/Reducers/createClearReducer';
|
||||||
|
|
||||||
|
interface FetchPayload {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
export const section = 'parse';
|
||||||
|
let parseTimeout: number | null = null;
|
||||||
|
let abortCurrentRequest: (() => void) | null = null;
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
export const defaultState = {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
item: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH = 'parse/fetch';
|
||||||
|
export const CLEAR = 'parse/clear';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetch = createThunk(FETCH);
|
||||||
|
export const clear = createAction(CLEAR);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
export const actionHandlers = handleThunks({
|
||||||
|
[FETCH]: function (
|
||||||
|
_getState: () => AppState,
|
||||||
|
payload: FetchPayload,
|
||||||
|
dispatch: Dispatch
|
||||||
|
) {
|
||||||
|
if (parseTimeout) {
|
||||||
|
clearTimeout(parseTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTimeout = window.setTimeout(async () => {
|
||||||
|
dispatch(set({ section, isFetching: true }));
|
||||||
|
|
||||||
|
if (abortCurrentRequest) {
|
||||||
|
abortCurrentRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { request, abortRequest } = createAjaxRequest({
|
||||||
|
url: '/parse',
|
||||||
|
data: {
|
||||||
|
title: payload.title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await request;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
batchActions([
|
||||||
|
update({ section, data }),
|
||||||
|
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: true,
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isAdding: false,
|
||||||
|
isAdded: false,
|
||||||
|
addError: error,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
abortCurrentRequest = abortRequest;
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
export const reducers = createHandleActions(
|
||||||
|
{
|
||||||
|
[CLEAR]: createClearReducer(section, defaultState),
|
||||||
|
},
|
||||||
|
defaultState,
|
||||||
|
section
|
||||||
|
);
|
39
frontend/src/Store/thunks.ts
Normal file
39
frontend/src/Store/thunks.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
|
||||||
|
type GetState = () => AppState;
|
||||||
|
type Thunk = (
|
||||||
|
getState: GetState,
|
||||||
|
identityFn: never,
|
||||||
|
dispatch: Dispatch
|
||||||
|
) => unknown;
|
||||||
|
|
||||||
|
const thunks: Record<string, Thunk> = {};
|
||||||
|
|
||||||
|
function identity<T, TResult>(payload: T): TResult {
|
||||||
|
return payload as unknown as TResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createThunk(type: string, identityFunction = identity) {
|
||||||
|
return function <T>(payload?: T) {
|
||||||
|
return function (dispatch: Dispatch, getState: GetState) {
|
||||||
|
const thunk = thunks[type];
|
||||||
|
|
||||||
|
if (thunk) {
|
||||||
|
const finalPayload = payload ?? {};
|
||||||
|
|
||||||
|
return thunk(getState, identityFunction(finalPayload), dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Error(`Thunk handler has not been registered for ${type}`);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleThunks(handlers: Record<string, Thunk>) {
|
||||||
|
const types = Object.keys(handlers);
|
||||||
|
|
||||||
|
types.forEach((type) => {
|
||||||
|
thunks[type] = handlers[type];
|
||||||
|
});
|
||||||
|
}
|
@ -102,6 +102,7 @@
|
|||||||
"@types/react-router-dom": "5.3.3",
|
"@types/react-router-dom": "5.3.3",
|
||||||
"@types/react-text-truncate": "0.14.1",
|
"@types/react-text-truncate": "0.14.1",
|
||||||
"@types/react-window": "1.8.5",
|
"@types/react-window": "1.8.5",
|
||||||
|
"@types/redux-actions": "2.6.2",
|
||||||
"@types/webpack-livereload-plugin": "2.3.3",
|
"@types/webpack-livereload-plugin": "2.3.3",
|
||||||
"@typescript-eslint/eslint-plugin": "5.59.5",
|
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||||
"@typescript-eslint/parser": "5.59.5",
|
"@typescript-eslint/parser": "5.59.5",
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"AllMoviesHiddenDueToFilter": "All movies are hidden due to applied filter.",
|
"AllMoviesHiddenDueToFilter": "All movies are hidden due to applied filter.",
|
||||||
"AllMoviesInPathHaveBeenImported": "All movies in {0} have been imported",
|
"AllMoviesInPathHaveBeenImported": "All movies in {0} have been imported",
|
||||||
"AllResultsHiddenFilter": "All results are hidden by the applied filter",
|
"AllResultsHiddenFilter": "All results are hidden by the applied filter",
|
||||||
|
"AllTitles": "All Titles",
|
||||||
"AllowHardcodedSubs": "Allow Hardcoded Subs",
|
"AllowHardcodedSubs": "Allow Hardcoded Subs",
|
||||||
"AllowHardcodedSubsHelpText": "Detected hardcoded subs will be automatically downloaded",
|
"AllowHardcodedSubsHelpText": "Detected hardcoded subs will be automatically downloaded",
|
||||||
"AlreadyInYourLibrary": "Already in your library",
|
"AlreadyInYourLibrary": "Already in your library",
|
||||||
@ -545,6 +546,7 @@
|
|||||||
"MarkAsFailed": "Mark as Failed",
|
"MarkAsFailed": "Mark as Failed",
|
||||||
"MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?",
|
"MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?",
|
||||||
"MassMovieSearch": "Mass Movie Search",
|
"MassMovieSearch": "Mass Movie Search",
|
||||||
|
"MatchedToMovie": "Matched to Movie",
|
||||||
"Max": "Max",
|
"Max": "Max",
|
||||||
"MaximumLimits": "Maximum Limits",
|
"MaximumLimits": "Maximum Limits",
|
||||||
"MaximumSize": "Maximum Size",
|
"MaximumSize": "Maximum Size",
|
||||||
@ -832,6 +834,7 @@
|
|||||||
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid Radarr release branch, you will not receive updates",
|
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid Radarr release branch, you will not receive updates",
|
||||||
"ReleaseDates": "Release Dates",
|
"ReleaseDates": "Release Dates",
|
||||||
"ReleaseGroup": "Release Group",
|
"ReleaseGroup": "Release Group",
|
||||||
|
"ReleaseHash": "Release Hash",
|
||||||
"ReleaseRejected": "Release Rejected",
|
"ReleaseRejected": "Release Rejected",
|
||||||
"ReleaseStatus": "Release Status",
|
"ReleaseStatus": "Release Status",
|
||||||
"ReleaseTitle": "Release Title",
|
"ReleaseTitle": "Release Title",
|
||||||
@ -1073,6 +1076,7 @@
|
|||||||
"TestAllClients": "Test All Clients",
|
"TestAllClients": "Test All Clients",
|
||||||
"TestAllIndexers": "Test All Indexers",
|
"TestAllIndexers": "Test All Indexers",
|
||||||
"TestAllLists": "Test All Lists",
|
"TestAllLists": "Test All Lists",
|
||||||
|
"TestParsing": "Test Parsing",
|
||||||
"TheLogLevelDefault": "The log level defaults to 'Info' and can be changed in",
|
"TheLogLevelDefault": "The log level defaults to 'Info' and can be changed in",
|
||||||
"ThereWasAnErrorLoadingThisItem": "There was an error loading this item",
|
"ThereWasAnErrorLoadingThisItem": "There was an error loading this item",
|
||||||
"ThereWasAnErrorLoadingThisPage": "There was an error loading this page",
|
"ThereWasAnErrorLoadingThisPage": "There was an error loading this page",
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.CustomFormats;
|
||||||
using NzbDrone.Core.Download.Aggregation;
|
using NzbDrone.Core.Download.Aggregation;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
|
using Radarr.Api.V3.CustomFormats;
|
||||||
using Radarr.Api.V3.Movies;
|
using Radarr.Api.V3.Movies;
|
||||||
using Radarr.Http;
|
using Radarr.Http;
|
||||||
|
|
||||||
@ -14,14 +16,17 @@ public class ParseController : Controller
|
|||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IRemoteMovieAggregationService _aggregationService;
|
private readonly IRemoteMovieAggregationService _aggregationService;
|
||||||
|
private readonly ICustomFormatCalculationService _formatCalculator;
|
||||||
|
|
||||||
public ParseController(IParsingService parsingService,
|
public ParseController(IParsingService parsingService,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IRemoteMovieAggregationService aggregationService)
|
IRemoteMovieAggregationService aggregationService,
|
||||||
|
ICustomFormatCalculationService formatCalculator)
|
||||||
{
|
{
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_aggregationService = aggregationService;
|
_aggregationService = aggregationService;
|
||||||
|
_formatCalculator = formatCalculator;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -44,15 +49,21 @@ public ParseResource Parse(string title)
|
|||||||
|
|
||||||
var remoteMovie = _parsingService.Map(parsedMovieInfo, "", 0);
|
var remoteMovie = _parsingService.Map(parsedMovieInfo, "", 0);
|
||||||
|
|
||||||
_aggregationService.Augment(remoteMovie);
|
|
||||||
|
|
||||||
if (remoteMovie != null)
|
if (remoteMovie != null)
|
||||||
{
|
{
|
||||||
|
_aggregationService.Augment(remoteMovie);
|
||||||
|
|
||||||
|
remoteMovie.CustomFormats = _formatCalculator.ParseCustomFormat(remoteMovie, 0);
|
||||||
|
remoteMovie.CustomFormatScore = remoteMovie.Movie?.Profile?.CalculateCustomFormatScore(remoteMovie.CustomFormats) ?? 0;
|
||||||
|
|
||||||
return new ParseResource
|
return new ParseResource
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
ParsedMovieInfo = remoteMovie.ParsedMovieInfo,
|
ParsedMovieInfo = remoteMovie.ParsedMovieInfo,
|
||||||
Movie = remoteMovie.Movie.ToResource(_configService.AvailabilityDelay)
|
Movie = remoteMovie.Movie.ToResource(_configService.AvailabilityDelay),
|
||||||
|
Languages = remoteMovie.Languages,
|
||||||
|
CustomFormats = remoteMovie.CustomFormats?.ToResource(false),
|
||||||
|
CustomFormatScore = remoteMovie.CustomFormatScore
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using Radarr.Api.V3.CustomFormats;
|
||||||
using Radarr.Api.V3.Movies;
|
using Radarr.Api.V3.Movies;
|
||||||
using Radarr.Http.REST;
|
using Radarr.Http.REST;
|
||||||
|
|
||||||
@ -9,5 +12,8 @@ public class ParseResource : RestResource
|
|||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public ParsedMovieInfo ParsedMovieInfo { get; set; }
|
public ParsedMovieInfo ParsedMovieInfo { get; set; }
|
||||||
public MovieResource Movie { get; set; }
|
public MovieResource Movie { get; set; }
|
||||||
|
public List<Language> Languages { get; set; }
|
||||||
|
public List<CustomFormatResource> CustomFormats { get; set; }
|
||||||
|
public int CustomFormatScore { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1471,6 +1471,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/redux-actions@2.6.2":
|
||||||
|
version "2.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.6.2.tgz#5956d9e7b9a644358e2c0610f47b1fa3060edc21"
|
||||||
|
integrity sha512-TvcINy8rWFANcpc3EiEQX9Yv3owM3d3KIrqr2ryUIOhYIYzXA/bhDZeGSSSuai62iVR2qMZUgz9tQ5kr0Kl+Tg==
|
||||||
|
|
||||||
"@types/scheduler@*":
|
"@types/scheduler@*":
|
||||||
version "0.16.3"
|
version "0.16.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
||||||
|
Loading…
Reference in New Issue
Block a user