pull/3810/merge
Jordan Jones 2 weeks ago committed by GitHub
commit 1b6e02d082
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -66,6 +66,9 @@ export class UserSettings {
@Column({ nullable: true })
public watchlistSyncTv?: boolean;
@Column({ nullable: true })
public collapseTags?: boolean;
@Column({
type: 'text',
nullable: true,

@ -16,6 +16,7 @@ export interface UserSettingsGeneralResponse {
globalTvQuotaDays?: number;
watchlistSyncMovies?: boolean;
watchlistSyncTv?: boolean;
collapseTags?: boolean;
}
export type NotificationAgentTypes = Record<NotificationAgentKey, number>;

@ -97,6 +97,7 @@ export interface MainSettings {
tv: Quota;
};
hideAvailable: boolean;
collapseTags: boolean;
localLogin: boolean;
newPlexLogin: boolean;
region: string;
@ -293,6 +294,7 @@ class Settings {
tv: {},
},
hideAvailable: false,
collapseTags: false,
localLogin: true,
newPlexLogin: true,
region: '',

@ -0,0 +1,31 @@
import type { MigrationInterface, QueryRunner } from 'typeorm';
export class AddCollapseTagsColumn1710339059307 implements MigrationInterface {
name = 'AddCollapseTagsColumn1710339059307';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "locale" varchar NOT NULL DEFAULT (''), "region" varchar, "originalLanguage" varchar, "pgpKey" varchar, "discordId" varchar, "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "pushoverSound" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "notificationTypes" text, "userId" integer, "collapseTags" boolean, CONSTRAINT "REL_986a2b6d3c05eb4091bb8066f7" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "temporary_user_settings"("id", "locale", "region", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId") SELECT "id", "locale", "region", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId" FROM "user_settings"`
);
await queryRunner.query(`DROP TABLE "user_settings"`);
await queryRunner.query(
`ALTER TABLE "temporary_user_settings" RENAME TO "user_settings"`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user_settings" RENAME TO "temporary_user_settings"`
);
await queryRunner.query(
`CREATE TABLE "user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "locale" varchar NOT NULL DEFAULT (''), "region" varchar, "originalLanguage" varchar, "pgpKey" varchar, "discordId" varchar, "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "pushoverSound" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "notificationTypes" text, "userId" integer, CONSTRAINT "REL_986a2b6d3c05eb4091bb8066f7" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "user_settings"("id", "locale", "region", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId") SELECT "id", "locale", "region", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId" FROM "temporary_user_settings"`
);
await queryRunner.query(`DROP TABLE "temporary_user_settings"`);
}
}

