mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
@@ -13,6 +13,7 @@ export interface PublicSettingsResponse {
|
||||
movie4kEnabled: boolean;
|
||||
series4kEnabled: boolean;
|
||||
region: string;
|
||||
originalLanguage: string;
|
||||
}
|
||||
|
||||
export interface CacheItem {
|
||||
|
@@ -85,6 +85,7 @@ interface FullPublicSettings extends PublicSettings {
|
||||
movie4kEnabled: boolean;
|
||||
series4kEnabled: boolean;
|
||||
region: string;
|
||||
originalLanguage: string;
|
||||
}
|
||||
|
||||
export interface NotificationAgentConfig {
|
||||
@@ -337,6 +338,7 @@ class Settings {
|
||||
(sonarr) => sonarr.is4k && sonarr.isDefault
|
||||
),
|
||||
region: this.data.main.region,
|
||||
originalLanguage: this.data.main.originalLanguage,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -5,16 +5,35 @@ import Media from '../entity/Media';
|
||||
import { isMovie, isPerson } from '../utils/typeHelpers';
|
||||
import { MediaType } from '../constants/media';
|
||||
import { getSettings } from '../lib/settings';
|
||||
import { User } from '../entity/User';
|
||||
|
||||
const createTmdbWithRegionLanaguage = (user?: User): TheMovieDb => {
|
||||
const settings = getSettings();
|
||||
|
||||
const region =
|
||||
user?.settings?.region === 'all'
|
||||
? ''
|
||||
: user?.settings?.region
|
||||
? user?.settings?.region
|
||||
: settings.main.region;
|
||||
|
||||
const originalLanguage =
|
||||
user?.settings?.originalLanguage === 'all'
|
||||
? ''
|
||||
: user?.settings?.originalLanguage
|
||||
? user?.settings?.originalLanguage
|
||||
: settings.main.originalLanguage;
|
||||
|
||||
return new TheMovieDb({
|
||||
region,
|
||||
originalLanguage,
|
||||
});
|
||||
};
|
||||
|
||||
const discoverRoutes = Router();
|
||||
|
||||
discoverRoutes.get('/movies', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
const tmdb = new TheMovieDb({
|
||||
region: req.user?.settings?.region ?? settings.main.region,
|
||||
originalLanguage:
|
||||
req.user?.settings?.originalLanguage ?? settings.main.originalLanguage,
|
||||
});
|
||||
const tmdb = createTmdbWithRegionLanaguage(req.user);
|
||||
|
||||
const data = await tmdb.getDiscoverMovies({
|
||||
page: Number(req.query.page),
|
||||
@@ -41,12 +60,7 @@ discoverRoutes.get('/movies', async (req, res) => {
|
||||
});
|
||||
|
||||
discoverRoutes.get('/movies/upcoming', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
const tmdb = new TheMovieDb({
|
||||
region: req.user?.settings?.region ?? settings.main.region,
|
||||
originalLanguage:
|
||||
req.user?.settings?.originalLanguage ?? settings.main.originalLanguage,
|
||||
});
|
||||
const tmdb = createTmdbWithRegionLanaguage(req.user);
|
||||
|
||||
const now = new Date();
|
||||
const offset = now.getTimezoneOffset();
|
||||
@@ -80,12 +94,7 @@ discoverRoutes.get('/movies/upcoming', async (req, res) => {
|
||||
});
|
||||
|
||||
discoverRoutes.get('/tv', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
const tmdb = new TheMovieDb({
|
||||
region: req.user?.settings?.region ?? settings.main.region,
|
||||
originalLanguage:
|
||||
req.user?.settings?.originalLanguage ?? settings.main.originalLanguage,
|
||||
});
|
||||
const tmdb = createTmdbWithRegionLanaguage(req.user);
|
||||
|
||||
const data = await tmdb.getDiscoverTv({
|
||||
page: Number(req.query.page),
|
||||
@@ -112,12 +121,7 @@ discoverRoutes.get('/tv', async (req, res) => {
|
||||
});
|
||||
|
||||
discoverRoutes.get('/tv/upcoming', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
const tmdb = new TheMovieDb({
|
||||
region: req.user?.settings?.region ?? settings.main.region,
|
||||
originalLanguage:
|
||||
req.user?.settings?.originalLanguage ?? settings.main.originalLanguage,
|
||||
});
|
||||
const tmdb = createTmdbWithRegionLanaguage(req.user);
|
||||
|
||||
const now = new Date();
|
||||
const offset = now.getTimezoneOffset();
|
||||
@@ -151,12 +155,7 @@ discoverRoutes.get('/tv/upcoming', async (req, res) => {
|
||||
});
|
||||
|
||||
discoverRoutes.get('/trending', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
const tmdb = new TheMovieDb({
|
||||
region: req.user?.settings?.region ?? settings.main.region,
|
||||
originalLanguage:
|
||||
req.user?.settings?.originalLanguage ?? settings.main.originalLanguage,
|
||||
});
|
||||
const tmdb = createTmdbWithRegionLanaguage(req.user);
|
||||
|
||||
const data = await tmdb.getAllTrending({
|
||||
page: Number(req.query.page),
|
||||
|
@@ -1,37 +1,54 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Listbox, Transition } from '@headlessui/react';
|
||||
import { countryCodeEmoji } from 'country-code-emoji';
|
||||
import useSWR from 'swr';
|
||||
import type { Region } from '../../../server/lib/settings';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
|
||||
const messages = defineMessages({
|
||||
regionDefault: 'All Regions',
|
||||
regionServerDefault: '{applicationTitle} Default ({region})',
|
||||
});
|
||||
|
||||
interface RegionSelectorProps {
|
||||
value: string;
|
||||
name: string;
|
||||
isUserSetting?: boolean;
|
||||
onChange?: (fieldName: string, region: string) => void;
|
||||
}
|
||||
|
||||
const RegionSelector: React.FC<RegionSelectorProps> = ({
|
||||
name,
|
||||
value,
|
||||
isUserSetting = false,
|
||||
onChange,
|
||||
}) => {
|
||||
const { currentSettings } = useSettings();
|
||||
const intl = useIntl();
|
||||
const { data: regions } = useSWR<Region[]>('/api/v1/regions');
|
||||
const [selectedRegion, setSelectedRegion] = useState<Region | null>(null);
|
||||
|
||||
const allRegion: Region = useMemo(
|
||||
() => ({
|
||||
iso_3166_1: 'all',
|
||||
english_name: 'All',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (regions && value) {
|
||||
if (value === 'all') {
|
||||
setSelectedRegion(allRegion);
|
||||
} else {
|
||||
const matchedRegion = regions.find(
|
||||
(region) => region.iso_3166_1 === value
|
||||
);
|
||||
setSelectedRegion(matchedRegion ?? null);
|
||||
}
|
||||
}, [value, regions]);
|
||||
}
|
||||
}, [value, regions, allRegion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onChange && regions) {
|
||||
@@ -47,15 +64,26 @@ const RegionSelector: React.FC<RegionSelectorProps> = ({
|
||||
<div className="relative">
|
||||
<span className="inline-block w-full rounded-md shadow-sm">
|
||||
<Listbox.Button className="relative flex items-center w-full py-2 pl-3 pr-10 text-left text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md cursor-default focus:outline-none focus:shadow-outline-blue focus:border-blue-300 sm:text-sm sm:leading-5">
|
||||
{selectedRegion && (
|
||||
{selectedRegion && selectedRegion.iso_3166_1 !== 'all' && (
|
||||
<span className="h-4 mr-2 overflow-hidden text-lg leading-4">
|
||||
{countryCodeEmoji(selectedRegion.iso_3166_1)}
|
||||
</span>
|
||||
)}
|
||||
<span className="block truncate">
|
||||
{selectedRegion
|
||||
{selectedRegion && selectedRegion.iso_3166_1 !== 'all'
|
||||
? intl.formatDisplayName(selectedRegion.iso_3166_1, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? selectedRegion.english_name
|
||||
: isUserSetting && selectedRegion?.iso_3166_1 !== 'all'
|
||||
? intl.formatMessage(messages.regionServerDefault, {
|
||||
applicationTitle: currentSettings.applicationTitle,
|
||||
region: currentSettings.region
|
||||
? intl.formatDisplayName(currentSettings.region, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? currentSettings.region
|
||||
: intl.formatMessage(messages.regionDefault),
|
||||
})
|
||||
: intl.formatMessage(messages.regionDefault)}
|
||||
</span>
|
||||
@@ -89,7 +117,60 @@ const RegionSelector: React.FC<RegionSelectorProps> = ({
|
||||
static
|
||||
className="py-1 overflow-auto text-base leading-6 rounded-md shadow-xs max-h-60 focus:outline-none sm:text-sm sm:leading-5"
|
||||
>
|
||||
{isUserSetting && (
|
||||
<Listbox.Option value={null}>
|
||||
{({ selected, active }) => (
|
||||
<div
|
||||
className={`${
|
||||
active
|
||||
? 'text-white bg-indigo-600'
|
||||
: 'text-gray-300'
|
||||
} cursor-default select-none relative py-2 pl-8 pr-4`}
|
||||
>
|
||||
<span
|
||||
className={`${
|
||||
selected ? 'font-semibold' : 'font-normal'
|
||||
} block truncate`}
|
||||
>
|
||||
{intl.formatMessage(messages.regionServerDefault, {
|
||||
applicationTitle:
|
||||
currentSettings.applicationTitle,
|
||||
region: currentSettings.region
|
||||
? intl.formatDisplayName(
|
||||
currentSettings.region,
|
||||
{
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}
|
||||
) ?? currentSettings.region
|
||||
: intl.formatMessage(messages.regionDefault),
|
||||
})}
|
||||
</span>
|
||||
{selected && (
|
||||
<span
|
||||
className={`${
|
||||
active ? 'text-white' : 'text-indigo-600'
|
||||
} absolute inset-y-0 left-0 flex items-center pl-1.5`}
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
)}
|
||||
<Listbox.Option value={isUserSetting ? allRegion : null}>
|
||||
{({ selected, active }) => (
|
||||
<div
|
||||
className={`${
|
||||
|
@@ -6,6 +6,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import { Language } from '../../../../../server/lib/settings';
|
||||
import useSettings from '../../../../hooks/useSettings';
|
||||
import { UserType, useUser } from '../../../../hooks/useUser';
|
||||
import Error from '../../../../pages/_error';
|
||||
import Badge from '../../../Common/Badge';
|
||||
@@ -29,6 +30,7 @@ const messages = defineMessages({
|
||||
originallanguageTip:
|
||||
'Filter content by original language (only applies to the "Popular" and "Upcoming" categories)',
|
||||
originalLanguageDefault: 'All Languages',
|
||||
languageServerDefault: '{applicationTitle} Default ({language})',
|
||||
});
|
||||
|
||||
const UserGeneralSettings: React.FC = () => {
|
||||
@@ -36,6 +38,7 @@ const UserGeneralSettings: React.FC = () => {
|
||||
const { addToast } = useToasts();
|
||||
const router = useRouter();
|
||||
const { user, mutate } = useUser({ id: Number(router.query.userId) });
|
||||
const { currentSettings } = useSettings();
|
||||
const { data, error, revalidate } = useSWR<{
|
||||
username?: string;
|
||||
region?: string;
|
||||
@@ -143,6 +146,7 @@ const UserGeneralSettings: React.FC = () => {
|
||||
<RegionSelector
|
||||
name="region"
|
||||
value={values.region ?? ''}
|
||||
isUserSetting
|
||||
onChange={setFieldValue}
|
||||
/>
|
||||
</div>
|
||||
@@ -162,6 +166,19 @@ const UserGeneralSettings: React.FC = () => {
|
||||
name="originalLanguage"
|
||||
>
|
||||
<option value="">
|
||||
{intl.formatMessage(messages.languageServerDefault, {
|
||||
applicationTitle: currentSettings.applicationTitle,
|
||||
language:
|
||||
intl.formatDisplayName(
|
||||
currentSettings.originalLanguage,
|
||||
{
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}
|
||||
) ?? currentSettings.originalLanguage,
|
||||
})}
|
||||
</option>
|
||||
<option value="all">
|
||||
{intl.formatMessage(messages.originalLanguageDefault)}
|
||||
</option>
|
||||
{languages?.map((language) => (
|
||||
|
@@ -139,6 +139,7 @@
|
||||
"components.PlexLoginButton.signingin": "Signing in…",
|
||||
"components.PlexLoginButton.signinwithplex": "Sign In",
|
||||
"components.RegionSelector.regionDefault": "All Regions",
|
||||
"components.RegionSelector.regionServerDefault": "{applicationTitle} Default ({region})",
|
||||
"components.RequestBlock.profilechanged": "Quality Profile",
|
||||
"components.RequestBlock.requestoverrides": "Request Overrides",
|
||||
"components.RequestBlock.rootfolder": "Root Folder",
|
||||
@@ -679,6 +680,7 @@
|
||||
"components.UserProfile.ProfileHeader.settings": "Edit Settings",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.displayName": "Display Name",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "General Settings",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.languageServerDefault": "{applicationTitle} Default ({language})",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Local User",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.originalLanguageDefault": "All Languages",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Discover Language",
|
||||
|
Reference in New Issue
Block a user