feat(ui): add option to only allow complete series requests (#1164)

This commit is contained in:
TheCatLady
2021-03-16 03:36:54 -04:00
committed by GitHub
parent 6f1a31de47
commit 36c00fde27
8 changed files with 108 additions and 32 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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,
}; };
} }

View File

@@ -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}

View File

@@ -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">

View File

@@ -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>({

View File

@@ -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",

View File

@@ -148,6 +148,7 @@ CoreApp.getInitialProps = async (initialProps) => {
localLogin: true, localLogin: true,
region: '', region: '',
originalLanguage: '', originalLanguage: '',
partialRequestsEnabled: true,
}; };
let locale = 'en'; let locale = 'en';