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:
|
||||
type: boolean
|
||||
example: false
|
||||
partialRequestsEnabled:
|
||||
type: boolean
|
||||
example: false
|
||||
localLogin:
|
||||
type: boolean
|
||||
example: true
|
||||
|
@@ -14,6 +14,7 @@ export interface PublicSettingsResponse {
|
||||
series4kEnabled: boolean;
|
||||
region: string;
|
||||
originalLanguage: string;
|
||||
partialRequestsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface CacheItem {
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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<RequestModalProps> = ({
|
||||
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<RequestModalProps> = ({
|
||||
};
|
||||
|
||||
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<RequestModalProps> = ({
|
||||
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<RequestModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
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<RequestModalProps> = ({
|
||||
data &&
|
||||
selectedSeasons.length >= 0 &&
|
||||
selectedSeasons.length <
|
||||
data?.seasons
|
||||
.filter((season) => season.seasonNumber !== 0)
|
||||
.filter(
|
||||
(season) => !getAllRequestedSeasons().includes(season.seasonNumber)
|
||||
getAllSeasons().filter(
|
||||
(season) => !getAllRequestedSeasons().includes(season)
|
||||
).length
|
||||
) {
|
||||
setSelectedSeasons(
|
||||
data.seasons
|
||||
.filter((season) => season.seasonNumber !== 0)
|
||||
.filter(
|
||||
(season) => !getAllRequestedSeasons().includes(season.seasonNumber)
|
||||
getAllSeasons().filter(
|
||||
(season) => !getAllRequestedSeasons().includes(season)
|
||||
)
|
||||
.map((season) => season.seasonNumber)
|
||||
);
|
||||
} else {
|
||||
setSelectedSeasons([]);
|
||||
@@ -268,10 +281,8 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
}
|
||||
return (
|
||||
selectedSeasons.length ===
|
||||
data.seasons
|
||||
.filter((season) => season.seasonNumber !== 0)
|
||||
.filter(
|
||||
(season) => !getAllRequestedSeasons().includes(season.seasonNumber)
|
||||
getAllSeasons().filter(
|
||||
(season) => !getAllRequestedSeasons().includes(season)
|
||||
).length
|
||||
);
|
||||
};
|
||||
@@ -329,15 +340,29 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
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<RequestModalProps> = ({
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
{(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 && (
|
||||
<p className="mt-6">
|
||||
<Alert
|
||||
@@ -385,7 +412,12 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
<table className="min-w-full">
|
||||
<thead>
|
||||
<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
|
||||
role="checkbox"
|
||||
tabIndex={0}
|
||||
@@ -438,7 +470,12 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
);
|
||||
return (
|
||||
<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
|
||||
role="checkbox"
|
||||
tabIndex={0}
|
||||
|
@@ -46,6 +46,7 @@ const messages = defineMessages({
|
||||
validationApplicationUrl: 'You must provide a valid URL',
|
||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
originalLanguageDefault: 'All Languages',
|
||||
partialRequestsEnabled: 'Enable Partial Series Requests',
|
||||
});
|
||||
|
||||
const SettingsMain: React.FC = () => {
|
||||
@@ -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 = () => {
|
||||
/>
|
||||
</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="flex justify-end">
|
||||
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||
|
@@ -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<SettingsContextProps>({
|
||||
|
@@ -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 <strong>{title}</strong> canceled.",
|
||||
"components.RequestModal.requestSuccess": "<strong>{title}</strong> 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",
|
||||
|
@@ -148,6 +148,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
localLogin: true,
|
||||
region: '',
|
||||
originalLanguage: '',
|
||||
partialRequestsEnabled: true,
|
||||
};
|
||||
|
||||
let locale = 'en';
|
||||
|
Reference in New Issue
Block a user