@ -65,6 +65,7 @@ userSettingsRoutes.get<{ id: string }, UserSettingsGeneralResponse>(
globalTvQuotaLimit: defaultQuotas.tv.quotaLimit,
watchlistSyncMovies: user.settings?.watchlistSyncMovies,
watchlistSyncTv: user.settings?.watchlistSyncTv,
collapseTags: user.settings?.collapseTags,
});
} catch (e) {
next({ status: 500, message: e.message });
@ -118,6 +119,7 @@ userSettingsRoutes.post<
originalLanguage: req.body.originalLanguage,
watchlistSyncMovies: req.body.watchlistSyncMovies,
watchlistSyncTv: req.body.watchlistSyncTv,
collapseTags: req.body.collapseTags,
});
} else {
user.settings.discordId = req.body.discordId;
@ -126,6 +128,7 @@ userSettingsRoutes.post<
user.settings.originalLanguage = req.body.originalLanguage;
user.settings.watchlistSyncMovies = req.body.watchlistSyncMovies;
user.settings.watchlistSyncTv = req.body.watchlistSyncTv;
user.settings.collapseTags = req.body.collapseTags;
}
await userRepository.save(user);
@ -138,6 +141,7 @@ userSettingsRoutes.post<
originalLanguage: user.settings.originalLanguage,
watchlistSyncMovies: user.settings.watchlistSyncMovies,
watchlistSyncTv: user.settings.watchlistSyncTv,
collapseTags: user.settings.collapseTags,
});
} catch (e) {
next({ status: 500, message: e.message });

@ -0,0 +1,66 @@
import Tag from '@app/components/Common/Tag';
import { useUser } from '@app/hooks/useUser';
import { Disclosure, Transition } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/24/solid';
import type { Keyword } from '@server/models/common';
import Link from 'next/link';
interface KeywordDisclosureProps {
keywords: Keyword[];
type: 'tv' | 'movies';
}
const KeywordDisclosure = ({ keywords, type }: KeywordDisclosureProps) => {
const { user } = useUser();
return (
<>
{keywords.length > 0 && (
<Disclosure defaultOpen={!user?.settings?.collapseTags}>
{({ open }) => (
<>
<Disclosure.Button
className={`mt-2 flex w-full items-center justify-between space-x-2 border-gray-700 bg-gray-800 px-4 py-2 text-gray-200 ${
open
? 'rounded-t-md border-t border-l border-r'
: 'rounded-md border'
}`}
>
<span className="text-lg">Tags</span>
<ChevronDownIcon
className={`${
open ? 'rotate-180' : ''
} h-6 w-6 text-gray-500`}
/>
</Disclosure.Button>
<Transition
show={open}
enter="transition-opacity duration-100 ease-out"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-75 ease-out"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Disclosure.Panel className="w-full rounded-b-md border-b border-l border-r border-gray-700 px-4 py-4">
{keywords.map((keyword) => (
<Link
href={`/discover/${type}?keywords=${keyword.id}`}
key={`keyword-id-${keyword.id}`}
>
<a className="mr-2 inline-flex last:mr-0">
<Tag>{keyword.name}</Tag>
</a>
</Link>
))}
</Disclosure.Panel>
</Transition>
</>
)}
</Disclosure>
)}
</>
);
};
export default KeywordDisclosure;

@ -10,10 +10,10 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import type { PlayButtonLink } from '@app/components/Common/PlayButton';
import PlayButton from '@app/components/Common/PlayButton';
import Tag from '@app/components/Common/Tag';
import Tooltip from '@app/components/Common/Tooltip';
import ExternalLinkBlock from '@app/components/ExternalLinkBlock';
import IssueModal from '@app/components/IssueModal';
import KeywordDisclosure from '@app/components/KeywordDisclosure';
import ManageSlideOver from '@app/components/ManageSlideOver';
import MediaSlider from '@app/components/MediaSlider';
import PersonCard from '@app/components/PersonCard';
@ -464,20 +464,10 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
</div>
</>
)}
{data.keywords.length > 0 && (
<div className="mt-6">
{data.keywords.map((keyword) => (
<Link
href={`/discover/movies?keywords=${keyword.id}`}
key={`keyword-id-${keyword.id}`}
>
<a className="mb-2 mr-2 inline-flex last:mr-0">
<Tag>{keyword.name}</Tag>
</a>
</Link>
))}
</div>
)}
<KeywordDisclosure
keywords={data.keywords}
type="movies"
></KeywordDisclosure>
</div>
<div className="media-overview-right">
{data.collection && (

@ -11,10 +11,10 @@ import PageTitle from '@app/components/Common/PageTitle';
import type { PlayButtonLink } from '@app/components/Common/PlayButton';
import PlayButton from '@app/components/Common/PlayButton';
import StatusBadgeMini from '@app/components/Common/StatusBadgeMini';
import Tag from '@app/components/Common/Tag';
import Tooltip from '@app/components/Common/Tooltip';
import ExternalLinkBlock from '@app/components/ExternalLinkBlock';
import IssueModal from '@app/components/IssueModal';
import KeywordDisclosure from '@app/components/KeywordDisclosure';
import ManageSlideOver from '@app/components/ManageSlideOver';
import MediaSlider from '@app/components/MediaSlider';
import PersonCard from '@app/components/PersonCard';
@ -503,20 +503,10 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
</div>
</>
)}
{data.keywords.length > 0 && (
<div className="mt-6">
{data.keywords.map((keyword) => (
<Link
href={`/discover/tv?keywords=${keyword.id}`}
key={`keyword-id-${keyword.id}`}
>
<a className="mb-2 mr-2 inline-flex last:mr-0">
<Tag>{keyword.name}</Tag>
</a>
</Link>
))}
</div>
)}
<KeywordDisclosure
keywords={data.keywords}
type="tv"
></KeywordDisclosure>
<h2 className="py-4">{intl.formatMessage(messages.seasonstitle)}</h2>
<div className="flex w-full flex-col space-y-2">
{data.seasons

@ -55,6 +55,8 @@ const messages = defineMessages({
plexwatchlistsyncseries: 'Auto-Request Series',
plexwatchlistsyncseriestip:
'Automatically request series on your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>',
collapsetags: 'Collapse Tags',
collapsetagstip: 'Collapse tags by default in Movie/Series detail page',
});
const UserGeneralSettings = () => {
@ -130,6 +132,7 @@ const UserGeneralSettings = () => {
tvQuotaDays: data?.tvQuotaDays,
watchlistSyncMovies: data?.watchlistSyncMovies,
watchlistSyncTv: data?.watchlistSyncTv,
collapseTags: data?.collapseTags,
}}
validationSchema={UserGeneralSettingsSchema}
enableReinitialize
@ -149,6 +152,7 @@ const UserGeneralSettings = () => {
tvQuotaDays: tvQuotaEnabled ? values.tvQuotaDays : null,
watchlistSyncMovies: values.watchlistSyncMovies,
watchlistSyncTv: values.watchlistSyncTv,
collapseTags: values.collapseTags,
});
if (currentUser?.id === user?.id && setLocale) {
@ -334,6 +338,24 @@ const UserGeneralSettings = () => {
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="collapseTags" className="checkbox-label">
<span>{intl.formatMessage(messages.collapsetags)}</span>
<span className="label-tip">
{intl.formatMessage(messages.collapsetagstip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="collapseTags"
name="collapseTags"
onChange={() => {
setFieldValue('collapseTags', !values.collapseTags);
}}
/>
</div>
</div>
{currentHasPermission(Permission.MANAGE_USERS) &&
!hasPermission(Permission.MANAGE_USERS) && (
<>

@ -33,6 +33,7 @@ export interface UserSettings {
notificationTypes: Partial<NotificationAgentTypes>;
watchlistSyncMovies?: boolean;
watchlistSyncTv?: boolean;
collapseTags?: boolean;
}
interface UserHookResponse {

@ -1087,6 +1087,8 @@
"components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Override Global Limit",
"components.UserProfile.UserSettings.UserGeneralSettings.general": "General",
"components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "General Settings",
"components.UserProfile.UserSettings.UserGeneralSettings.collapsetags": "Collapse Tags",
"components.UserProfile.UserSettings.UserGeneralSettings.collapsetagstip": "Collapse tags by default in Movie/Series detail page",
"components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "Default ({language})",
"components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Local User",
"components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Movie Request Limit",

Loading…
Cancel
Save