Skip to content

Commit

Permalink
feat(frontend): allow selecting multiple original languages
Browse files Browse the repository at this point in the history
  • Loading branch information
sct committed Mar 30, 2021
1 parent 5c135c9 commit a908c07
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 71 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"react-intersection-observer": "^8.31.0",
"react-intl": "5.13.5",
"react-markdown": "^5.0.3",
"react-select": "^4.3.0",
"react-spring": "^8.0.27",
"react-toast-notifications": "^2.4.3",
"react-transition-group": "^4.4.1",
Expand Down Expand Up @@ -99,6 +100,7 @@
"@types/nodemailer": "^6.4.1",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3",
"@types/react-select": "^4.0.13",
"@types/react-toast-notifications": "^2.4.0",
"@types/react-transition-group": "^4.4.1",
"@types/secure-random-password": "^0.2.0",
Expand Down
194 changes: 194 additions & 0 deletions src/components/LanguageSelector/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import dynamic from 'next/dynamic';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import type { OptionsType, OptionTypeBase } from 'react-select';
import { Language } from '../../../server/lib/settings';
import globalMessages from '../../i18n/globalMessages';

const messages = defineMessages({
originalLanguageDefault: 'All Languages',
languageServerDefault: 'Default ({language})',
});

const Select = dynamic(() => import('react-select'), { ssr: false });

type OptionType = {
value: string;
label: string;
isFixed?: boolean;
};

const selectStyles = {
multiValueLabel: (base: any, state: { data: { isFixed?: boolean } }) => {
return state.data.isFixed ? { ...base, paddingRight: 6 } : base;
},
multiValueRemove: (base: any, state: { data: { isFixed?: boolean } }) => {
return state.data.isFixed ? { ...base, display: 'none' } : base;
},
};

interface LanguageSelectorProps {
languages: Language[];
value?: string;
setFieldValue: (property: string, value: string) => void;
serverValue?: string;
isUserSettings?: boolean;
}

const LanguageSelector: React.FC<LanguageSelectorProps> = ({
languages,
value,
setFieldValue,
serverValue,
isUserSettings = false,
}) => {
const intl = useIntl();

const defaultLanguageNameFallback = serverValue
? languages.find((language) => language.iso_639_1 === serverValue)
?.english_name ?? serverValue
: undefined;

const options: OptionType[] =
languages.map((language) => ({
label:
intl.formatDisplayName(language.iso_639_1, {
type: 'language',
fallback: 'none',
}) ?? language.english_name,
value: language.iso_639_1,
})) ?? [];

if (isUserSettings) {
options.unshift({
value: 'server',
label: intl.formatMessage(messages.languageServerDefault, {
language: serverValue
? serverValue
.split('|')
.map(
(value) =>
intl.formatDisplayName(value, {
type: 'language',
fallback: 'none',
}) ?? defaultLanguageNameFallback
)
.reduce((prev, curr) =>
intl.formatMessage(globalMessages.delimitedlist, {
a: prev,
b: curr,
})
)
: intl.formatMessage(messages.originalLanguageDefault),
}),
isFixed: true,
});
}

options.unshift({
value: 'all',
label: intl.formatMessage(messages.originalLanguageDefault),
isFixed: true,
});

return (
<Select
options={options}
isMulti
className="react-select-container"
classNamePrefix="react-select"
value={
(isUserSettings && value === 'all') || (!isUserSettings && !value)
? {
value: 'all',
label: intl.formatMessage(messages.originalLanguageDefault),
isFixed: true,
}
: (value === '' || !value || value === 'server') && isUserSettings
? {
value: 'server',
label: intl.formatMessage(messages.languageServerDefault, {
language: serverValue
? serverValue
.split('|')
.map(
(value) =>
intl.formatDisplayName(value, {
type: 'language',
fallback: 'none',
}) ?? defaultLanguageNameFallback
)
.reduce((prev, curr) =>
intl.formatMessage(globalMessages.delimitedlist, {
a: prev,
b: curr,
})
)
: intl.formatMessage(messages.originalLanguageDefault),
}),
isFixed: true,
}
: value?.split('|').map((code) => {
const matchedLanguage = languages.find(
(lang) => lang.iso_639_1 === code
);

if (!matchedLanguage) {
return undefined;
}

return {
label:
intl.formatDisplayName(matchedLanguage.iso_639_1, {
type: 'language',
fallback: 'none',
}) ?? matchedLanguage.english_name,
value: matchedLanguage.iso_639_1,
};
}) ?? undefined
}
onChange={(
value: OptionTypeBase | OptionsType<OptionType> | null,
options
) => {
if (!Array.isArray(value)) {
return;
}

if (
(options &&
options.action === 'select-option' &&
options.option?.value === 'server') ||
value?.every(
(v: { value: string; label: string }) => v.value === 'server'
)
) {
return setFieldValue('originalLanguage', '');
}

if (
(options &&
options.action === 'select-option' &&
options.option?.value === 'all') ||
value?.every(
(v: { value: string; label: string }) => v.value === 'all'
)
) {
return setFieldValue('originalLanguage', isUserSettings ? 'all' : '');
}

setFieldValue(
'originalLanguage',
value
?.map((lang) => lang.value)
.filter((v) => v !== 'all')
.join('|')
);
}}
styles={selectStyles}
/>
);
};

