From f3b84cadadc75ba2a519e3ff97c8964393dd2363 Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Mon, 10 Apr 2023 12:06:00 +0300 Subject: [PATCH 01/59] enable airplay for audioplayer --- src/components/nowPlayingBar/nowPlayingBar.js | 16 +++++++++ src/plugins/htmlAudioPlayer/plugin.js | 35 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 2bf29a2a9c..b25097e325 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -32,6 +32,7 @@ import { appRouter } from '../appRouter'; let volumeSliderContainer; let playPauseButtons; let positionSlider; + let toggleAirPlayButton; let toggleRepeatButton; let toggleRepeatButtonIcon; @@ -80,6 +81,8 @@ import { appRouter } from '../appRouter'; html += ''; html += ''; + html += ''; + html += ''; html += ''; @@ -190,6 +193,13 @@ import { appRouter } from '../appRouter'; } }); + toggleAirPlayButton = elem.querySelector('.btnAirPlay'); + toggleAirPlayButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleAirPlay(currentPlayer); + } + }); + elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { if (currentPlayer) { playbackManager.toggleQueueShuffleMode(); @@ -326,6 +336,12 @@ import { appRouter } from '../appRouter'; toggleRepeatButton.classList.remove('hide'); } + if (supportedCommands.indexOf('AirPlay') === -1) { + toggleAirPlayButton.classList.add('hide'); + } else { + toggleAirPlayButton.classList.remove('hide'); + } + updateRepeatModeDisplay(playbackManager.getRepeatMode()); onQueueShuffleModeChange(); diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 585d935f05..30e1cb4880 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -347,6 +347,10 @@ class HtmlAudioPlayer { return getDefaultProfile(); } + toggleAirPlay() { + return this.setAirPlayEnabled(!this.isAirPlayEnabled()); + } + // Save this for when playback stops, because querying the time at that point might return 0 currentTime(val) { const mediaElement = this._mediaElement; @@ -488,6 +492,33 @@ class HtmlAudioPlayer { return false; } + isAirPlayEnabled() { + if (document.AirPlayEnabled) { + return !!document.AirplayElement; + } + return false; + } + + setAirPlayEnabled(isEnabled) { + const mediaElement = this._mediaElement; + + if (document.AirPlayEnabled) { + if (mediaElement) { + if (isEnabled) { + mediaElement.requestAirPlay().catch(function(err) { + console.error('Error requesting AirPlay', err); + }); + } else { + document.exitAirPLay().catch(function(err) { + console.error('Error exiting AirPlay', err); + }); + } + } + } else { + mediaElement.webkitShowPlaybackTargetPicker(); + } + } + supports(feature) { if (!supportedFeatures) { supportedFeatures = getSupportedFeatures(); @@ -507,6 +538,10 @@ function getSupportedFeatures() { list.push('PlaybackRate'); } + if (browser.safari || browser.iOS || browser.iPad) { + list.push('AirPlay'); + } + return list; } From 14fca37e783a15f8f5f924d01efd0abc635ab827 Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Tue, 18 Apr 2023 20:17:25 +0300 Subject: [PATCH 02/59] Convert if/else add/remove statements to toggle --- src/components/nowPlayingBar/nowPlayingBar.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index b25097e325..8ec7103fe3 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -336,11 +336,8 @@ import { appRouter } from '../appRouter'; toggleRepeatButton.classList.remove('hide'); } - if (supportedCommands.indexOf('AirPlay') === -1) { - toggleAirPlayButton.classList.add('hide'); - } else { - toggleAirPlayButton.classList.remove('hide'); - } + const hideAirPlayButton = supportedCommands.indexOf('AirPlay') === -1; + toggleAirPlayButton.classList.toggle('hide', hideAirPlayButton); updateRepeatModeDisplay(playbackManager.getRepeatMode()); onQueueShuffleModeChange(); From 90fc207c9eecb6a9f6e7e8224bdaadd93f1def7b Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Thu, 20 Apr 2023 22:26:01 +0300 Subject: [PATCH 03/59] fix eslint newline required at end of file --- src/components/nowPlayingBar/nowPlayingBar.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index b2c892e85c..ba14fc41d8 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -793,4 +793,5 @@ document.addEventListener('viewbeforeshow', function (e) { hideNowPlayingBar(); } } -}); \ No newline at end of file +}); + From 70f1557086aee41f26f9f96549e4a7b21527a473 Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Thu, 11 May 2023 22:27:57 +0300 Subject: [PATCH 04/59] Add AirPlay for html audioplayer only if is safari desktop --- src/plugins/htmlAudioPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index fb6afc72e7..66b95ecc1d 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -538,7 +538,7 @@ function getSupportedFeatures() { list.push('PlaybackRate'); } - if (browser.safari || browser.iOS || browser.iPad) { + if (browser.safari) { list.push('AirPlay'); } From 23158a352c4bb7d2050003bf712a32c584a3191b Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Sat, 17 Jun 2023 13:18:13 +0300 Subject: [PATCH 05/59] update airplay conditions for htmlaudioplayer --- src/plugins/htmlAudioPlayer/plugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 66b95ecc1d..653796055c 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -502,8 +502,8 @@ class HtmlAudioPlayer { setAirPlayEnabled(isEnabled) { const mediaElement = this._mediaElement; - if (document.AirPlayEnabled) { - if (mediaElement) { + if (mediaElement) { + if (document.AirPlayEnabled) { if (isEnabled) { mediaElement.requestAirPlay().catch(function(err) { console.error('Error requesting AirPlay', err); @@ -513,9 +513,9 @@ class HtmlAudioPlayer { console.error('Error exiting AirPlay', err); }); } + } else { + mediaElement.webkitShowPlaybackTargetPicker(); } - } else { - mediaElement.webkitShowPlaybackTargetPicker(); } } From 1825b821af94db2f8ba6b5179b9be4b7691f9876 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Fri, 14 Jul 2023 21:32:34 +0300 Subject: [PATCH 06/59] cleanup duplicate userSettings from libraryBrowser --- src/controllers/movies/moviecollections.js | 8 ++-- src/controllers/movies/moviegenres.js | 10 ++-- src/controllers/movies/movietrailers.js | 6 +-- src/controllers/music/musicalbums.js | 15 ++---- src/controllers/music/musicartists.js | 56 ++++++++++------------ src/controllers/music/musicgenres.js | 12 ++--- src/controllers/music/musicplaylists.js | 10 ++-- src/controllers/music/songs.js | 36 +++++++------- src/controllers/shows/episodes.js | 34 ++++++------- src/controllers/shows/tvgenres.js | 10 ++-- src/controllers/shows/tvshows.js | 44 ++++++++--------- src/controllers/shows/tvstudios.js | 7 ++- src/scripts/libraryBrowser.js | 43 ----------------- src/scripts/playlists.js | 32 ++++++------- src/scripts/settings/userSettings.js | 20 ++++++++ 15 files changed, 147 insertions(+), 196 deletions(-) diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index 6e6af9dc98..da9477c888 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -24,7 +24,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -32,7 +32,7 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -183,7 +183,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -234,7 +234,7 @@ export default function (view, params, tabContent) { btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); getQuery().StartIndex = 0; onViewStyleChange(); reloadItems(tabElement); diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 52cb97bb79..3d866c6e9a 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -1,7 +1,7 @@ import escapeHtml from 'escape-html'; import layoutManager from '../../components/layoutManager'; import loading from '../../components/loading/loading'; -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver'; import globalize from '../../scripts/globalize'; @@ -22,10 +22,10 @@ export default function (view, params, tabContent) { Recursive: true, EnableTotalRecordCount: false }, - view: 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -181,7 +181,7 @@ export default function (view, params, tabContent) { elem.innerHTML = html; lazyLoader.lazyChildren(elem, fillItemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); }); } @@ -203,7 +203,7 @@ export default function (view, params, tabContent) { this.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index 81f9f8b149..8e9f7c4acc 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -27,14 +27,14 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { pageData.query['Limit'] = userSettings.libraryPageSize(); } - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -172,7 +172,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; }); diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index 6c0c45a0f0..f878b7e65c 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -42,7 +42,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -50,7 +50,7 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -61,11 +61,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - if (!savedQueryKey) { - savedQueryKey = libraryBrowser.getSavedQueryKey('musicalbums'); - } - - return savedQueryKey; + return `${params.topParentId}-musicalbums`; } const onViewStyleChange = () => { @@ -174,7 +170,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -184,7 +180,6 @@ export default function (view, params, tabContent) { }); }; - let savedQueryKey; let pageData; let isLoading = false; @@ -280,7 +275,7 @@ export default function (view, params, tabContent) { btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); getQuery().StartIndex = 0; onViewStyleChange(); reloadItems(); diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 4126086ade..2d89fd8078 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -10,8 +10,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -31,26 +31,22 @@ export default function (view, params, tabContent) { pageData = data[key] = { query: queryValues, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - const getSavedQueryKey = (context) => { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey(this.mode); - } - - return context.savedQueryKey; - }; + function getSavedQueryKey() { + return `${params.topParentId}-${this.mode}`; + } const onViewStyleChange = () => { const viewStyle = this.getCurrentViewStyle(); @@ -67,10 +63,10 @@ export default function (view, params, tabContent) { itemsContainer.innerHTML = ''; }; - const reloadItems = (page) => { + const reloadItems = () => { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); const promise = this.mode == 'albumartists' ? ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : ApiClient.getArtists(ApiClient.getCurrentUserId(), query); @@ -83,7 +79,7 @@ export default function (view, params, tabContent) { if (userSettings.libraryPageSize() > 0) { query.StartIndex += query.Limit; } - reloadItems(tabContent); + reloadItems(); } function onPreviousPageClick() { @@ -94,7 +90,7 @@ export default function (view, params, tabContent) { if (userSettings.libraryPageSize() > 0) { query.StartIndex = Math.max(0, query.StartIndex - query.Limit); } - reloadItems(tabContent); + reloadItems(); } window.scrollTo(0, 0); @@ -156,7 +152,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -172,20 +168,20 @@ export default function (view, params, tabContent) { this.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: this.mode, serverId: ApiClient.serverId() }); Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); + getQuery().StartIndex = 0; + reloadItems(); }); filterDialog.show(); }); }; this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; const initPage = (tabElement) => { @@ -194,7 +190,7 @@ export default function (view, params, tabContent) { alphaPickerElement.addEventListener('alphavaluechanged', function (e) { const newValue = e.detail.value; - const query = getQuery(tabElement); + const query = getQuery(); if (newValue === '#') { query.NameLessThan = 'A'; delete query.NameStartsWith; @@ -203,7 +199,7 @@ export default function (view, params, tabContent) { delete query.NameLessThan; } query.StartIndex = 0; - reloadItems(tabElement); + reloadItems(); }); this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, @@ -223,11 +219,11 @@ export default function (view, params, tabContent) { }); btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; + getPageData().view = viewStyle; + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); + getQuery().StartIndex = 0; onViewStyleChange(); - reloadItems(tabElement); + reloadItems(); }); }; @@ -235,8 +231,8 @@ export default function (view, params, tabContent) { onViewStyleChange(); this.renderTab = () => { - reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); + reloadItems(); + this.alphaPicker?.updateControls(getQuery()); }; } diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index 9c5bc9dcb2..3b79529570 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -1,4 +1,4 @@ -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; @@ -17,10 +17,10 @@ export default function (view, params, tabContent) { Fields: 'PrimaryImageAspectRatio,ItemCounts', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -31,7 +31,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); + return `${params.topParentId}-genres`; } function getPromise() { @@ -87,7 +87,7 @@ export default function (view, params, tabContent) { const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); import('../../components/autoFocuser').then(({ default: autoFocuser }) => { @@ -113,7 +113,7 @@ export default function (view, params, tabContent) { this.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index ab3cf8f1d2..3bf23b3857 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -1,4 +1,4 @@ -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; @@ -18,10 +18,10 @@ export default function (view, params, tabContent) { Fields: 'PrimaryImageAspectRatio,SortName,CanDelete', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -32,7 +32,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); + return `${params.topParentId}-musicplaylists`; } function getPromise() { @@ -58,7 +58,7 @@ export default function (view, params, tabContent) { const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); import('../../components/autoFocuser').then(({ default: autoFocuser }) => { diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 5e5337d032..9f405ed7b1 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -12,8 +12,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -35,28 +35,24 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('songs'); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${params.topParentId}-songs`; } function reloadItems(page) { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -117,7 +113,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); tabContent.querySelector('.btnShuffle').classList.toggle('hide', result.TotalRecordCount < 1); @@ -137,13 +133,13 @@ export default function (view, params, tabContent) { self.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: 'songs', serverId: ApiClient.serverId() }); Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); + getQuery().StartIndex = 0; + reloadItems(); }); filterDialog.show(); }); @@ -156,7 +152,7 @@ export default function (view, params, tabContent) { } self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; function initPage(tabElement) { @@ -197,10 +193,10 @@ export default function (view, params, tabContent) { id: 'Random,SortName' }], callback: function () { - getQuery(tabElement).StartIndex = 0; - reloadItems(tabElement); + getQuery().StartIndex = 0; + reloadItems(); }, - query: getQuery(tabElement), + query: getQuery(), button: e.target }); }); diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index f2f6c605d7..ea67583fed 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -11,8 +11,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -28,7 +28,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -36,22 +36,18 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('episodes'); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${params.topParentId}-episodes`; } function onViewStyleChange() { @@ -72,7 +68,7 @@ export default function (view, params, tabContent) { function reloadItems(page) { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -156,7 +152,7 @@ export default function (view, params, tabContent) { itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -173,7 +169,7 @@ export default function (view, params, tabContent) { self.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: 'episodes', serverId: ApiClient.serverId() }); @@ -185,7 +181,7 @@ export default function (view, params, tabContent) { }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; function initPage(tabElement) { @@ -222,7 +218,7 @@ export default function (view, params, tabContent) { callback: function () { reloadItems(tabElement); }, - query: getQuery(tabElement), + query: getQuery(), button: e.target }); }); @@ -232,8 +228,8 @@ export default function (view, params, tabContent) { }); btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + getPageData().view = viewStyle; + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); onViewStyleChange(); reloadItems(tabElement); }); diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index b9e02039eb..061089af86 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -1,7 +1,7 @@ import escapeHtml from 'escape-html'; import layoutManager from '../../components/layoutManager'; import loading from '../../components/loading/loading'; -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver'; import globalize from '../../scripts/globalize'; @@ -25,7 +25,7 @@ export default function (view, params, tabContent) { view: 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -36,7 +36,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('seriesgenres'); + return `${params.topParentId}-seriesgenres`; } function getPromise() { @@ -176,7 +176,7 @@ export default function (view, params, tabContent) { elem.innerHTML = html; lazyLoader.lazyChildren(elem, fillItemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); }); } @@ -199,7 +199,7 @@ export default function (view, params, tabContent) { self.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index 3dea00f1b8..f6751f38f3 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -11,8 +11,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -27,7 +27,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -35,22 +35,18 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('series'); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${params.topParentId}-series`; } const onViewStyleChange = () => { @@ -71,7 +67,7 @@ export default function (view, params, tabContent) { const reloadItems = (page) => { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { function onNextPageClick() { if (isLoading) { @@ -185,7 +181,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -201,12 +197,12 @@ export default function (view, params, tabContent) { this.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: 'series', serverId: ApiClient.serverId() }); Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; + getQuery().StartIndex = 0; reloadItems(tabContent); }); filterDialog.show(); @@ -214,7 +210,7 @@ export default function (view, params, tabContent) { }; this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; const initPage = (tabElement) => { @@ -223,7 +219,7 @@ export default function (view, params, tabContent) { alphaPickerElement.addEventListener('alphavaluechanged', function (e) { const newValue = e.detail.value; - const query = getQuery(tabElement); + const query = getQuery(); if (newValue === '#') { query.NameLessThan = 'A'; delete query.NameStartsWith; @@ -274,10 +270,10 @@ export default function (view, params, tabContent) { id: 'PremiereDate,SortName' }], callback: function () { - getQuery(tabElement).StartIndex = 0; + getQuery().StartIndex = 0; reloadItems(tabElement); }, - query: getQuery(tabElement), + query: getQuery(), button: e.target }); }); @@ -287,9 +283,9 @@ export default function (view, params, tabContent) { }); btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; + getPageData().view = viewStyle; + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); + getQuery().StartIndex = 0; onViewStyleChange(); reloadItems(tabElement); }); @@ -300,7 +296,7 @@ export default function (view, params, tabContent) { this.renderTab = () => { reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); + this.alphaPicker?.updateControls(getQuery()); }; } diff --git a/src/controllers/shows/tvstudios.js b/src/controllers/shows/tvstudios.js index 26ed743f73..3f75cd27fd 100644 --- a/src/controllers/shows/tvstudios.js +++ b/src/controllers/shows/tvstudios.js @@ -1,9 +1,8 @@ import loading from '../../components/loading/loading'; -import libraryBrowser from '../../scripts/libraryBrowser'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; function getQuery(params) { - const key = getSavedQueryKey(); + const key = getSavedQueryKey(params); let pageData = data[key]; if (!pageData) { @@ -23,8 +22,8 @@ function getQuery(params) { return pageData.query; } -function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('studios'); +function getSavedQueryKey(params) { + return `${params.topParentId}-studios`; } function getPromise(context, params) { diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js index ce4f6410a1..abf1488688 100644 --- a/src/scripts/libraryBrowser.js +++ b/src/scripts/libraryBrowser.js @@ -1,43 +1,5 @@ -import * as userSettings from './settings/userSettings'; import globalize from './globalize'; -export function getSavedQueryKey(modifier) { - return window.location.href.split('#')[0] + (modifier || ''); -} - -export function loadSavedQueryValues(key, query) { - let values = userSettings.get(key); - - if (values) { - values = JSON.parse(values); - return Object.assign(query, values); - } - - return query; -} - -export function saveQueryValues(key, query) { - const values = {}; - - if (query.SortBy) { - values.SortBy = query.SortBy; - } - - if (query.SortOrder) { - values.SortOrder = query.SortOrder; - } - - userSettings.set(key, JSON.stringify(values)); -} - -export function saveViewSetting (key, value) { - userSettings.set(key + '-_view', value); -} - -export function getSavedView (key) { - return userSettings.get(key + '-_view'); -} - export function showLayoutMenu (button, currentLayout, views) { let dispatchEvent = true; @@ -204,11 +166,6 @@ export function showSortMenu (options) { } const libraryBrowser = { - getSavedQueryKey, - loadSavedQueryValues, - saveQueryValues, - saveViewSetting, - getSavedView, showLayoutMenu, getQueryPagingHtml, showSortMenu diff --git a/src/scripts/playlists.js b/src/scripts/playlists.js index aba0c389ae..b8b7380b57 100644 --- a/src/scripts/playlists.js +++ b/src/scripts/playlists.js @@ -9,8 +9,8 @@ import '../elements/emby-itemscontainer/emby-itemscontainer'; import Dashboard from '../utils/dashboard'; export default function (view) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -23,7 +23,7 @@ export default function (view) { Fields: 'PrimaryImageAspectRatio,SortName,CumulativeRunTimeTicks,CanDelete', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -31,22 +31,18 @@ export default function (view) { } pageData.query.ParentId = libraryMenu.getTopParentId(); - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey(); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${libraryMenu.getTopParentId()}-playlists`; } function showLoadingMessage() { @@ -58,7 +54,7 @@ export default function (view) { } function onViewStyleChange() { - const viewStyle = getPageData(view).view; + const viewStyle = getPageData().view; const itemsContainer = view.querySelector('.itemsContainer'); if (viewStyle == 'List') { @@ -74,7 +70,7 @@ export default function (view) { function reloadItems() { showLoadingMessage(); - const query = getQuery(view); + const query = getQuery(); const promise1 = ApiClient.getItems(Dashboard.getCurrentUserId(), query); // TODO: promise2 is unused, check if necessary. const promise2 = Dashboard.getCurrentUser(); @@ -83,7 +79,7 @@ export default function (view) { // TODO: Is the scroll necessary? window.scrollTo(0, 0); let html = ''; - const viewStyle = getPageData(view).view; + const viewStyle = getPageData().view; view.querySelector('.listTopPaging').innerHTML = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, @@ -172,14 +168,14 @@ export default function (view) { if (btnChangeLayout) { btnChangeLayout.addEventListener('layoutchange', function (e) { const layout = e.detail.viewStyle; - getPageData(view).view = layout; - libraryBrowser.saveViewSetting(getSavedQueryKey(view), layout); + getPageData().view = layout; + userSettings.saveViewSetting(getSavedQueryKey(), layout); onViewStyleChange(); reloadItems(); }); } - libraryBrowser.saveQueryValues(getSavedQueryKey(view), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); hideLoadingMessage(); }); } diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 086ae747dc..bfe060868d 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -533,6 +533,24 @@ export class UserSettings { return this.set(key, JSON.stringify(values)); } + /** + * Get view layout setting. + * @param {string} key - View Setting key. + * @return {string} View Setting value. + */ + getSavedView(key) { + return this.get(key + '-_view'); + } + + /** + * Set view layout setting. + * @param {string} key - View Setting key. + * @param {string} value - View Setting value. + */ + saveViewSetting(key, value) { + return this.set(key + '-_view', value); + } + /** * Get subtitle appearance settings. * @param {string|undefined} key - Settings key. @@ -638,3 +656,5 @@ export const setFilter = currentSettings.setFilter.bind(currentSettings); export const getFilter = currentSettings.getFilter.bind(currentSettings); export const customCss = currentSettings.customCss.bind(currentSettings); export const disableCustomCss = currentSettings.disableCustomCss.bind(currentSettings); +export const getSavedView = currentSettings.getSavedView.bind(currentSettings); +export const saveViewSetting = currentSettings.saveViewSetting.bind(currentSettings); From f47bd0633d159ef86dd80c83127a2ff0720a9735 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sat, 8 Jul 2023 20:04:51 +0300 Subject: [PATCH 07/59] Add Sort Menu setting components --- .../components/library/SortButton.tsx | 185 ++++++++++++++++++ src/types/libraryTab.ts | 5 +- 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/apps/experimental/components/library/SortButton.tsx diff --git a/src/apps/experimental/components/library/SortButton.tsx b/src/apps/experimental/components/library/SortButton.tsx new file mode 100644 index 0000000000..7deeae349b --- /dev/null +++ b/src/apps/experimental/components/library/SortButton.tsx @@ -0,0 +1,185 @@ +import React, { FC, useCallback } from 'react'; +import IconButton from '@mui/material/IconButton'; +import MenuItem from '@mui/material/MenuItem'; +import Popover from '@mui/material/Popover'; +import Typography from '@mui/material/Typography'; +import Divider from '@mui/material/Divider'; +import Box from '@mui/material/Box'; +import InputLabel from '@mui/material/InputLabel'; +import FormControl from '@mui/material/FormControl'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import SortByAlphaIcon from '@mui/icons-material/SortByAlpha'; + +import globalize from 'scripts/globalize'; +import { LibraryViewSettings } from 'types/library'; +import { LibraryTab } from 'types/libraryTab'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client'; + +const sortMenuOptions = [ + { label: 'Name', value: ItemSortBy.SortName }, + { label: 'OptionRandom', value: ItemSortBy.Random }, + { label: 'OptionImdbRating', value: ItemSortBy.CommunityRating }, + { label: 'OptionCriticRating', value: ItemSortBy.CriticRating }, + { label: 'OptionDateAdded', value: ItemSortBy.DateCreated }, + { label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed }, + { label: 'OptionParentalRating', value: ItemSortBy.OfficialRating }, + { label: 'OptionPlayCount', value: ItemSortBy.PlayCount }, + { label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate }, + { label: 'Runtime', value: ItemSortBy.Runtime } +]; + +const sortOrderMenuOptions = [ + { label: 'Ascending', value: SortOrder.Ascending }, + { label: 'Descending', value: SortOrder.Descending } +]; + +interface SortButtonProps { + viewType: LibraryTab; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch< + React.SetStateAction + >; +} + +const SortButton: FC = ({ + viewType, + libraryViewSettings, + setLibraryViewSettings +}) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const id = open ? 'sort-popover' : undefined; + + const handleClick = useCallback((event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }, []); + + const handleClose = useCallback(() => { + setAnchorEl(null); + }, []); + + const onSelectChange = useCallback( + (event: SelectChangeEvent) => { + const name = event.target.name; + + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + [name]: event.target.value + })); + }, + [setLibraryViewSettings] + ); + + const getVisibleSortMenu = () => { + const visibleSortMenu: ItemSortBy[] = [ItemSortBy.SortName, ItemSortBy.Random, ItemSortBy.DateCreated]; + + if ( + viewType !== LibraryTab.Photos + && viewType !== LibraryTab.Videos + && viewType !== LibraryTab.Books + ) { + visibleSortMenu.push(ItemSortBy.CommunityRating); + visibleSortMenu.push(ItemSortBy.CriticRating); + visibleSortMenu.push(ItemSortBy.DatePlayed); + visibleSortMenu.push(ItemSortBy.OfficialRating); + visibleSortMenu.push(ItemSortBy.PlayCount); + visibleSortMenu.push(ItemSortBy.PremiereDate); + visibleSortMenu.push(ItemSortBy.Runtime); + } + + return visibleSortMenu; + }; + + return ( + + + + + + + + + + {globalize.translate('LabelSortBy')} + + + + + + + + + + {globalize.translate('LabelSortOrder')} + + + + + + + ); +}; + +export default SortButton; diff --git a/src/types/libraryTab.ts b/src/types/libraryTab.ts index 8ccf93663e..1484ed9646 100644 --- a/src/types/libraryTab.ts +++ b/src/types/libraryTab.ts @@ -19,5 +19,8 @@ export enum LibraryTab { Songs = 'songs', Suggestions = 'suggestions', Trailers = 'trailers', - Upcoming = 'upcoming' + Upcoming = 'upcoming', + Photos = 'photos', + Videos = 'videos', + Books = 'books', } From e767077826f7ea87253d017d189a54e18b34780b Mon Sep 17 00:00:00 2001 From: TelepathicWalrus Date: Mon, 31 Jul 2023 12:02:52 +0100 Subject: [PATCH 08/59] Remove LUFS scan option from non music libraries --- src/components/libraryoptionseditor/libraryoptionseditor.js | 2 +- .../libraryoptionseditor/libraryoptionseditor.template.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index 36ee3c7978..11c0806104 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -416,7 +416,7 @@ export function setContentType(parent, contentType) { } } - parent.querySelector('.chkEnableLUFSScan').classList.toggle('hide', contentType !== 'music'); + parent.querySelector('.chkEnableLUFSScanContainer').classList.toggle('hide', contentType !== 'music'); if (contentType === 'tvshows') { parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide'); diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.template.html b/src/components/libraryoptionseditor/libraryoptionseditor.template.html index 746b67107b..a221f53bbf 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.template.html +++ b/src/components/libraryoptionseditor/libraryoptionseditor.template.html @@ -55,7 +55,7 @@
${LabelEnableRealtimeMonitorHelp}
-
+
From 77e7d487a41a5d87baf1d843ed7d274d805effd6 Mon Sep 17 00:00:00 2001 From: cheat2winPT Date: Thu, 24 Aug 2023 14:53:46 +0000 Subject: [PATCH 20/59] Translated using Weblate (Portuguese (Portugal)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_PT/ --- src/strings/pt-pt.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json index a9c9d901e8..0e1c522a4b 100644 --- a/src/strings/pt-pt.json +++ b/src/strings/pt-pt.json @@ -442,7 +442,7 @@ "MessageNoPluginsInstalled": "Não existe nenhuma extensão instalada.", "MessageNoTrailersFound": "Instale o canal de trailers para melhorar a sua experiência, adicionando uma biblioteca de trailers da internet.", "MessageNothingHere": "Nada aqui.", - "MessagePasswordResetForUsers": "As palavras-passe dos seguintes utilizadores foram repostas. Deverão utilizar os 'Easy PIN' que foram usados para realizar a reposição para iniciar sessão.", + "MessagePasswordResetForUsers": "As palavras-passe dos seguintes utilizadores foram repostas. Deverão utilizar os PINs que foram usados para realizar a reposição para iniciar sessão.", "MessagePleaseEnsureInternetMetadata": "Certifique-se que a transferência de metadados da Internet está ativa.", "MessageReenableUser": "Veja abaixo para reativar", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "As seguintes pastas multimédia serão removidas da Biblioteca", @@ -1733,5 +1733,20 @@ "LabelTonemappingMode": "Modo de Tone Mapping", "LabelDate": "Data", "LogLevel.Information": "Informação", - "Unknown": "Desconhecido" + "Unknown": "Desconhecido", + "HeaderEpisodesStatus": "Estado do Episódio", + "LabelMediaDetails": "Detalhes Multimédia", + "LogLevel.Trace": "Vestígio", + "LogLevel.Error": "Erro", + "LogLevel.Critical": "Crítico", + "LogLevel.None": "Nenhum", + "LogLevel.Warning": "Aviso", + "HeaderConfirmRepositoryInstallation": "Confirmar a Instalação do Repositório de Plugins", + "AllowSegmentDeletion": "Apagar segmentos", + "AllowSegmentDeletionHelp": "Apagar segmentos velhos depois de terem sido enviados para o cliente. Isto previne o ter de guardar todo o ficheiro transcodificado no disco. Funcionará apenas com o estrangulamento ligado. Desliga esta opção se tiveres problemas de reprodução.", + "LabelThrottleDelaySeconds": "Estrangular depois", + "LabelThrottleDelaySecondsHelp": "Tempo em segundos depois o qual o transcodificador irá ser estrangulado. Deve ser suficientemente grande para o cliente manter um buffer saudável. Só funciona se o estrangulamento estiver ligado.", + "LabelSegmentKeepSeconds": "Tempo para guardar segmentos", + "LabelSegmentKeepSecondsHelp": "Tempo em segundos no qual os segmentos deve ser guardados antes de serem gravados por cima. Deve ser superior a \"Estrangular depois\". Só funciona se a eliminação de segmentos estiver ligada.", + "LabelStereoDownmixAlgorithm": "Algoritmo para reduzir a mistura para Stereo" } From 8bb46dbef98587e8d256b5386181417bcedfe864 Mon Sep 17 00:00:00 2001 From: stanol Date: Thu, 24 Aug 2023 15:21:34 +0000 Subject: [PATCH 21/59] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index c22ea84559..ec2b00e516 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1474,7 +1474,7 @@ "SeriesCancelled": "Серіал скасовано.", "Series": "Серіал", "SendMessage": "Відправити повідомлення", - "SelectServer": "Виберіть Сервер", + "SelectServer": "Вибрати Сервер", "SelectAdminUsername": "Будь ласка, виберіть ім’я користувача для облікового запису адміністратора.", "Season": "Сезон", "SearchResults": "Результати пошуку", From 838f6e2137ef46e5e5b5008e770c9141879b563b Mon Sep 17 00:00:00 2001 From: cheat2winPT Date: Thu, 24 Aug 2023 14:58:51 +0000 Subject: [PATCH 22/59] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt/ --- src/strings/pt.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/strings/pt.json b/src/strings/pt.json index 93fe7dcd17..eb5e9ce583 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -1053,7 +1053,7 @@ "NoNewDevicesFound": "Não foram encontrados novos dispositivos. Para adicionar um novo sintonizador, feche esta caixa de diálogo e insira as informações do dispositivo manualmente.", "NoCreatedLibraries": "Parece que você ainda não criou nenhuma biblioteca. {0} Deseja criar um agora? {1}", "No": "Não", - "Mobile": "Celular", + "Mobile": "Telemóvel", "MetadataSettingChangeHelp": "Alterar as configurações de metadados afetará o novo conteúdo adicionado daqui para frente. Para atualizar o conteúdo existente, abra a tela de detalhes e clique no botão atualizar ou execute atualizações em massa usando o gerenciador de metadados.", "MetadataManager": "Gestor de metadados", "Metadata": "Metadados", @@ -1063,12 +1063,12 @@ "MessagePlayAccessRestricted": "A reprodução deste conteúdo está atualmente restrita. Entre em contato com o administrador do servidor para obter mais informações.", "MessageNoServersAvailable": "Nenhum servidor foi encontrado usando a descoberta automática de servidores.", "MessageNoCollectionsAvailable": "As coleções permitem que você desfrute de agrupamentos personalizados de filmes, séries e álbuns. Clique no botão + para começar a criar coleções.", - "MessageConfirmAppExit": "Você quer sair?", + "MessageConfirmAppExit": "Deseja sair?", "MediaInfoLayout": "Layout", - "MediaInfoLanguage": "Língua", - "MediaInfoInterlaced": "Entrelaçada", + "MediaInfoLanguage": "Idioma", + "MediaInfoInterlaced": "Entrelaçado", "MediaInfoFramerate": "Taxa de quadros", - "MediaInfoForced": "Forçar", + "MediaInfoForced": "Forçado", "MediaInfoExternal": "Externo", "MediaInfoDefault": "Padrão", "MediaInfoCodecTag": "Codec tag", @@ -1076,7 +1076,7 @@ "MediaInfoBitrate": "Taxa de bits", "MediaInfoBitDepth": "Profundidade de bits", "MediaInfoAspectRatio": "Proporção da tela", - "ManageRecording": "Gerenciar gravações", + "ManageRecording": "Gerenciar gravação", "Logo": "Logo", "List": "Lista", "LeaveBlankToNotSetAPassword": "Você pode deixar esse campo em branco para definir nenhuma senha.", @@ -1084,22 +1084,22 @@ "Large": "Ampla", "LanNetworksHelp": "Lista separada por vírgula de endereços IP ou entradas de máscara de rede/IP para redes que serão consideradas na rede local ao impor restrições de largura de banda. Se definido, todos os outros endereços IP serão considerados na rede externa e estarão sujeitos às restrições de largura de banda externa. Se deixado em branco, apenas a sub-rede do servidor é considerada na rede local.", "LabelffmpegPathHelp": "O caminho para o arquivo ou pasta do aplicativo ffmpeg que contém o ffmpeg.", - "LabelffmpegPath": "FFmpeg caminho", + "LabelffmpegPath": "Caminho do FFmpeg", "LabelYear": "Ano", "LabelXDlnaDoc": "Documentação X-DLNA", "LabelXDlnaCap": "Limite X-DLNA", "LabelWeb": "Web", - "LabelVideoCodec": "Vídeo: codec", - "LabelVideoBitrate": "Vídeo taxa de bits", + "LabelVideoCodec": "Codec do vídeo", + "LabelVideoBitrate": "Taxa de bits do vídeo", "DashboardArchitecture": "Arquitetura: {0}", "DashboardServerName": "Servidor: {0}", "DashboardVersionNumber": "Versão: {0}", "LabelVersion": "Versão", "LabelVaapiDeviceHelp": "Este é o nó de renderização usado para aceleração de hardware.", - "LabelVaapiDevice": "VA API Dispositivo", + "LabelVaapiDevice": "Dispositivo VA-API", "LabelUserAgent": "Agente de usuário", "LabelTranscodes": "Transcodificação", - "LabelTranscodingFramerate": "Transcodificação frame por segundo", + "LabelTranscodingFramerate": "Transcodificação de taxa de quadros", "LabelTranscodingProgress": "Progresso da transcodificação", "LabelTitle": "Título", "LabelTheme": "Tema", @@ -1135,7 +1135,7 @@ "Episode": "Episódio", "OptionRequirePerfectSubtitleMatch": "Baixar apenas legendas que correspondem corretamente aos arquivos de vídeo", "OptionRandom": "Aleatório", - "OptionLoginAttemptsBeforeLockout": "Determinar a quantidade de tentativas de login incorretas até que ocorra bloqueio.", + "OptionLoginAttemptsBeforeLockout": "Determina a quantidade de tentativas de login incorretas até que ocorra bloqueio.", "OptionIsSD": "Definição padrão", "OptionIsHD": "Alta definição", "OptionEnableExternalContentInSuggestions": "Habilitar sugestão de conteúdo externo", @@ -1145,16 +1145,16 @@ "OptionAllowVideoPlaybackRemuxing": "Permitir execução de vídeo que requer conversão sem recodificar", "OptionAllowLinkSharingHelp": "Somente páginas da web que contêm informações sobre mídia são compartilhadas. Os arquivos de mídia nunca são compartilhados publicamente. O tempo de compartilhamento é limitado e expira após {0} dias.", "Option3D": "3D", - "NextUp": "Próximo", + "NextUp": "A seguir", "Next": "Próximo", - "NewEpisodesOnly": "apenas novos episódios", + "NewEpisodesOnly": "Apenas novos episódios", "NewEpisodes": "Novos episódios", - "NewCollectionHelp": "Coleções permitem criar agrupamentos personalizados de filmes ou de outros conteúdos da biblioteca.", + "NewCollectionHelp": "As coleções permitem criar agrupamentos personalizados de filmes e outros conteúdos da biblioteca.", "BoxSet": "Coleção", "AlbumArtist": "Álbum do Artista", "Quality": "Qualidade", "Previous": "Anterior", - "PictureInPicture": "vídeo destacado", + "PictureInPicture": "Vídeo em janela", "OptionRequirePerfectSubtitleMatchHelp": "Solicitar a \"correspondência perfeita\" filtrará as legendas incluindo apenas aquelas que foram testadas com o arquivo de vídeo. Desmarcar isto aumentará a probabilidade de baixar legendas, mas poderá obter legendas incorretas ou não sincronizadas.", "StopRecording": "Parar gravação", "ShowYear": "Exibir ano", @@ -1186,7 +1186,7 @@ "OptionExtractChapterImage": "Ativar extração de imagem de capítulo", "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir informações de episódios incorporados sobre nomes de arquivos", "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Isso usa as informações do episódio dos metadados incorporados, se disponíveis.", - "PreferEmbeddedTitlesOverFileNamesHelp": "Isso determina o título quando nenhum metadado da Internet ou local está disponível.", + "PreferEmbeddedTitlesOverFileNamesHelp": "Determina o título quando nenhum metadado da Internet ou local está disponível.", "PlaybackErrorNoCompatibleStream": "Este cliente não é compatível com a mídia e o servidor não está enviando um formato de mídia compatível.", "Person": "Pessoa", "LabelRequireHttpsHelp": "Se marcado, o servidor redirecionará automaticamente todas as solicitações por HTTP para HTTPS. Isso não terá efeito se o servidor não estiver escutando HTTPS.", From fd24e7ff6a5e6b36b8049b9d5d3c60f8a0d33e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roxeda=E2=84=A2?= Date: Thu, 24 Aug 2023 15:00:38 +0000 Subject: [PATCH 23/59] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt/ --- src/strings/pt.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/strings/pt.json b/src/strings/pt.json index eb5e9ce583..884060a5ba 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -1141,9 +1141,9 @@ "OptionEnableExternalContentInSuggestions": "Habilitar sugestão de conteúdo externo", "OptionDisplayFolderViewHelp": "Exiba pastas ao lado de outras bibliotecas de mídia. Isso pode ser útil se você quiser ter uma visualização simples de pastas.", "OptionDisplayFolderView": "Exibir uma exibição de pasta para mostrar pastas de mídia simples", - "OptionBluray": "Bluray", - "OptionAllowVideoPlaybackRemuxing": "Permitir execução de vídeo que requer conversão sem recodificar", - "OptionAllowLinkSharingHelp": "Somente páginas da web que contêm informações sobre mídia são compartilhadas. Os arquivos de mídia nunca são compartilhados publicamente. O tempo de compartilhamento é limitado e expira após {0} dias.", + "OptionBluray": "Blu-ray", + "OptionAllowVideoPlaybackRemuxing": "Permitir reprodução de vídeo que requer conversão sem recodificação", + "OptionAllowLinkSharingHelp": "Somente páginas da web que contêm informações sobre mídia são compartilhadas. Os arquivos de mídia nunca são compartilhados publicamente. As ações são limitadas no tempo e expiram após {0} dias.", "Option3D": "3D", "NextUp": "A seguir", "Next": "Próximo", From bb12bb94b75ed54a202e987c47e149d51e4253be Mon Sep 17 00:00:00 2001 From: Cota Fainado <20hvoby5pb@protonmail.com> Date: Thu, 24 Aug 2023 15:03:11 +0000 Subject: [PATCH 24/59] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt/ --- src/strings/pt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/pt.json b/src/strings/pt.json index 884060a5ba..4356e6ad8f 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -83,7 +83,7 @@ "Screenshot": "Captura de Ecrã", "Schedule": "Agendamentos", "ScanForNewAndUpdatedFiles": "Procurar ficheiros novos ou actualizados", - "SaveSubtitlesIntoMediaFoldersHelp": "Salvar arquivos de legendas junto aos arquivos vídeo facilita o gerenciamento.", + "SaveSubtitlesIntoMediaFoldersHelp": "Guardar ficheiros de legendas junto aos ficheiros vídeo facilita a sua gestão.", "SaveSubtitlesIntoMediaFolders": "Guardar legendas nas pastas multimédia", "Save": "Guardar", "Saturday": "Sábado", From 71abbf05f4d4e4c24c2b81d1b914c7ee286ba222 Mon Sep 17 00:00:00 2001 From: newo-2001 Date: Sat, 12 Aug 2023 15:43:47 +0200 Subject: [PATCH 25/59] Make backdrop screensaver interval configurable --- src/components/displaySettings/displaySettings.js | 5 +++++ .../displaySettings/displaySettings.template.html | 5 +++++ src/components/slideshow/slideshow.js | 2 +- src/plugins/backdropScreensaver/plugin.js | 6 +++++- src/scripts/settings/userSettings.js | 14 ++++++++++++++ src/strings/en-us.json | 2 ++ 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index c0ede8aec5..9103011ac9 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -90,8 +90,10 @@ function loadForm(context, user, userSettings) { if (appHost.supports('screensaver')) { context.querySelector('.selectScreensaverContainer').classList.remove('hide'); + context.querySelector('.txtBackdropScreensaverIntervalContainer').classList.remove('hide'); } else { context.querySelector('.selectScreensaverContainer').classList.add('hide'); + context.querySelector('.txtBackdropScreensaverIntervalContainer').classList.add('hide'); } if (datetime.supportsLocalization()) { @@ -105,6 +107,8 @@ function loadForm(context, user, userSettings) { loadScreensavers(context, userSettings); + context.querySelector('#txtBackdropScreensaverInterval').value = userSettings.backdropScreensaverInterval(); + context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false; context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs(); @@ -147,6 +151,7 @@ function saveUser(context, user, userSettingsInstance, apiClient) { userSettingsInstance.theme(context.querySelector('#selectTheme').value); userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value); userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); + userSettingsInstance.backdropScreensaverInterval(context.querySelector('#txtBackdropScreensaverInterval').value); userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index a61537cbf3..a9b7b9d2a3 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -203,6 +203,11 @@ +
+ +
${LabelBackdropScreensaverIntervalHelp}
+
+