mirror of
https://github.com/sct/overseerr.git
synced 2025-12-27 16:46:29 +01:00
feat: radarr/sonarr tag support (#1366)
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { Listbox, Transition } from '@headlessui/react';
|
||||
import { isEqual } from 'lodash';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import type { OptionsType, OptionTypeBase } from 'react-select';
|
||||
import useSWR from 'swr';
|
||||
import type {
|
||||
ServiceCommonServer,
|
||||
@@ -13,6 +16,13 @@ import globalMessages from '../../../i18n/globalMessages';
|
||||
import { formatBytes } from '../../../utils/numberHelpers';
|
||||
import { SmallLoadingSpinner } from '../../Common/LoadingSpinner';
|
||||
|
||||
type OptionType = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const Select = dynamic(() => import('react-select'), { ssr: false });
|
||||
|
||||
const messages = defineMessages({
|
||||
advancedoptions: 'Advanced Options',
|
||||
destinationserver: 'Destination Server',
|
||||
@@ -23,12 +33,16 @@ const messages = defineMessages({
|
||||
folder: '{path} ({space})',
|
||||
requestas: 'Request As',
|
||||
languageprofile: 'Language Profile',
|
||||
tags: 'Tags',
|
||||
selecttags: 'Select tags',
|
||||
notagoptions: 'No Tags',
|
||||
});
|
||||
|
||||
export type RequestOverrides = {
|
||||
server?: number;
|
||||
profile?: number;
|
||||
folder?: string;
|
||||
tags?: number[];
|
||||
language?: number;
|
||||
user?: User;
|
||||
};
|
||||
@@ -77,6 +91,10 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
defaultOverrides?.language ?? -1
|
||||
);
|
||||
|
||||
const [selectedTags, setSelectedTags] = useState<number[]>(
|
||||
defaultOverrides?.tags ?? []
|
||||
);
|
||||
|
||||
const {
|
||||
data: serverData,
|
||||
isValidating,
|
||||
@@ -150,6 +168,9 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
? serverData.server.activeAnimeLanguageProfileId
|
||||
: serverData.server.activeLanguageProfileId)
|
||||
);
|
||||
const defaultTags = isAnime
|
||||
? serverData.server.activeAnimeTags
|
||||
: serverData.server.activeTags;
|
||||
|
||||
if (
|
||||
defaultProfile &&
|
||||
@@ -174,46 +195,43 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
) {
|
||||
setSelectedLanguage(defaultLanguage.id);
|
||||
}
|
||||
|
||||
if (
|
||||
defaultTags &&
|
||||
!isEqual(defaultTags, selectedTags) &&
|
||||
(!defaultOverrides || defaultOverrides.tags === null)
|
||||
) {
|
||||
setSelectedTags(defaultTags);
|
||||
}
|
||||
}
|
||||
}, [serverData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
defaultOverrides &&
|
||||
defaultOverrides.server !== null &&
|
||||
defaultOverrides.server !== undefined
|
||||
) {
|
||||
if (defaultOverrides && defaultOverrides.server != null) {
|
||||
setSelectedServer(defaultOverrides.server);
|
||||
}
|
||||
|
||||
if (
|
||||
defaultOverrides &&
|
||||
defaultOverrides.profile !== null &&
|
||||
defaultOverrides.profile !== undefined
|
||||
) {
|
||||
if (defaultOverrides && defaultOverrides.profile != null) {
|
||||
setSelectedProfile(defaultOverrides.profile);
|
||||
}
|
||||
|
||||
if (
|
||||
defaultOverrides &&
|
||||
defaultOverrides.folder !== null &&
|
||||
defaultOverrides.folder !== undefined
|
||||
) {
|
||||
if (defaultOverrides && defaultOverrides.folder != null) {
|
||||
setSelectedFolder(defaultOverrides.folder);
|
||||
}
|
||||
|
||||
if (
|
||||
defaultOverrides &&
|
||||
defaultOverrides.language !== null &&
|
||||
defaultOverrides.language !== undefined
|
||||
) {
|
||||
if (defaultOverrides && defaultOverrides.language != null) {
|
||||
setSelectedLanguage(defaultOverrides.language);
|
||||
}
|
||||
|
||||
if (defaultOverrides && defaultOverrides.tags != null) {
|
||||
setSelectedTags(defaultOverrides.tags);
|
||||
}
|
||||
}, [
|
||||
defaultOverrides?.server,
|
||||
defaultOverrides?.folder,
|
||||
defaultOverrides?.profile,
|
||||
defaultOverrides?.language,
|
||||
defaultOverrides?.tags,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -224,6 +242,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
server: selectedServer ?? undefined,
|
||||
user: selectedUser ?? undefined,
|
||||
language: selectedLanguage ?? undefined,
|
||||
tags: selectedTags,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
@@ -232,6 +251,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
selectedProfile,
|
||||
selectedUser,
|
||||
selectedLanguage,
|
||||
selectedTags,
|
||||
]);
|
||||
|
||||
if (!data && !error) {
|
||||
@@ -436,9 +456,43 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!!data && selectedServer !== null && (
|
||||
<div className="mt-0 sm:mt-2">
|
||||
<label htmlFor="tags">{intl.formatMessage(messages.tags)}</label>
|
||||
<Select
|
||||
name="tags"
|
||||
options={(serverData?.tags ?? []).map((tag) => ({
|
||||
label: tag.label,
|
||||
value: tag.id,
|
||||
}))}
|
||||
isMulti
|
||||
placeholder={intl.formatMessage(messages.selecttags)}
|
||||
className="react-select-container react-select-container-dark"
|
||||
classNamePrefix="react-select"
|
||||
value={selectedTags.map((tagId) => {
|
||||
const foundTag = serverData?.tags.find(
|
||||
(tag) => tag.id === tagId
|
||||
);
|
||||
return {
|
||||
value: foundTag?.id,
|
||||
label: foundTag?.label,
|
||||
};
|
||||
})}
|
||||
onChange={(
|
||||
value: OptionTypeBase | OptionsType<OptionType> | null
|
||||
) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
setSelectedTags(value?.map((option) => option.value));
|
||||
}}
|
||||
noOptionsMessage={() => intl.formatMessage(messages.notagoptions)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{hasPermission([Permission.MANAGE_REQUESTS, Permission.MANAGE_USERS]) &&
|
||||
selectedUser && (
|
||||
<div className="first:mt-0 sm:mt-4">
|
||||
<div className="mt-2 first:mt-0">
|
||||
<Listbox
|
||||
as="div"
|
||||
value={selectedUser}
|
||||
|
||||
@@ -84,6 +84,7 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
||||
profileId: requestOverrides.profile,
|
||||
rootFolder: requestOverrides.folder,
|
||||
userId: requestOverrides.user?.id,
|
||||
tags: requestOverrides.tags,
|
||||
};
|
||||
}
|
||||
const response = await axios.post<MediaRequest>('/api/v1/request', {
|
||||
@@ -173,6 +174,7 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
||||
profileId: requestOverrides?.profile,
|
||||
rootFolder: requestOverrides?.folder,
|
||||
userId: requestOverrides?.user?.id,
|
||||
tags: requestOverrides?.tags,
|
||||
});
|
||||
|
||||
addToast(
|
||||
@@ -254,6 +256,7 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
||||
folder: editRequest.rootFolder,
|
||||
profile: editRequest.profileId,
|
||||
server: editRequest.serverId,
|
||||
tags: editRequest.tags,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
import { SonarrSeries } from '../../../../server/api/sonarr';
|
||||
import { SonarrSeries } from '../../../../server/api/servarr/sonarr';
|
||||
import globalMessages from '../../../i18n/globalMessages';
|
||||
import Alert from '../../Common/Alert';
|
||||
import { SmallLoadingSpinner } from '../../Common/LoadingSpinner';
|
||||
|
||||
@@ -107,6 +107,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
rootFolder: requestOverrides?.folder,
|
||||
languageProfileId: requestOverrides?.language,
|
||||
userId: requestOverrides?.user?.id,
|
||||
tags: requestOverrides?.tags,
|
||||
seasons: selectedSeasons,
|
||||
});
|
||||
} else {
|
||||
@@ -170,6 +171,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
rootFolder: requestOverrides.folder,
|
||||
languageProfileId: requestOverrides.language,
|
||||
userId: requestOverrides?.user?.id,
|
||||
tags: requestOverrides.tags,
|
||||
};
|
||||
}
|
||||
const response = await axios.post<MediaRequest>('/api/v1/request', {
|
||||
@@ -669,6 +671,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
profile: editRequest.profileId,
|
||||
server: editRequest.serverId,
|
||||
language: editRequest.languageProfileId,
|
||||
tags: editRequest.tags,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import axios from 'axios';
|
||||
import { Field, Formik } from 'formik';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import type { OptionsType, OptionTypeBase } from 'react-select';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import * as Yup from 'yup';
|
||||
import type { RadarrSettings } from '../../../../server/lib/settings';
|
||||
@@ -9,9 +11,18 @@ import globalMessages from '../../../i18n/globalMessages';
|
||||
import Modal from '../../Common/Modal';
|
||||
import Transition from '../../Transition';
|
||||
|
||||
type OptionType = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const Select = dynamic(() => import('react-select'), { ssr: false });
|
||||
|
||||
const messages = defineMessages({
|
||||
createradarr: 'Add New Radarr Server',
|
||||
create4kradarr: 'Add New 4K Radarr Server',
|
||||
editradarr: 'Edit Radarr Server',
|
||||
edit4kradarr: 'Edit 4K Radarr Server',
|
||||
validationNameRequired: 'You must provide a server name',
|
||||
validationHostnameRequired: 'You must provide a hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
@@ -24,6 +35,7 @@ const messages = defineMessages({
|
||||
toastRadarrTestFailure: 'Failed to connect to Radarr.',
|
||||
add: 'Add Server',
|
||||
defaultserver: 'Default Server',
|
||||
default4kserver: 'Default 4K Server',
|
||||
servername: 'Server Name',
|
||||
servernamePlaceholder: 'A Radarr Server',
|
||||
hostname: 'Hostname or IP Address',
|
||||
@@ -47,11 +59,15 @@ const messages = defineMessages({
|
||||
testFirstQualityProfiles: 'Test connection to load quality profiles',
|
||||
loadingrootfolders: 'Loading root folders…',
|
||||
testFirstRootFolders: 'Test connection to load root folders',
|
||||
testFirstTags: 'Test connection to load tags',
|
||||
tags: 'Tags',
|
||||
preventSearch: 'Disable Auto-Search',
|
||||
validationApplicationUrl: 'You must provide a valid URL',
|
||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
validationBaseUrlLeadingSlash: 'Base URL must have a leading slash',
|
||||
validationBaseUrlTrailingSlash: 'Base URL must not end in a trailing slash',
|
||||
notagoptions: 'No Tags',
|
||||
selecttags: 'Select tags',
|
||||
});
|
||||
|
||||
interface TestResponse {
|
||||
@@ -63,6 +79,10 @@ interface TestResponse {
|
||||
id: number;
|
||||
path: string;
|
||||
}[];
|
||||
tags: {
|
||||
id: number;
|
||||
label: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface RadarrModalProps {
|
||||
@@ -84,6 +104,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
const [testResponse, setTestResponse] = useState<TestResponse>({
|
||||
profiles: [],
|
||||
rootFolders: [],
|
||||
tags: [],
|
||||
});
|
||||
const RadarrSettingsSchema = Yup.object().shape({
|
||||
name: Yup.string().required(
|
||||
@@ -92,7 +113,6 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
hostname: Yup.string()
|
||||
.required(intl.formatMessage(messages.validationHostnameRequired))
|
||||
.matches(
|
||||
// eslint-disable-next-line
|
||||
/^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i,
|
||||
intl.formatMessage(messages.validationHostnameRequired)
|
||||
),
|
||||
@@ -194,7 +214,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
initialLoad.current = true;
|
||||
}
|
||||
},
|
||||
[addToast]
|
||||
[addToast, intl]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -231,6 +251,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
activeProfileId: radarr?.activeProfileId,
|
||||
rootFolder: radarr?.activeDirectory,
|
||||
minimumAvailability: radarr?.minimumAvailability ?? 'released',
|
||||
tags: radarr?.tags ?? [],
|
||||
isDefault: radarr?.isDefault ?? false,
|
||||
is4k: radarr?.is4k ?? false,
|
||||
externalUrl: radarr?.externalUrl,
|
||||
@@ -256,6 +277,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
activeDirectory: values.rootFolder,
|
||||
is4k: values.is4k,
|
||||
minimumAvailability: values.minimumAvailability,
|
||||
tags: values.tags,
|
||||
isDefault: values.isDefault,
|
||||
externalUrl: values.externalUrl,
|
||||
syncEnabled: values.syncEnabled,
|
||||
@@ -324,14 +346,24 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
onOk={() => handleSubmit()}
|
||||
title={
|
||||
!radarr
|
||||
? intl.formatMessage(messages.createradarr)
|
||||
: intl.formatMessage(messages.editradarr)
|
||||
? intl.formatMessage(
|
||||
values.is4k
|
||||
? messages.create4kradarr
|
||||
: messages.createradarr
|
||||
)
|
||||
: intl.formatMessage(
|
||||
values.is4k ? messages.edit4kradarr : messages.editradarr
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<div className="form-row">
|
||||
<label htmlFor="isDefault" className="checkbox-label">
|
||||
{intl.formatMessage(messages.defaultserver)}
|
||||
{intl.formatMessage(
|
||||
values.is4k
|
||||
? messages.default4kserver
|
||||
: messages.defaultserver
|
||||
)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Field type="checkbox" id="isDefault" name="isDefault" />
|
||||
@@ -584,6 +616,55 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="tags" className="text-label">
|
||||
{intl.formatMessage(messages.tags)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Select
|
||||
options={
|
||||
isValidated
|
||||
? testResponse.tags.map((tag) => ({
|
||||
label: tag.label,
|
||||
value: tag.id,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
isMulti
|
||||
isDisabled={!isValidated}
|
||||
placeholder={
|
||||
!isValidated
|
||||
? intl.formatMessage(messages.testFirstTags)
|
||||
: intl.formatMessage(messages.selecttags)
|
||||
}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
value={values.tags.map((tagId) => {
|
||||
const foundTag = testResponse.tags.find(
|
||||
(tag) => tag.id === tagId
|
||||
);
|
||||
return {
|
||||
value: foundTag?.id,
|
||||
label: foundTag?.label,
|
||||
};
|
||||
})}
|
||||
onChange={(
|
||||
value: OptionTypeBase | OptionsType<OptionType> | null
|
||||
) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
setFieldValue(
|
||||
'tags',
|
||||
value?.map((option) => option.value)
|
||||
);
|
||||
}}
|
||||
noOptionsMessage={() =>
|
||||
intl.formatMessage(messages.notagoptions)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="externalUrl" className="text-label">
|
||||
{intl.formatMessage(messages.externalUrl)}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import axios from 'axios';
|
||||
import { Field, Formik } from 'formik';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import type { OptionsType, OptionTypeBase } from 'react-select';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import * as Yup from 'yup';
|
||||
import type { SonarrSettings } from '../../../../server/lib/settings';
|
||||
@@ -9,9 +11,18 @@ import globalMessages from '../../../i18n/globalMessages';
|
||||
import Modal from '../../Common/Modal';
|
||||
import Transition from '../../Transition';
|
||||
|
||||
type OptionType = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const Select = dynamic(() => import('react-select'), { ssr: false });
|
||||
|
||||
const messages = defineMessages({
|
||||
createsonarr: 'Add New Sonarr Server',
|
||||
create4ksonarr: 'Add New 4K Sonarr Server',
|
||||
editsonarr: 'Edit Sonarr Server',
|
||||
edit4ksonarr: 'Edit 4K Sonarr Server',
|
||||
validationNameRequired: 'You must provide a server name',
|
||||
validationHostnameRequired: 'You must provide a hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
@@ -23,6 +34,7 @@ const messages = defineMessages({
|
||||
toastSonarrTestFailure: 'Failed to connect to Sonarr.',
|
||||
add: 'Add Server',
|
||||
defaultserver: 'Default Server',
|
||||
default4kserver: 'Default 4K Server',
|
||||
servername: 'Server Name',
|
||||
servernamePlaceholder: 'A Sonarr Server',
|
||||
hostname: 'Hostname or IP Address',
|
||||
@@ -49,6 +61,8 @@ const messages = defineMessages({
|
||||
testFirstRootFolders: 'Test connection to load root folders',
|
||||
loadinglanguageprofiles: 'Loading language profiles…',
|
||||
testFirstLanguageProfiles: 'Test connection to load language profiles',
|
||||
loadingTags: 'Loading tags…',
|
||||
testFirstTags: 'Test connection to load tags',
|
||||
syncEnabled: 'Enable Scan',
|
||||
externalUrl: 'External URL',
|
||||
externalUrlPlaceholder: 'External URL pointing to your Sonarr server',
|
||||
@@ -57,6 +71,10 @@ const messages = defineMessages({
|
||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
validationBaseUrlLeadingSlash: 'Base URL must have a leading slash',
|
||||
validationBaseUrlTrailingSlash: 'Base URL must not end in a trailing slash',
|
||||
tags: 'Tags',
|
||||
animeTags: 'Tags',
|
||||
notagoptions: 'No Tags',
|
||||
selecttags: 'Select tags',
|
||||
});
|
||||
|
||||
interface TestResponse {
|
||||
@@ -72,6 +90,10 @@ interface TestResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
tags: {
|
||||
id: number;
|
||||
label: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface SonarrModalProps {
|
||||
@@ -94,6 +116,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
profiles: [],
|
||||
rootFolders: [],
|
||||
languageProfiles: [],
|
||||
tags: [],
|
||||
});
|
||||
const SonarrSettingsSchema = Yup.object().shape({
|
||||
name: Yup.string().required(
|
||||
@@ -102,7 +125,6 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
hostname: Yup.string()
|
||||
.required(intl.formatMessage(messages.validationHostnameRequired))
|
||||
.matches(
|
||||
// eslint-disable-next-line
|
||||
/^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i,
|
||||
intl.formatMessage(messages.validationHostnameRequired)
|
||||
),
|
||||
@@ -204,7 +226,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
initialLoad.current = true;
|
||||
}
|
||||
},
|
||||
[addToast]
|
||||
[addToast, intl]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -244,6 +266,8 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
activeAnimeProfileId: sonarr?.activeAnimeProfileId,
|
||||
activeAnimeLanguageProfileId: sonarr?.activeAnimeLanguageProfileId,
|
||||
activeAnimeRootFolder: sonarr?.activeAnimeDirectory,
|
||||
tags: sonarr?.tags ?? [],
|
||||
animeTags: sonarr?.animeTags ?? [],
|
||||
isDefault: sonarr?.isDefault ?? false,
|
||||
is4k: sonarr?.is4k ?? false,
|
||||
enableSeasonFolders: sonarr?.enableSeasonFolders ?? false,
|
||||
@@ -282,6 +306,8 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
: undefined,
|
||||
activeAnimeProfileName: animeProfileName ?? undefined,
|
||||
activeAnimeDirectory: values.activeAnimeRootFolder,
|
||||
tags: values.tags,
|
||||
animeTags: values.animeTags,
|
||||
is4k: values.is4k,
|
||||
isDefault: values.isDefault,
|
||||
enableSeasonFolders: values.enableSeasonFolders,
|
||||
@@ -352,14 +378,24 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
onOk={() => handleSubmit()}
|
||||
title={
|
||||
!sonarr
|
||||
? intl.formatMessage(messages.createsonarr)
|
||||
: intl.formatMessage(messages.editsonarr)
|
||||
? intl.formatMessage(
|
||||
values.is4k
|
||||
? messages.create4ksonarr
|
||||
: messages.createsonarr
|
||||
)
|
||||
: intl.formatMessage(
|
||||
values.is4k ? messages.edit4ksonarr : messages.editsonarr
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<div className="form-row">
|
||||
<label htmlFor="isDefault" className="checkbox-label">
|
||||
{intl.formatMessage(messages.defaultserver)}
|
||||
{intl.formatMessage(
|
||||
values.is4k
|
||||
? messages.default4kserver
|
||||
: messages.defaultserver
|
||||
)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Field type="checkbox" id="isDefault" name="isDefault" />
|
||||
@@ -634,6 +670,62 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="tags" className="text-label">
|
||||
{intl.formatMessage(messages.tags)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Select
|
||||
options={
|
||||
isValidated
|
||||
? testResponse.tags.map((tag) => ({
|
||||
label: tag.label,
|
||||
value: tag.id,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
isMulti
|
||||
isDisabled={!isValidated}
|
||||
placeholder={
|
||||
!isValidated
|
||||
? intl.formatMessage(messages.testFirstTags)
|
||||
: isTesting
|
||||
? intl.formatMessage(messages.loadingTags)
|
||||
: intl.formatMessage(messages.selecttags)
|
||||
}
|
||||
isLoading={isTesting}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
value={
|
||||
isTesting
|
||||
? []
|
||||
: values.tags.map((tagId) => {
|
||||
const foundTag = testResponse.tags.find(
|
||||
(tag) => tag.id === tagId
|
||||
);
|
||||
return {
|
||||
value: foundTag?.id,
|
||||
label: foundTag?.label,
|
||||
};
|
||||
})
|
||||
}
|
||||
onChange={(
|
||||
value: OptionTypeBase | OptionsType<OptionType> | null
|
||||
) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
setFieldValue(
|
||||
'tags',
|
||||
value?.map((option) => option.value)
|
||||
);
|
||||
}}
|
||||
noOptionsMessage={() =>
|
||||
intl.formatMessage(messages.notagoptions)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="activeAnimeProfileId" className="text-label">
|
||||
{intl.formatMessage(messages.animequalityprofile)}
|
||||
@@ -757,6 +849,62 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="tags" className="text-label">
|
||||
{intl.formatMessage(messages.animeTags)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Select
|
||||
options={
|
||||
isValidated
|
||||
? testResponse.tags.map((tag) => ({
|
||||
label: tag.label,
|
||||
value: tag.id,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
isMulti
|
||||
isDisabled={!isValidated}
|
||||
placeholder={
|
||||
!isValidated
|
||||
? intl.formatMessage(messages.testFirstTags)
|
||||
: isTesting
|
||||
? intl.formatMessage(messages.loadingTags)
|
||||
: intl.formatMessage(messages.selecttags)
|
||||
}
|
||||
isLoading={isTesting}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
value={
|
||||
isTesting
|
||||
? []
|
||||
: values.animeTags.map((tagId) => {
|
||||
const foundTag = testResponse.tags.find(
|
||||
(tag) => tag.id === tagId
|
||||
);
|
||||
return {
|
||||
value: foundTag?.id,
|
||||
label: foundTag?.label,
|
||||
};
|
||||
})
|
||||
}
|
||||
onChange={(
|
||||
value: OptionTypeBase | OptionsType<OptionType> | null
|
||||
) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
setFieldValue(
|
||||
'animeTags',
|
||||
value?.map((option) => option.value)
|
||||
);
|
||||
}}
|
||||
noOptionsMessage={() =>
|
||||
intl.formatMessage(messages.notagoptions)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="enableSeasonFolders"
|
||||
|
||||
Reference in New Issue
Block a user