export default LanguageSelector;
27 changes: 6 additions & 21 deletions src/components/Settings/SettingsMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Badge from '../Common/Badge';
import Button from '../Common/Button';
import LoadingSpinner from '../Common/LoadingSpinner';
import PageTitle from '../Common/PageTitle';
import LanguageSelector from '../LanguageSelector';
import RegionSelector from '../RegionSelector';
import CopyButton from './CopyButton';

Expand Down Expand Up @@ -46,7 +47,6 @@ const messages = defineMessages({
validationApplicationTitle: 'You must provide an application title',
validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
originalLanguageDefault: 'All Languages',
partialRequestsEnabled: 'Allow Partial Series Requests',
});

Expand Down Expand Up @@ -347,26 +347,11 @@ const SettingsMain: React.FC = () => {
</label>
<div className="form-input">
<div className="form-input-field">
<Field
as="select"
id="originalLanguage"
name="originalLanguage"
>
<option value="">
{intl.formatMessage(messages.originalLanguageDefault)}
</option>
{sortedLanguages?.map((language) => (
<option
key={`language-key-${language.iso_639_1}`}
value={language.iso_639_1}
>
{intl.formatDisplayName(language.iso_639_1, {
type: 'language',
fallback: 'none',
}) ?? language.english_name}
</option>
))}
</Field>
<LanguageSelector
languages={sortedLanguages ?? []}
setFieldValue={setFieldValue}
value={values.originalLanguage}
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Badge from '../../../Common/Badge';
import Button from '../../../Common/Button';
import LoadingSpinner from '../../../Common/LoadingSpinner';
import PageTitle from '../../../Common/PageTitle';
import LanguageSelector from '../../../LanguageSelector';
import QuotaSelector from '../../../QuotaSelector';
import RegionSelector from '../../../RegionSelector';

Expand Down Expand Up @@ -101,11 +102,6 @@ const UserGeneralSettings: React.FC = () => {
return <Error statusCode={500} />;
}

const defaultLanguageNameFallback =
languages.find(
(language) => language.iso_639_1 === currentSettings.originalLanguage
)?.english_name ?? currentSettings.originalLanguage;

return (
<>
<PageTitle
Expand Down Expand Up @@ -237,41 +233,13 @@ const UserGeneralSettings: React.FC = () => {
</label>
<div className="form-input">
<div className="form-input-field">
<Field
as="select"
id="originalLanguage"
name="originalLanguage"
>
<option value="">
{intl.formatMessage(messages.languageServerDefault, {
language: currentSettings.originalLanguage
? intl.formatDisplayName(
currentSettings.originalLanguage,
{
type: 'language',
fallback: 'none',
}
) ?? defaultLanguageNameFallback
: intl.formatMessage(
messages.originalLanguageDefault
),
})}
</option>
<option value="all">
{intl.formatMessage(messages.originalLanguageDefault)}
</option>
{sortedLanguages?.map((language) => (
<option
key={`language-key-${language.iso_639_1}`}
value={language.iso_639_1}
>
{intl.formatDisplayName(language.iso_639_1, {
type: 'language',
fallback: 'none',
}) ?? language.english_name}
</option>
))}
</Field>
<LanguageSelector
languages={sortedLanguages ?? []}
setFieldValue={setFieldValue}
serverValue={currentSettings.originalLanguage}
value={values.originalLanguage}
isUserSettings
/>
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"components.Discover.upcoming": "Upcoming Movies",
"components.Discover.upcomingmovies": "Upcoming Movies",
"components.Discover.upcomingtv": "Upcoming Series",
"components.LanguageSelector.languageServerDefault": "Default ({language})",
"components.LanguageSelector.originalLanguageDefault": "All Languages",
"components.Layout.LanguagePicker.changelanguage": "Change Language",
"components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV",
"components.Layout.Sidebar.dashboard": "Discover",
Expand Down Expand Up @@ -524,7 +526,6 @@
"components.Settings.notificationsettingsfailed": "Notification settings failed to save.",
"components.Settings.notificationsettingssaved": "Notification settings saved successfully!",
"components.Settings.notrunning": "Not Running",
"components.Settings.originalLanguageDefault": "All Languages",
"components.Settings.originallanguage": "Discover Language",
"components.Settings.originallanguageTip": "Filter content by original language",
"components.Settings.partialRequestsEnabled": "Allow Partial Series Requests",
Expand Down

0 comments on commit a908c07

Please sign in to comment.