feat(requests): add language profile support (#860)

This commit is contained in:
Jakob Ankarhem
2021-02-07 16:33:18 +01:00
committed by GitHub
parent 8956cb3915
commit 53f6f59798
13 changed files with 358 additions and 32 deletions

View File

@@ -21,12 +21,15 @@ const messages = defineMessages({
loadingprofiles: 'Loading profiles…',
loadingfolders: 'Loading folders…',
requestas: 'Request As',
languageprofile: 'Language Profile',
loadinglanguages: 'Loading languages…',
});
export type RequestOverrides = {
server?: number;
profile?: number;
folder?: string;
language?: number;
user?: User;
};
@@ -69,6 +72,11 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
const [selectedFolder, setSelectedFolder] = useState<string>(
defaultOverrides?.folder ?? ''
);
const [selectedLanguage, setSelectedLanguage] = useState<number>(
defaultOverrides?.language ?? -1
);
const {
data: serverData,
isValidating,
@@ -135,6 +143,13 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
? serverData.server.activeAnimeDirectory
: serverData.server.activeDirectory)
);
const defaultLanguage = serverData.languageProfiles?.find(
(language) =>
language.id ===
(isAnime
? serverData.server.activeAnimeLanguageProfileId
: serverData.server.activeLanguageProfileId)
);
if (
defaultProfile &&
@@ -149,7 +164,15 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
defaultFolder.path !== selectedFolder &&
(!defaultOverrides || defaultOverrides.folder === null)
) {
setSelectedFolder(defaultFolder?.path ?? '');
setSelectedFolder(defaultFolder.path ?? '');
}
if (
defaultLanguage &&
defaultLanguage.id !== selectedLanguage &&
(!defaultOverrides || defaultOverrides.language === null)
) {
setSelectedLanguage(defaultLanguage.id);
}
}
}, [serverData]);
@@ -178,10 +201,19 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
) {
setSelectedFolder(defaultOverrides.folder);
}
if (
defaultOverrides &&
defaultOverrides.language !== null &&
defaultOverrides.language !== undefined
) {
setSelectedLanguage(defaultOverrides.language);
}
}, [
defaultOverrides?.server,
defaultOverrides?.folder,
defaultOverrides?.profile,
defaultOverrides?.language,
]);
useEffect(() => {
@@ -191,9 +223,16 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
profile: selectedProfile !== -1 ? selectedProfile : undefined,
server: selectedServer ?? undefined,
user: selectedUser ?? undefined,
language: selectedLanguage ?? undefined,
});
}
}, [selectedFolder, selectedServer, selectedProfile, selectedUser]);
}, [
selectedFolder,
selectedServer,
selectedProfile,
selectedUser,
selectedLanguage,
]);
if (!data && !error) {
return (
@@ -225,7 +264,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
{!!data && selectedServer !== null && (
<>
<div className="flex flex-col items-center justify-between md:flex-row">
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:pr-4 md:mb-0">
<label htmlFor="server" className="text-label">
{intl.formatMessage(messages.destinationserver)}
</label>
@@ -247,8 +286,8 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
))}
</select>
</div>
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
<label htmlFor="server" className="text-label">
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:pr-4 md:mb-0">
<label htmlFor="profile" className="text-label">
{intl.formatMessage(messages.qualityprofile)}
</label>
<select
@@ -283,8 +322,12 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
))}
</select>
</div>
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:mb-0">
<label htmlFor="server" className="text-label">
<div
className={`flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:mb-0 ${
type === 'tv' ? 'md:pr-4' : ''
}`}
>
<label htmlFor="folder" className="text-label">
{intl.formatMessage(messages.rootfolder)}
</label>
<select
@@ -319,6 +362,50 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
))}
</select>
</div>
{type === 'tv' && (
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:mb-0">
<label htmlFor="language" className="text-label">
{intl.formatMessage(messages.languageprofile)}
</label>
<select
id="language"
name="language"
value={selectedLanguage}
onChange={(e) =>
setSelectedLanguage(parseInt(e.target.value))
}
onBlur={(e) =>
setSelectedLanguage(parseInt(e.target.value))
}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white transition duration-150 ease-in-out bg-gray-800 border-gray-700 rounded-md form-select focus:outline-none focus:ring-blue focus:border-blue-300 sm:text-sm sm:leading-5"
>
{isValidating && (
<option value="">
{intl.formatMessage(messages.loadinglanguages)}
</option>
)}
{!isValidating &&
serverData &&
serverData.languageProfiles?.map((language) => (
<option
key={`folder-list${language.id}`}
value={language.id}
>
{language.name}
{isAnime &&
serverData.server.activeAnimeLanguageProfileId ===
language.id
? ` ${intl.formatMessage(messages.default)}`
: !isAnime &&
serverData.server.activeLanguageProfileId ===
language.id
? ` ${intl.formatMessage(messages.default)}`
: ''}
</option>
))}
</select>
</div>
)}
</div>
</>
)}

