diff --git a/.gitignore b/.gitignore index 52cd61ad14..d039edb955 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ config.json # ide .idea +.vs # log yarn-error.log diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f03a56ab71..c87ab0c92b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -80,6 +80,7 @@ - [Rasmus Krämer](https://github.com/rasmuslos) - [ntarelix](https://github.com/ntarelix) - [András Maróy](https://github.com/andrasmaroy) +- [Chris-Codes-It](https://github.com/Chris-Codes-It) - [Vedant](https://github.com/viktory36) ## Emby Contributors diff --git a/package-lock.json b/package-lock.json index 9c96e2080f..1c8a294411 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.17", - "@jellyfin/sdk": "0.0.0-unstable.202403100501", + "@jellyfin/sdk": "0.0.0-unstable.202403180216", "@loadable/component": "5.16.3", "@mui/icons-material": "5.15.11", "@mui/material": "5.15.11", @@ -3641,9 +3641,9 @@ "dev": true }, "node_modules/@jellyfin/sdk": { - "version": "0.0.0-unstable.202403100501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403100501.tgz", - "integrity": "sha512-gt3wIz/bUVznwuhffPaCGED8+mttjCSyrZZ2cz2UcDMqzzjmwhp57oP0AIUS1A5YQe5RVFWi95ZKQlI5aj/x+Q==", + "version": "0.0.0-unstable.202403180216", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403180216.tgz", + "integrity": "sha512-YvBixS+kT/8z8xJUW3A36cYGMwYuishXYsYY+4gNBZKfJ+z9iygIMhbLybrDoZcnAc5QyiaE5SLheOVPPBNXWw==", "peerDependencies": { "axios": "^1.3.4" } @@ -25268,9 +25268,9 @@ "dev": true }, "@jellyfin/sdk": { - "version": "0.0.0-unstable.202403100501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403100501.tgz", - "integrity": "sha512-gt3wIz/bUVznwuhffPaCGED8+mttjCSyrZZ2cz2UcDMqzzjmwhp57oP0AIUS1A5YQe5RVFWi95ZKQlI5aj/x+Q==", + "version": "0.0.0-unstable.202403180216", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403180216.tgz", + "integrity": "sha512-YvBixS+kT/8z8xJUW3A36cYGMwYuishXYsYY+4gNBZKfJ+z9iygIMhbLybrDoZcnAc5QyiaE5SLheOVPPBNXWw==", "requires": {} }, "@jest/schemas": { diff --git a/package.json b/package.json index c3344d3519..cf4b05c131 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.17", - "@jellyfin/sdk": "0.0.0-unstable.202403100501", + "@jellyfin/sdk": "0.0.0-unstable.202403180216", "@loadable/component": "5.16.3", "@mui/icons-material": "5.15.11", "@mui/material": "5.15.11", diff --git a/src/apps/dashboard/routes/users/index.tsx b/src/apps/dashboard/routes/users/index.tsx index 705713ff90..f758b85016 100644 --- a/src/apps/dashboard/routes/users/index.tsx +++ b/src/apps/dashboard/routes/users/index.tsx @@ -49,6 +49,7 @@ const UserProfiles: FunctionComponent = () => { const showUserMenu = (elem: HTMLElement) => { const card = dom.parentWithClass(elem, 'card'); const userId = card?.getAttribute('data-userid'); + const username = card?.getAttribute('data-username'); if (!userId) { console.error('Unexpected null user id'); @@ -106,7 +107,7 @@ const UserProfiles: FunctionComponent = () => { break; case 'delete': - deleteUser(userId); + deleteUser(userId, username); } } }).catch(() => { @@ -117,12 +118,13 @@ const UserProfiles: FunctionComponent = () => { }); }; - const deleteUser = (id: string) => { - const msg = globalize.translate('DeleteUserConfirmation'); + const deleteUser = (id: string, username?: string | null) => { + const title = username ? globalize.translate('DeleteName', username) : globalize.translate('DeleteUser'); + const text = globalize.translate('DeleteUserConfirmation'); confirm({ - title: globalize.translate('DeleteUser'), - text: msg, + title, + text, confirmText: globalize.translate('Delete'), primary: 'delete' }).then(function () { diff --git a/src/apps/dashboard/routes/users/profile.tsx b/src/apps/dashboard/routes/users/profile.tsx index 11cda31fcd..7ef62f8261 100644 --- a/src/apps/dashboard/routes/users/profile.tsx +++ b/src/apps/dashboard/routes/users/profile.tsx @@ -182,6 +182,7 @@ const UserEdit: FunctionComponent = () => { (page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled; (page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden; (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = user.Policy.EnableCollectionManagement; + (page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked = user.Policy.EnableSubtitleManagement; (page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl; (page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers; (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading; @@ -240,6 +241,7 @@ const UserEdit: FunctionComponent = () => { user.Policy.EnableVideoPlaybackTranscoding = (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked; user.Policy.EnablePlaybackRemuxing = (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked; user.Policy.EnableCollectionManagement = (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked; + user.Policy.EnableSubtitleManagement = (page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked; user.Policy.ForceRemoteSourceTranscoding = (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked; user.Policy.EnableContentDownloading = (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked; user.Policy.EnableRemoteAccess = (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked; @@ -392,6 +394,11 @@ const UserEdit: FunctionComponent = () => { className='chkEnableCollectionManagement' title='AllowCollectionManagement' /> +

