diff --git a/server/entity/UserSettings.ts b/server/entity/UserSettings.ts index ea4a7d33..77d41bbe 100644 --- a/server/entity/UserSettings.ts +++ b/server/entity/UserSettings.ts @@ -66,6 +66,9 @@ export class UserSettings { @Column({ nullable: true }) public watchlistSyncTv?: boolean; + @Column({ nullable: true }) + public collapseTags?: boolean; + @Column({ type: 'text', nullable: true, diff --git a/server/interfaces/api/userSettingsInterfaces.ts b/server/interfaces/api/userSettingsInterfaces.ts index fb0767b2..050ef825 100644 --- a/server/interfaces/api/userSettingsInterfaces.ts +++ b/server/interfaces/api/userSettingsInterfaces.ts @@ -16,6 +16,7 @@ export interface UserSettingsGeneralResponse { globalTvQuotaDays?: number; watchlistSyncMovies?: boolean; watchlistSyncTv?: boolean; + collapseTags?: boolean; } export type NotificationAgentTypes = Record; diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 10213a04..799bff9a 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -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: '', diff --git a/server/migration/1710339059307-AddCollapseTagsColumn.ts b/server/migration/1710339059307-AddCollapseTagsColumn.ts new file mode 100644 index 00000000..fa8b6d66 --- /dev/null +++ b/server/migration/1710339059307-AddCollapseTagsColumn.ts @@ -0,0 +1,31 @@ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCollapseTagsColumn1710339059307 implements MigrationInterface { + name = 'AddCollapseTagsColumn1710339059307'; + + public async up(queryRunner: QueryRunner): Promise { + 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 { + 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"`); + } +} diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index c8b3f50b..e72e1044 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -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 }); diff --git a/src/components/KeywordDisclosure/index.tsx b/src/components/KeywordDisclosure/index.tsx new file mode 100644 index 00000000..477fe7ed --- /dev/null +++ b/src/components/KeywordDisclosure/index.tsx @@ -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 && ( + + {({ open }) => ( + <> + + Tags + + + + + {keywords.map((keyword) => ( + + + {keyword.name} + + + ))} + + + + )} + + )} + + ); +}; + +export default KeywordDisclosure; diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 11b2a775..85a74ad2 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -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) => { )} - {data.keywords.length > 0 && ( -
- {data.keywords.map((keyword) => ( - - - {keyword.name} - - - ))} -
- )} +
{data.collection && ( diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 2f44a7d8..688e0f49 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -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) => {
)} - {data.keywords.length > 0 && ( -
- {data.keywords.map((keyword) => ( - - - {keyword.name} - - - ))} -
- )} +

{intl.formatMessage(messages.seasonstitle)}

{data.seasons diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx index 842ea7af..1ff76cd1 100644 --- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx @@ -55,6 +55,8 @@ const messages = defineMessages({ plexwatchlistsyncseries: 'Auto-Request Series', plexwatchlistsyncseriestip: 'Automatically request series on your Plex Watchlist', + 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 = () => {
+
+ +
+ { + setFieldValue('collapseTags', !values.collapseTags); + }} + /> +
+
{currentHasPermission(Permission.MANAGE_USERS) && !hasPermission(Permission.MANAGE_USERS) && ( <> diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts index 192b3fe9..1bef3846 100644 --- a/src/hooks/useUser.ts +++ b/src/hooks/useUser.ts @@ -33,6 +33,7 @@ export interface UserSettings { notificationTypes: Partial; watchlistSyncMovies?: boolean; watchlistSyncTv?: boolean; + collapseTags?: boolean; } interface UserHookResponse { diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 10165c9e..9909c625 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -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",