diff --git a/overseerr-api.yml b/overseerr-api.yml index dd11cd9ea..e436aa628 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -125,6 +125,9 @@ components: hideAvailable: type: boolean example: false + partialRequestsEnabled: + type: boolean + example: false localLogin: type: boolean example: true diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 122df7bd1..b687d5977 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -14,6 +14,7 @@ export interface PublicSettingsResponse { series4kEnabled: boolean; region: string; originalLanguage: string; + partialRequestsEnabled: boolean; } export interface CacheItem { diff --git a/server/lib/settings.ts b/server/lib/settings.ts index ad0c3dbad..1b4321187 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -72,6 +72,7 @@ export interface MainSettings { region: string; originalLanguage: string; trustProxy: boolean; + partialRequestsEnabled: boolean; } interface PublicSettings { @@ -86,6 +87,7 @@ interface FullPublicSettings extends PublicSettings { series4kEnabled: boolean; region: string; originalLanguage: string; + partialRequestsEnabled: boolean; } export interface NotificationAgentConfig { @@ -199,6 +201,7 @@ class Settings { region: '', originalLanguage: '', trustProxy: false, + partialRequestsEnabled: true, }, plex: { name: '', @@ -345,6 +348,7 @@ class Settings { ), region: this.data.main.region, originalLanguage: this.data.main.originalLanguage, + partialRequestsEnabled: this.data.main.partialRequestsEnabled, }; } diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 37c16e82a..8aa031651 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -19,6 +19,7 @@ import SeasonRequest from '../../../server/entity/SeasonRequest'; import Alert from '../Common/Alert'; import AdvancedRequester, { RequestOverrides } from './AdvancedRequester'; import SearchByNameModal from './SearchByNameModal'; +import useSettings from '../../hooks/useSettings'; const messages = defineMessages({ requestadmin: 'Your request will be immediately approved.', @@ -30,7 +31,9 @@ const messages = defineMessages({ requesting: 'Requesting…', requestseasons: 'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}', - selectseason: 'Select season(s)', + requestall: 'Request All Seasons', + alreadyrequested: 'Already Requested', + selectseason: 'Select Season(s)', season: 'Season', numberofepisodes: '# of Episodes', status: 'Status', @@ -63,6 +66,7 @@ const TvRequestModal: React.FC = ({ editRequest, is4k = false, }) => { + const settings = useSettings(); const { addToast } = useToasts(); const editingSeasons: number[] = (editRequest?.seasons ?? []).map( (season) => season.seasonNumber @@ -135,9 +139,13 @@ const TvRequestModal: React.FC = ({ }; const sendRequest = async () => { - if (selectedSeasons.length === 0) { + if ( + settings.currentSettings.partialRequestsEnabled && + selectedSeasons.length === 0 + ) { return; } + if (onUpdating) { onUpdating(true); } @@ -158,7 +166,11 @@ const TvRequestModal: React.FC = ({ tvdbId: tvdbId ?? data?.externalIds.tvdbId, mediaType: 'tv', is4k, - seasons: selectedSeasons, + seasons: settings.currentSettings.partialRequestsEnabled + ? selectedSeasons + : getAllSeasons().filter( + (season) => !getAllRequestedSeasons().includes(season) + ), ...overrideParams, }); @@ -190,6 +202,12 @@ const TvRequestModal: React.FC = ({ } }; + const getAllSeasons = (): number[] => { + return (data?.seasons ?? []) + .filter((season) => season.seasonNumber !== 0) + .map((season) => season.seasonNumber); + }; + const getAllRequestedSeasons = (): number[] => { const requestedSeasons = (data?.mediaInfo?.requests ?? []) .filter( @@ -243,19 +261,14 @@ const TvRequestModal: React.FC = ({ data && selectedSeasons.length >= 0 && selectedSeasons.length < - data?.seasons - .filter((season) => season.seasonNumber !== 0) - .filter( - (season) => !getAllRequestedSeasons().includes(season.seasonNumber) - ).length + getAllSeasons().filter( + (season) => !getAllRequestedSeasons().includes(season) + ).length ) { setSelectedSeasons( - data.seasons - .filter((season) => season.seasonNumber !== 0) - .filter( - (season) => !getAllRequestedSeasons().includes(season.seasonNumber) - ) - .map((season) => season.seasonNumber) + getAllSeasons().filter( + (season) => !getAllRequestedSeasons().includes(season) + ) ); } else { setSelectedSeasons([]); @@ -268,11 +281,9 @@ const TvRequestModal: React.FC = ({ } return ( selectedSeasons.length === - data.seasons - .filter((season) => season.seasonNumber !== 0) - .filter( - (season) => !getAllRequestedSeasons().includes(season.seasonNumber) - ).length + getAllSeasons().filter( + (season) => !getAllRequestedSeasons().includes(season) + ).length ); }; @@ -329,15 +340,29 @@ const TvRequestModal: React.FC = ({ okText={ editRequest && selectedSeasons.length === 0 ? 'Cancel Request' + : getAllRequestedSeasons().length >= getAllSeasons().length + ? intl.formatMessage(messages.alreadyrequested) + : !settings.currentSettings.partialRequestsEnabled + ? intl.formatMessage(messages.requestall) : selectedSeasons.length === 0 ? intl.formatMessage(messages.selectseason) : intl.formatMessage(messages.requestseasons, { seasonCount: selectedSeasons.length, }) } - okDisabled={editRequest ? false : selectedSeasons.length === 0} + okDisabled={ + editRequest + ? false + : getAllRequestedSeasons().length >= getAllSeasons().length || + (settings.currentSettings.partialRequestsEnabled && + selectedSeasons.length === 0) + } okButtonType={ - editRequest && selectedSeasons.length === 0 ? 'danger' : `primary` + editRequest && + settings.currentSettings.partialRequestsEnabled && + selectedSeasons.length === 0 + ? 'danger' + : `primary` } cancelText={ tvdbId @@ -361,13 +386,15 @@ const TvRequestModal: React.FC = ({ } > - {(hasPermission(Permission.MANAGE_REQUESTS) || - hasPermission( - is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE - ) || - hasPermission( - is4k ? Permission.AUTO_APPROVE_4K_TV : Permission.AUTO_APPROVE_TV - )) && + {hasPermission( + [ + Permission.MANAGE_REQUESTS, + is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE, + is4k ? Permission.AUTO_APPROVE_4K_TV : Permission.AUTO_APPROVE_TV, + ], + { type: 'or' } + ) && + getAllRequestedSeasons().length < getAllSeasons().length && !editRequest && (

= ({ - -
+ = ({ ); return (
+ { @@ -116,6 +117,7 @@ const SettingsMain: React.FC = () => { hideAvailable: data?.hideAvailable, region: data?.region, originalLanguage: data?.originalLanguage, + partialRequestsEnabled: data?.partialRequestsEnabled, trustProxy: data?.trustProxy, }} enableReinitialize @@ -129,6 +131,7 @@ const SettingsMain: React.FC = () => { hideAvailable: values.hideAvailable, region: values.region, originalLanguage: values.originalLanguage, + partialRequestsEnabled: values.partialRequestsEnabled, trustProxy: values.trustProxy, }); @@ -338,6 +341,29 @@ const SettingsMain: React.FC = () => { /> +
+ +
+ { + setFieldValue( + 'partialRequestsEnabled', + !values.partialRequestsEnabled + ); + }} + /> +
+
diff --git a/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx index 121543474..f89dcacf1 100644 --- a/src/context/SettingsContext.tsx +++ b/src/context/SettingsContext.tsx @@ -10,11 +10,12 @@ const defaultSettings = { initialized: false, applicationTitle: 'Overseerr', hideAvailable: false, - localLogin: false, + localLogin: true, movie4kEnabled: false, series4kEnabled: false, region: '', originalLanguage: '', + partialRequestsEnabled: true, }; export const SettingsContext = React.createContext({ diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index e0f2437bd..4a51a2040 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -213,6 +213,7 @@ "components.RequestModal.SearchByNameModal.nosummary": "No summary for this title was found.", "components.RequestModal.SearchByNameModal.notvdbid": "Manual Match Required", "components.RequestModal.SearchByNameModal.notvdbiddescription": "We couldn't automatically match your request. Please select the correct match from the list below:", + "components.RequestModal.alreadyrequested": "Already Requested", "components.RequestModal.autoapproval": "Automatic Approval", "components.RequestModal.backbutton": "Back", "components.RequestModal.cancel": "Cancel Request", @@ -233,6 +234,7 @@ "components.RequestModal.requestCancel": "Request for {title} canceled.", "components.RequestModal.requestSuccess": "{title} requested.", "components.RequestModal.requestadmin": "Your request will be immediately approved.", + "components.RequestModal.requestall": "Request All Seasons", "components.RequestModal.requestcancelled": "Request canceled.", "components.RequestModal.requestedited": "Request edited.", "components.RequestModal.requesterror": "Something went wrong while submitting the request.", @@ -242,7 +244,7 @@ "components.RequestModal.requesttitle": "Request {title}", "components.RequestModal.season": "Season", "components.RequestModal.seasonnumber": "Season {number}", - "components.RequestModal.selectseason": "Select season(s)", + "components.RequestModal.selectseason": "Select Season(s)", "components.RequestModal.status": "Status", "components.ResetPassword.confirmpassword": "Confirm Password", "components.ResetPassword.email": "Email Address", @@ -557,6 +559,7 @@ "components.Settings.originalLanguageDefault": "All Languages", "components.Settings.originallanguage": "Discover Language", "components.Settings.originallanguageTip": "Filter content by original language (only applies to the \"Popular\" and \"Upcoming\" categories)", + "components.Settings.partialRequestsEnabled": "Enable Partial Series Requests", "components.Settings.plexlibraries": "Plex Libraries", "components.Settings.plexlibrariesDescription": "The libraries Overseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.", "components.Settings.plexsettings": "Plex Settings", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index dbf2b5859..f98240d97 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -148,6 +148,7 @@ CoreApp.getInitialProps = async (initialProps) => { localLogin: true, region: '', originalLanguage: '', + partialRequestsEnabled: true, }; let locale = 'en';