{globalize.translate('HeaderFeatureAccess')} diff --git a/src/components/dashboard/users/UserCardBox.tsx b/src/components/dashboard/users/UserCardBox.tsx index e4bc40d2bf..8046be24ef 100644 --- a/src/components/dashboard/users/UserCardBox.tsx +++ b/src/components/dashboard/users/UserCardBox.tsx @@ -61,7 +61,7 @@ const UserCardBox: FunctionComponent = ({ user = {} }: IProps) => {

`; return ( -
+
diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 6d1c3c24f5..ad2052a3e1 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -214,11 +214,7 @@ export function getCommands(options) { }); } - if (canEdit && item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' - && item.LocationType !== 'Virtual' - && !(item.Type === 'Recording' && item.Status !== 'Completed') - && options.editSubtitles !== false - ) { + if (itemHelper.canEditSubtitles(user, item) && options.editSubtitles !== false) { commands.push({ name: globalize.translate('EditSubtitles'), id: 'editsubtitles', diff --git a/src/components/itemHelper.js b/src/components/itemHelper.js index d763003fb9..498a8bab80 100644 --- a/src/components/itemHelper.js +++ b/src/components/itemHelper.js @@ -1,6 +1,10 @@ import { appHost } from './apphost'; import globalize from '../scripts/globalize'; import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { LocationType } from '@jellyfin/sdk/lib/generated-client/models/location-type'; +import { RecordingStatus } from '@jellyfin/sdk/lib/generated-client/models/recording-status'; +import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type'; export function getDisplayName(item, options = {}) { if (!item) { @@ -155,6 +159,33 @@ export function canEditImages (user, item) { return itemType !== 'Timer' && itemType !== 'SeriesTimer' && canEdit(user, item) && !isLocalItem(item); } +export function canEditSubtitles (user, item) { + if (item.MediaType !== MediaType.Video) { + return false; + } + const itemType = item.Type; + if (itemType === BaseItemKind.Recording && item.Status !== RecordingStatus.Completed) { + return false; + } + if (itemType === BaseItemKind.TvChannel + || itemType === BaseItemKind.Program + || itemType === 'Timer' + || itemType === 'SeriesTimer' + || itemType === BaseItemKind.UserRootFolder + || itemType === BaseItemKind.UserView + ) { + return false; + } + if (isLocalItem(item)) { + return false; + } + if (item.LocationType === LocationType.Virtual) { + return false; + } + return user.Policy.EnableSubtitleManagement + || user.Policy.IsAdministrator; +} + export function canShare (item, user) { if (item.Type === 'Program') { return false; @@ -300,6 +331,7 @@ export default { canIdentify: canIdentify, canEdit: canEdit, canEditImages: canEditImages, + canEditSubtitles, canShare: canShare, enableDateAddedDisplay: enableDateAddedDisplay, canMarkPlayed: canMarkPlayed, diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index 11c0806104..0b9ce0b967 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -416,6 +416,8 @@ export function setContentType(parent, contentType) { } } + parent.querySelector('.chkUseReplayGainTagsContainer').classList.toggle('hide', contentType !== 'music'); + parent.querySelector('.chkEnableLUFSScanContainer').classList.toggle('hide', contentType !== 'music'); if (contentType === 'tvshows') { @@ -515,6 +517,7 @@ export function getLibraryOptions(parent) { EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, EnableLUFSScan: parent.querySelector('.chkEnableLUFSScan').checked, + UseReplayGainTags: parent.querySelector('.chkUseReplayGainTags').checked, ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, EnableInternetProviders: true, @@ -577,6 +580,7 @@ export function setLibraryOptions(parent, options) { parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan; + parent.querySelector('.chkUseReplayGainTags').checked = options.UseReplayGainTags; parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.template.html b/src/components/libraryoptionseditor/libraryoptionseditor.template.html index a221f53bbf..bf3bd7aaa8 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.template.html +++ b/src/components/libraryoptionseditor/libraryoptionseditor.template.html @@ -55,6 +55,14 @@
${LabelEnableRealtimeMonitorHelp}
+
+ +
${LabelUseReplayGainTagsHelp}
+
+
-
+
+
+
+ +
${AllowVideoToolboxTonemappingHelp}
+
+
+