mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(ui): add option to only allow complete series requests (#1164)
This commit is contained in:
@@ -125,6 +125,9 @@ components:
|
|||||||
hideAvailable:
|
hideAvailable:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
|
partialRequestsEnabled:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
localLogin:
|
localLogin:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
|
@@ -14,6 +14,7 @@ export interface PublicSettingsResponse {
|
|||||||
series4kEnabled: boolean;
|
series4kEnabled: boolean;
|
||||||
region: string;
|
region: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
|
partialRequestsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CacheItem {
|
export interface CacheItem {
|
||||||
|
@@ -72,6 +72,7 @@ export interface MainSettings {
|
|||||||
region: string;
|
region: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
trustProxy: boolean;
|
trustProxy: boolean;
|
||||||
|
partialRequestsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublicSettings {
|
interface PublicSettings {
|
||||||
@@ -86,6 +87,7 @@ interface FullPublicSettings extends PublicSettings {
|
|||||||
series4kEnabled: boolean;
|
series4kEnabled: boolean;
|
||||||
region: string;
|
region: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
|
partialRequestsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotificationAgentConfig {
|
export interface NotificationAgentConfig {
|
||||||
@@ -199,6 +201,7 @@ class Settings {
|
|||||||
region: '',
|
region: '',
|
||||||
originalLanguage: '',
|
originalLanguage: '',
|
||||||
trustProxy: false,
|
trustProxy: false,
|
||||||
|
partialRequestsEnabled: true,
|
||||||
},
|
},
|
||||||
plex: {
|
plex: {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -345,6 +348,7 @@ class Settings {
|
|||||||
),
|
),
|
||||||
region: this.data.main.region,
|
region: this.data.main.region,
|
||||||
originalLanguage: this.data.main.originalLanguage,
|
originalLanguage: this.data.main.originalLanguage,
|
||||||
|
partialRequestsEnabled: this.data.main.partialRequestsEnabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,6 +19,7 @@ import SeasonRequest from '../../../server/entity/SeasonRequest';
|
|||||||
import Alert from '../Common/Alert';
|
import Alert from '../Common/Alert';
|
||||||
import AdvancedRequester, { RequestOverrides } from './AdvancedRequester';
|
import AdvancedRequester, { RequestOverrides } from './AdvancedRequester';
|
||||||
import SearchByNameModal from './SearchByNameModal';
|
import SearchByNameModal from './SearchByNameModal';
|
||||||
|
import useSettings from '../../hooks/useSettings';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
requestadmin: 'Your request will be immediately approved.',
|
requestadmin: 'Your request will be immediately approved.',
|
||||||
@@ -30,7 +31,9 @@ const messages = defineMessages({
|
|||||||
requesting: 'Requesting…',
|
requesting: 'Requesting…',
|
||||||
requestseasons:
|
requestseasons:
|
||||||
'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}',
|
'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',
|
season: 'Season',
|
||||||
numberofepisodes: '# of Episodes',
|
numberofepisodes: '# of Episodes',
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
@@ -63,6 +66,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
editRequest,
|
editRequest,
|
||||||
is4k = false,
|
is4k = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const settings = useSettings();
|
||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
const editingSeasons: number[] = (editRequest?.seasons ?? []).map(
|
const editingSeasons: number[] = (editRequest?.seasons ?? []).map(
|
||||||
(season) => season.seasonNumber
|
(season) => season.seasonNumber
|
||||||
@@ -135,9 +139,13 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sendRequest = async () => {
|
const sendRequest = async () => {
|
||||||
if (selectedSeasons.length === 0) {
|
if (
|
||||||
|
settings.currentSettings.partialRequestsEnabled &&
|
||||||
|
selectedSeasons.length === 0
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onUpdating) {
|
if (onUpdating) {
|
||||||
onUpdating(true);
|
onUpdating(true);
|
||||||
}
|
}
|
||||||
@@ -158,7 +166,11 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
tvdbId: tvdbId ?? data?.externalIds.tvdbId,
|
tvdbId: tvdbId ?? data?.externalIds.tvdbId,
|
||||||
mediaType: 'tv',
|
mediaType: 'tv',
|
||||||
is4k,
|
is4k,
|
||||||
seasons: selectedSeasons,
|
seasons: settings.currentSettings.partialRequestsEnabled
|
||||||
|
? selectedSeasons
|
||||||
|
: getAllSeasons().filter(
|
||||||
|
(season) => !getAllRequestedSeasons().includes(season)
|
||||||
|
),
|
||||||
...overrideParams,
|
...overrideParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -190,6 +202,12 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAllSeasons = (): number[] => {
|
||||||
|
return (data?.seasons ?? [])
|
||||||
|
.filter((season) => season.seasonNumber !== 0)
|
||||||
|
.map((season) => season.seasonNumber);
|
||||||
|
};
|
||||||
|
|
||||||
const getAllRequestedSeasons = (): number[] => {
|
const getAllRequestedSeasons = (): number[] => {
|
||||||
const requestedSeasons = (data?.mediaInfo?.requests ?? [])
|
const requestedSeasons = (data?.mediaInfo?.requests ?? [])
|
||||||
.filter(
|
.filter(
|
||||||
@@ -243,19 +261,14 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
data &&
|
data &&
|
||||||
selectedSeasons.length >= 0 &&
|
selectedSeasons.length >= 0 &&
|
||||||
selectedSeasons.length <
|
selectedSeasons.length <
|
||||||
data?.seasons
|
getAllSeasons().filter(
|
||||||
.filter((season) => season.seasonNumber !== 0)
|
(season) => !getAllRequestedSeasons().includes(season)
|
||||||
.filter(
|
).length
|
||||||
(season) => !getAllRequestedSeasons().includes(season.seasonNumber)
|
|
||||||
).length
|
|
||||||
) {
|
) {
|
||||||
setSelectedSeasons(
|
setSelectedSeasons(
|
||||||
data.seasons
|
getAllSeasons().filter(
|
||||||
.filter((season) => season.seasonNumber !== 0)
|
(season) => !getAllRequestedSeasons().includes(season)
|
||||||
.filter(
|
)
|
||||||
(season) => !getAllRequestedSeasons().includes(season.seasonNumber)
|
|
||||||
)
|
|
||||||
.map((season) => season.seasonNumber)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setSelectedSeasons([]);
|
setSelectedSeasons([]);
|
||||||
@@ -268,11 +281,9 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
selectedSeasons.length ===
|
selectedSeasons.length ===
|
||||||
data.seasons
|
getAllSeasons().filter(
|
||||||
.filter((season) => season.seasonNumber !== 0)
|
(season) => !getAllRequestedSeasons().includes(season)
|
||||||
.filter(
|
).length
|
||||||
(season) => !getAllRequestedSeasons().includes(season.seasonNumber)
|
|
||||||
).length
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -329,15 +340,29 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
okText={
|
okText={
|
||||||
editRequest && selectedSeasons.length === 0
|
editRequest && selectedSeasons.length === 0
|
||||||
? 'Cancel Request'
|
? 'Cancel Request'
|
||||||
|
: getAllRequestedSeasons().length >= getAllSeasons().length
|
||||||
|
? intl.formatMessage(messages.alreadyrequested)
|
||||||
|
: !settings.currentSettings.partialRequestsEnabled
|
||||||
|
? intl.formatMessage(messages.requestall)
|
||||||
: selectedSeasons.length === 0
|
: selectedSeasons.length === 0
|
||||||
? intl.formatMessage(messages.selectseason)
|
? intl.formatMessage(messages.selectseason)
|
||||||
: intl.formatMessage(messages.requestseasons, {
|
: intl.formatMessage(messages.requestseasons, {
|
||||||
seasonCount: selectedSeasons.length,
|
seasonCount: selectedSeasons.length,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
okDisabled={editRequest ? false : selectedSeasons.length === 0}
|
okDisabled={
|
||||||
|
editRequest
|
||||||
|
? false
|
||||||
|
: getAllRequestedSeasons().length >= getAllSeasons().length ||
|
||||||
|
(settings.currentSettings.partialRequestsEnabled &&
|
||||||
|
selectedSeasons.length === 0)
|
||||||
|
}
|
||||||
okButtonType={
|
okButtonType={
|
||||||
editRequest && selectedSeasons.length === 0 ? 'danger' : `primary`
|
editRequest &&
|
||||||
|
settings.currentSettings.partialRequestsEnabled &&
|
||||||
|
selectedSeasons.length === 0
|
||||||
|
? 'danger'
|
||||||
|
: `primary`
|
||||||
}
|
}
|
||||||
cancelText={
|
cancelText={
|
||||||
tvdbId
|
tvdbId
|
||||||
@@ -361,13 +386,15 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(hasPermission(Permission.MANAGE_REQUESTS) ||
|
{hasPermission(
|
||||||
hasPermission(
|
[
|
||||||
is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE
|
Permission.MANAGE_REQUESTS,
|
||||||
) ||
|
is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE,
|
||||||
hasPermission(
|
is4k ? Permission.AUTO_APPROVE_4K_TV : Permission.AUTO_APPROVE_TV,
|
||||||
is4k ? Permission.AUTO_APPROVE_4K_TV : Permission.AUTO_APPROVE_TV
|
],
|
||||||
)) &&
|
{ type: 'or' }
|
||||||
|
) &&
|
||||||
|
getAllRequestedSeasons().length < getAllSeasons().length &&
|
||||||
!editRequest && (
|
!editRequest && (
|
||||||
<p className="mt-6">
|
<p className="mt-6">
|
||||||
<Alert
|
<Alert
|
||||||
@@ -385,7 +412,12 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
<table className="min-w-full">
|
<table className="min-w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="w-16 px-4 py-3 bg-gray-500">
|
<th
|
||||||
|
className={`w-16 px-4 py-3 bg-gray-500 ${
|
||||||
|
!settings.currentSettings.partialRequestsEnabled &&
|
||||||
|
'hidden'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -438,7 +470,12 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<tr key={`season-${season.id}`}>
|
<tr key={`season-${season.id}`}>
|
||||||
<td className="px-4 py-4 text-sm font-medium leading-5 text-gray-100 whitespace-nowrap">
|
<td
|
||||||
|
className={`px-4 py-4 text-sm font-medium leading-5 text-gray-100 whitespace-nowrap ${
|
||||||
|
!settings.currentSettings
|
||||||
|
.partialRequestsEnabled && 'hidden'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@@ -46,6 +46,7 @@ const messages = defineMessages({
|
|||||||
validationApplicationUrl: 'You must provide a valid URL',
|
validationApplicationUrl: 'You must provide a valid URL',
|
||||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||||
originalLanguageDefault: 'All Languages',
|
originalLanguageDefault: 'All Languages',
|
||||||
|
partialRequestsEnabled: 'Enable Partial Series Requests',
|
||||||
});
|
});
|
||||||
|
|
||||||
const SettingsMain: React.FC = () => {
|
const SettingsMain: React.FC = () => {
|
||||||
@@ -116,6 +117,7 @@ const SettingsMain: React.FC = () => {
|
|||||||
hideAvailable: data?.hideAvailable,
|
hideAvailable: data?.hideAvailable,
|
||||||
region: data?.region,
|
region: data?.region,
|
||||||
originalLanguage: data?.originalLanguage,
|
originalLanguage: data?.originalLanguage,
|
||||||
|
partialRequestsEnabled: data?.partialRequestsEnabled,
|
||||||
trustProxy: data?.trustProxy,
|
trustProxy: data?.trustProxy,
|
||||||
}}
|
}}
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
@@ -129,6 +131,7 @@ const SettingsMain: React.FC = () => {
|
|||||||
hideAvailable: values.hideAvailable,
|
hideAvailable: values.hideAvailable,
|
||||||
region: values.region,
|
region: values.region,
|
||||||
originalLanguage: values.originalLanguage,
|
originalLanguage: values.originalLanguage,
|
||||||
|
partialRequestsEnabled: values.partialRequestsEnabled,
|
||||||
trustProxy: values.trustProxy,
|
trustProxy: values.trustProxy,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -338,6 +341,29 @@ const SettingsMain: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<label
|
||||||
|
htmlFor="partialRequestsEnabled"
|
||||||
|
className="checkbox-label"
|
||||||
|
>
|
||||||
|
<span className="mr-2">
|
||||||
|
{intl.formatMessage(messages.partialRequestsEnabled)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div className="form-input">
|
||||||
|
<Field
|
||||||
|
type="checkbox"
|
||||||
|
id="partialRequestsEnabled"
|
||||||
|
name="partialRequestsEnabled"
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue(
|
||||||
|
'partialRequestsEnabled',
|
||||||
|
!values.partialRequestsEnabled
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||||
|
@@ -10,11 +10,12 @@ const defaultSettings = {
|
|||||||
initialized: false,
|
initialized: false,
|
||||||
applicationTitle: 'Overseerr',
|
applicationTitle: 'Overseerr',
|
||||||
hideAvailable: false,
|
hideAvailable: false,
|
||||||
localLogin: false,
|
localLogin: true,
|
||||||
movie4kEnabled: false,
|
movie4kEnabled: false,
|
||||||
series4kEnabled: false,
|
series4kEnabled: false,
|
||||||
region: '',
|
region: '',
|
||||||
originalLanguage: '',
|
originalLanguage: '',
|
||||||
|
partialRequestsEnabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsContext = React.createContext<SettingsContextProps>({
|
export const SettingsContext = React.createContext<SettingsContextProps>({
|
||||||
|
@@ -213,6 +213,7 @@
|
|||||||
"components.RequestModal.SearchByNameModal.nosummary": "No summary for this title was found.",
|
"components.RequestModal.SearchByNameModal.nosummary": "No summary for this title was found.",
|
||||||
"components.RequestModal.SearchByNameModal.notvdbid": "Manual Match Required",
|
"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.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.autoapproval": "Automatic Approval",
|
||||||
"components.RequestModal.backbutton": "Back",
|
"components.RequestModal.backbutton": "Back",
|
||||||
"components.RequestModal.cancel": "Cancel Request",
|
"components.RequestModal.cancel": "Cancel Request",
|
||||||
@@ -233,6 +234,7 @@
|
|||||||
"components.RequestModal.requestCancel": "Request for <strong>{title}</strong> canceled.",
|
"components.RequestModal.requestCancel": "Request for <strong>{title}</strong> canceled.",
|
||||||
"components.RequestModal.requestSuccess": "<strong>{title}</strong> requested.",
|
"components.RequestModal.requestSuccess": "<strong>{title}</strong> requested.",
|
||||||
"components.RequestModal.requestadmin": "Your request will be immediately approved.",
|
"components.RequestModal.requestadmin": "Your request will be immediately approved.",
|
||||||
|
"components.RequestModal.requestall": "Request All Seasons",
|
||||||
"components.RequestModal.requestcancelled": "Request canceled.",
|
"components.RequestModal.requestcancelled": "Request canceled.",
|
||||||
"components.RequestModal.requestedited": "Request edited.",
|
"components.RequestModal.requestedited": "Request edited.",
|
||||||
"components.RequestModal.requesterror": "Something went wrong while submitting the request.",
|
"components.RequestModal.requesterror": "Something went wrong while submitting the request.",
|
||||||
@@ -242,7 +244,7 @@
|
|||||||
"components.RequestModal.requesttitle": "Request {title}",
|
"components.RequestModal.requesttitle": "Request {title}",
|
||||||
"components.RequestModal.season": "Season",
|
"components.RequestModal.season": "Season",
|
||||||
"components.RequestModal.seasonnumber": "Season {number}",
|
"components.RequestModal.seasonnumber": "Season {number}",
|
||||||
"components.RequestModal.selectseason": "Select season(s)",
|
"components.RequestModal.selectseason": "Select Season(s)",
|
||||||
"components.RequestModal.status": "Status",
|
"components.RequestModal.status": "Status",
|
||||||
"components.ResetPassword.confirmpassword": "Confirm Password",
|
"components.ResetPassword.confirmpassword": "Confirm Password",
|
||||||
"components.ResetPassword.email": "Email Address",
|
"components.ResetPassword.email": "Email Address",
|
||||||
@@ -557,6 +559,7 @@
|
|||||||
"components.Settings.originalLanguageDefault": "All Languages",
|
"components.Settings.originalLanguageDefault": "All Languages",
|
||||||
"components.Settings.originallanguage": "Discover Language",
|
"components.Settings.originallanguage": "Discover Language",
|
||||||
"components.Settings.originallanguageTip": "Filter content by original language (only applies to the \"Popular\" and \"Upcoming\" categories)",
|
"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.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.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",
|
"components.Settings.plexsettings": "Plex Settings",
|
||||||
|
@@ -148,6 +148,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
localLogin: true,
|
localLogin: true,
|
||||||
region: '',
|
region: '',
|
||||||
originalLanguage: '',
|
originalLanguage: '',
|
||||||
|
partialRequestsEnabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let locale = 'en';
|
let locale = 'en';
|
||||||
|
Reference in New Issue
Block a user