View File

@@ -103,6 +103,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
languageProfileId: requestOverrides?.language,
userId: requestOverrides?.user?.id,
seasons: selectedSeasons,
});
@@ -151,6 +152,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
serverId: requestOverrides.server,
profileId: requestOverrides.profile,
rootFolder: requestOverrides.folder,
languageProfileId: requestOverrides.language,
userId: requestOverrides?.user?.id,
};
}
@@ -569,6 +571,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
folder: editRequest.rootFolder,
profile: editRequest.profileId,
server: editRequest.serverId,
language: editRequest.languageProfileId,
}
: undefined
}

View File

@@ -16,7 +16,8 @@ const messages = defineMessages({
validationPortRequired: 'You must provide a port',
validationApiKeyRequired: 'You must provide an API key',
validationRootFolderRequired: 'You must select a root folder',
validationProfileRequired: 'You must select a profile',
validationProfileRequired: 'You must select a quality profile',
validationLanguageProfileRequired: 'You must select a language profile',
toastSonarrTestSuccess: 'Sonarr connection established!',
toastSonarrTestFailure: 'Failed to connect to Sonarr.',
saving: 'Saving…',
@@ -35,17 +36,22 @@ const messages = defineMessages({
baseUrl: 'Base URL',
baseUrlPlaceholder: 'Example: /sonarr',
qualityprofile: 'Quality Profile',
languageprofile: 'Language Profile',
rootfolder: 'Root Folder',
animequalityprofile: 'Anime Quality Profile',
animelanguageprofile: 'Anime Language Profile',
animerootfolder: 'Anime Root Folder',
seasonfolders: 'Season Folders',
server4k: '4K Server',
selectQualityProfile: 'Select quality profile',
selectRootFolder: 'Select root folder',
selectLanguageProfile: 'Select language profile',
loadingprofiles: 'Loading quality profiles…',
testFirstQualityProfiles: 'Test connection to load quality profiles',
loadingrootfolders: 'Loading root folders…',
testFirstRootFolders: 'Test connection to load root folders',
loadinglanguageprofiles: 'Loading language profiles…',
testFirstLanguageProfiles: 'Test connection to load language profiles',
syncEnabled: 'Enable Sync',
externalUrl: 'External URL',
externalUrlPlaceholder: 'External URL pointing to your Sonarr server',
@@ -65,6 +71,10 @@ interface TestResponse {
id: number;
path: string;
}[];
languageProfiles: {
id: number;
name: string;
}[];
}
interface SonarrModalProps {
@@ -86,6 +96,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
const [testResponse, setTestResponse] = useState<TestResponse>({
profiles: [],
rootFolders: [],
languageProfiles: [],
});
const SonarrSettingsSchema = Yup.object().shape({
name: Yup.string().required(
@@ -106,6 +117,9 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
activeProfileId: Yup.string().required(
intl.formatMessage(messages.validationProfileRequired)
),
activeLanguageProfileId: Yup.number().required(
intl.formatMessage(messages.validationLanguageProfileRequired)
),
externalUrl: Yup.string()
.url(intl.formatMessage(messages.validationApplicationUrl))
.test(
@@ -224,8 +238,10 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
apiKey: sonarr?.apiKey,
baseUrl: sonarr?.baseUrl,
activeProfileId: sonarr?.activeProfileId,
activeLanguageProfileId: sonarr?.activeLanguageProfileId,
rootFolder: sonarr?.activeDirectory,
activeAnimeProfileId: sonarr?.activeAnimeProfileId,
activeAnimeLanguageProfileId: sonarr?.activeAnimeLanguageProfileId,
activeAnimeRootFolder: sonarr?.activeAnimeDirectory,
isDefault: sonarr?.isDefault ?? false,
is4k: sonarr?.is4k ?? false,
@@ -252,11 +268,17 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
useSsl: values.ssl,
baseUrl: values.baseUrl,
activeProfileId: Number(values.activeProfileId),
activeLanguageProfileId: values.activeLanguageProfileId
? Number(values.activeLanguageProfileId)
: undefined,
activeProfileName: profileName,
activeDirectory: values.rootFolder,
activeAnimeProfileId: values.activeAnimeProfileId
? Number(values.activeAnimeProfileId)
: undefined,
activeAnimeLanguageProfileId: values.activeAnimeLanguageProfileId
? Number(values.activeAnimeLanguageProfileId)
: undefined,
activeAnimeProfileName: animeProfileName ?? undefined,
activeAnimeDirectory: values.activeAnimeRootFolder,
is4k: values.is4k,
@@ -559,6 +581,54 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="activeLanguageProfileId"
className="text-label"
>
{intl.formatMessage(messages.languageprofile)}
<span className="text-red-500">*</span>
</label>
<div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm">
<Field
as="select"
id="activeLanguageProfileId"
name="activeLanguageProfileId"
disabled={!isValidated || isTesting}
>
<option value="">
{isTesting
? intl.formatMessage(
messages.loadinglanguageprofiles
)
: !isValidated
? intl.formatMessage(
messages.testFirstLanguageProfiles
)
: intl.formatMessage(
messages.selectLanguageProfile
)}
</option>
{testResponse.languageProfiles.length > 0 &&
testResponse.languageProfiles.map((language) => (
<option
key={`loaded-profile-${language.id}`}
value={language.id}
>
{language.name}
</option>
))}
</Field>
</div>
{errors.activeLanguageProfileId &&
touched.activeLanguageProfileId && (
<div className="error">
{errors.activeLanguageProfileId}
</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="activeAnimeProfileId" className="text-label">
{intl.formatMessage(messages.animequalityprofile)}
@@ -635,6 +705,53 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="activeAnimeLanguageProfileId"
className="text-label"
>
{intl.formatMessage(messages.animerootfolder)}
</label>
<div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm">
<Field
as="select"
id="activeAnimeLanguageProfileId"
name="activeAnimeLanguageProfileId"
disabled={!isValidated || isTesting}
>
<option value="">
{isTesting
? intl.formatMessage(
messages.loadinglanguageprofiles
)
: !isValidated
? intl.formatMessage(
messages.testFirstLanguageProfiles
)
: intl.formatMessage(
messages.selectLanguageProfile
)}
</option>
{testResponse.languageProfiles.length > 0 &&
testResponse.languageProfiles.map((language) => (
<option
key={`loaded-profile-${language.id}`}
value={language.id}
>
{language.name}
</option>
))}
</Field>
</div>
{errors.activeAnimeLanguageProfileId &&
touched.activeAnimeLanguageProfileId && (
<div className="error">
{errors.activeAnimeLanguageProfileId}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="enableSeasonFolders"