From aec90cbc96598a57a597fae91d70b707272bdb85 Mon Sep 17 00:00:00 2001 From: TheMelmacian <76712303+TheMelmacian@users.noreply.github.com> Date: Fri, 7 Jul 2023 22:51:19 +0200 Subject: [PATCH 01/49] use random backdrop image on item details pages --- CONTRIBUTORS.md | 1 + src/controllers/itemDetails/index.js | 29 ++------------- src/utils/url.ts | 55 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 52006a7b46..eccfd4cf27 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -65,6 +65,7 @@ - [Merlin Sievers](https://github.com/dann-merlin) - [Fishbigger](https://github.com/fishbigger) - [sleepycatcoding](https://github.com/sleepycatcoding) + - [TheMelmacian](https://github.com/TheMelmacian) # Emby Contributors diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 37f5d3108a..719c1b2671 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -36,6 +36,7 @@ import Dashboard from '../../utils/dashboard'; import ServerConnections from '../../components/ServerConnections'; import confirm from '../../components/confirm/confirm'; import { download } from '../../scripts/fileDownloader'; +import { getRandomItemBackdropImageUrl } from '../../utils/url'; function autoFocus(container) { import('../../components/autoFocuser').then(({ default: autoFocuser }) => { @@ -501,34 +502,12 @@ function renderDetailPageBackdrop(page, item, apiClient) { return false; } - let imgUrl; let hasbackdrop = false; const itemBackdropElement = page.querySelector('#itemBackdrop'); - if (item.BackdropImageTags?.length) { - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: 'Backdrop', - maxWidth: dom.getScreenWidth(), - index: 0, - tag: item.BackdropImageTags[0] - }); - imageLoader.lazyImage(itemBackdropElement, imgUrl); - hasbackdrop = true; - } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { - type: 'Backdrop', - maxWidth: dom.getScreenWidth(), - index: 0, - tag: item.ParentBackdropImageTags[0] - }); - imageLoader.lazyImage(itemBackdropElement, imgUrl); - hasbackdrop = true; - } else if (item.ImageTags?.Primary) { - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: 'Primary', - maxWidth: dom.getScreenWidth(), - tag: item.ImageTags.Primary - }); + const imgUrl = getRandomItemBackdropImageUrl(apiClient, item, { maxWitdh: dom.getScreenWidth() }); + + if (imgUrl) { imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; } else { diff --git a/src/utils/url.ts b/src/utils/url.ts index 1c65b2343a..e65df92ab3 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -1,3 +1,7 @@ +import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { randomInt } from './number'; +import { ApiClient } from 'jellyfin-apiclient'; + /** * Gets the url search string. * This function should be used instead of location.search alone, because the app router @@ -33,3 +37,54 @@ export const getParameterByName = (name: string, url?: string | null | undefined // eslint-disable-next-line compat/compat return new URLSearchParams(url).get(name) || ''; }; + +export interface ScaleImageOptions { + maxWidth?: number; + width?: number; + maxHeight?: number; + height?: number; + fillWidth?: number; + fillHeight?: number; + quality?: number; +} + +/** + * Returns the url of a random backdrop image of an item. + * If the item has no backdrop image, the url of a random backdrop image of the parent item is returned. + * Falls back to the primary image (cover) of the item, if neither the item nor it's parent have at least one backdrop image. + * Returns undefined if no usable image was found. + * @param apiClient The ApiClient to generate the url. + * @param item The item for which the backdrop image is requested. + * @param options Optional; allows to scale the backdrop image. + * @returns The url of a random backdrop image of the provided item. + */ +export const getRandomItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, options: ScaleImageOptions = {}): string | undefined => { + let imgUrl; + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + const backdropImgIndex = randomInt(0, item.BackdropImageTags.length - 1); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + imgUrl = apiClient.getScaledImageUrl(item.Id!, { + type: 'Backdrop', + index: backdropImgIndex, + tag: item.BackdropImageTags[backdropImgIndex], + ...options + }); + } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + const backdropImgIndex = randomInt(0, item.ParentBackdropImageTags.length - 1); + imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { + type: 'Backdrop', + index: backdropImgIndex, + tag: item.ParentBackdropImageTags[backdropImgIndex], + ...options + }); + } else if (item.ImageTags && item.ImageTags.Primary) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + imgUrl = apiClient.getScaledImageUrl(item.Id!, { + type: 'Primary', + tag: item.ImageTags.Primary, + ...options + }); + } + return imgUrl; +}; From 23a22d6b24c7e2808c2fb14a315061a9f5ce5c37 Mon Sep 17 00:00:00 2001 From: TheMelmacian <76712303+TheMelmacian@users.noreply.github.com> Date: Fri, 7 Jul 2023 22:54:44 +0200 Subject: [PATCH 02/49] use random backdrop image in video player --- src/components/playback/playbackmanager.js | 25 ++-------------------- src/plugins/htmlVideoPlayer/plugin.js | 8 ++++++- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 308acd79f5..8e09a45de0 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -14,6 +14,7 @@ import alert from '../alert'; import { PluginType } from '../../types/plugin.ts'; import { includesAny } from '../../utils/container.ts'; import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; +import { getRandomItemBackdropImageUrl } from '../../utils/url'; const UNLIMITED_ITEMS = -1; @@ -154,28 +155,6 @@ function mergePlaybackQueries(obj1, obj2) { return query; } -function backdropImageUrl(apiClient, item, options) { - options = options || {}; - options.type = options.type || 'Backdrop'; - - // If not resizing, get the original image - if (!options.maxWidth && !options.width && !options.maxHeight && !options.height && !options.fillWidth && !options.fillHeight) { - options.quality = 100; - } - - if (item.BackdropImageTags?.length) { - options.tag = item.BackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.Id, options); - } - - if (item.ParentBackdropImageTags?.length) { - options.tag = item.ParentBackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.ParentBackdropItemId, options); - } - - return null; -} - function getMimeType(type, container) { container = (container || '').toLowerCase(); @@ -2693,7 +2672,7 @@ class PlaybackManager { title: item.Name }; - const backdropUrl = backdropImageUrl(apiClient, item, {}); + const backdropUrl = getRandomItemBackdropImageUrl(apiClient, item); if (backdropUrl) { resultInfo.backdropUrl = backdropUrl; } diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 9e5a5865ee..2283fc54c0 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1664,7 +1664,13 @@ export class HtmlVideoPlayer { } } - return Promise.resolve(dlg.querySelector('video')); + const videoElement = dlg.querySelector('video'); + if (options.backdropUrl) { + // update backdrop image + videoElement.poster = options.backdropUrl; + } + + return Promise.resolve(videoElement); } } From 5df5f6cdb2824040c0f65a989dcbe9d2ac17399f Mon Sep 17 00:00:00 2001 From: TheMelmacian <76712303+TheMelmacian@users.noreply.github.com> Date: Wed, 19 Jul 2023 23:56:09 +0200 Subject: [PATCH 03/49] don't use random backdrop in item header --- src/components/playback/playbackmanager.js | 4 ++-- src/controllers/itemDetails/index.js | 4 ++-- src/utils/url.ts | 19 ++++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 8e09a45de0..29fe4aaedc 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -14,7 +14,7 @@ import alert from '../alert'; import { PluginType } from '../../types/plugin.ts'; import { includesAny } from '../../utils/container.ts'; import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; -import { getRandomItemBackdropImageUrl } from '../../utils/url'; +import { getItemBackdropImageUrl } from '../../utils/url'; const UNLIMITED_ITEMS = -1; @@ -2672,7 +2672,7 @@ class PlaybackManager { title: item.Name }; - const backdropUrl = getRandomItemBackdropImageUrl(apiClient, item); + const backdropUrl = getItemBackdropImageUrl(apiClient, item, true); if (backdropUrl) { resultInfo.backdropUrl = backdropUrl; } diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 719c1b2671..37c559f888 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -36,7 +36,7 @@ import Dashboard from '../../utils/dashboard'; import ServerConnections from '../../components/ServerConnections'; import confirm from '../../components/confirm/confirm'; import { download } from '../../scripts/fileDownloader'; -import { getRandomItemBackdropImageUrl } from '../../utils/url'; +import { getItemBackdropImageUrl } from '../../utils/url'; function autoFocus(container) { import('../../components/autoFocuser').then(({ default: autoFocuser }) => { @@ -505,7 +505,7 @@ function renderDetailPageBackdrop(page, item, apiClient) { let hasbackdrop = false; const itemBackdropElement = page.querySelector('#itemBackdrop'); - const imgUrl = getRandomItemBackdropImageUrl(apiClient, item, { maxWitdh: dom.getScreenWidth() }); + const imgUrl = getItemBackdropImageUrl(apiClient, item, false, { maxWitdh: dom.getScreenWidth() }); if (imgUrl) { imageLoader.lazyImage(itemBackdropElement, imgUrl); diff --git a/src/utils/url.ts b/src/utils/url.ts index e65df92ab3..1768807bc8 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -49,20 +49,21 @@ export interface ScaleImageOptions { } /** - * Returns the url of a random backdrop image of an item. - * If the item has no backdrop image, the url of a random backdrop image of the parent item is returned. + * Returns the url of the first or a random backdrop image of an item. + * If the item has no backdrop image, the url of the first or a random backdrop image of the parent item is returned. * Falls back to the primary image (cover) of the item, if neither the item nor it's parent have at least one backdrop image. * Returns undefined if no usable image was found. * @param apiClient The ApiClient to generate the url. * @param item The item for which the backdrop image is requested. + * @param random If set to true and the item has more than one backdrop, a random image is returned. * @param options Optional; allows to scale the backdrop image. - * @returns The url of a random backdrop image of the provided item. + * @returns The url of the first or a random backdrop image of the provided item. */ -export const getRandomItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, options: ScaleImageOptions = {}): string | undefined => { +export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, random = false, options: ScaleImageOptions = {}): string | undefined => { let imgUrl; - if (item.BackdropImageTags && item.BackdropImageTags.length) { - const backdropImgIndex = randomInt(0, item.BackdropImageTags.length - 1); + if (item.BackdropImageTags?.length) { + const backdropImgIndex = random ? randomInt(0, item.BackdropImageTags.length - 1) : 0; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion imgUrl = apiClient.getScaledImageUrl(item.Id!, { type: 'Backdrop', @@ -70,15 +71,15 @@ export const getRandomItemBackdropImageUrl = (apiClient: ApiClient, item: BaseIt tag: item.BackdropImageTags[backdropImgIndex], ...options }); - } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - const backdropImgIndex = randomInt(0, item.ParentBackdropImageTags.length - 1); + } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags?.length) { + const backdropImgIndex = random ? randomInt(0, item.ParentBackdropImageTags.length - 1) : 0; imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { type: 'Backdrop', index: backdropImgIndex, tag: item.ParentBackdropImageTags[backdropImgIndex], ...options }); - } else if (item.ImageTags && item.ImageTags.Primary) { + } else if (item.ImageTags?.Primary) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion imgUrl = apiClient.getScaledImageUrl(item.Id!, { type: 'Primary', From 51eeef00a619cdf0a929fdfc6824ec82cb666aea Mon Sep 17 00:00:00 2001 From: Sky-High Date: Tue, 8 Aug 2023 21:23:09 +0200 Subject: [PATCH 04/49] fix-cast-with-localhost-server --- src/plugins/chromecastPlayer/plugin.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 569e347cd2..38ac2c8660 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -335,16 +335,25 @@ class CastPlayer { apiClient = ServerConnections.currentApiClient(); } + /* If serverAddress is localhost,this address can not be used for the cast receiver device. + * Use the local address (ULA, Unique Local Address) in that case. + */ + const srvAddress = apiClient.serverAddress(); + const srvLocalAddress = srvAddress.startsWith('http://localhost') || srvAddress.startsWith('http://127.') || srvAddress.startsWith('http://[::1]') + ? apiClient.serverInfo().LocalAddress : srvAddress; + message = Object.assign(message, { userId: apiClient.getCurrentUserId(), deviceId: apiClient.deviceId(), accessToken: apiClient.accessToken(), - serverAddress: apiClient.serverAddress(), + serverAddress: srvLocalAddress, serverId: apiClient.serverId(), serverVersion: apiClient.serverVersion(), receiverName: receiverName }); + console.debug('cc: message{'+message.command+'; '+srvAddress+' -> '+srvLocalAddress+'}'); + const bitrateSetting = appSettings.maxChromecastBitrate(); if (bitrateSetting) { message.maxBitrate = bitrateSetting; From 7746795730a3a2ff9a7bc3618064817496530694 Mon Sep 17 00:00:00 2001 From: Sky-High Date: Tue, 8 Aug 2023 22:14:00 +0200 Subject: [PATCH 05/49] fix-sonar-and-lint --- src/plugins/chromecastPlayer/plugin.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 38ac2c8660..7a5e971228 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -339,8 +339,9 @@ class CastPlayer { * Use the local address (ULA, Unique Local Address) in that case. */ const srvAddress = apiClient.serverAddress(); - const srvLocalAddress = srvAddress.startsWith('http://localhost') || srvAddress.startsWith('http://127.') || srvAddress.startsWith('http://[::1]') - ? apiClient.serverInfo().LocalAddress : srvAddress; + const prefix = 'http' + ':' + '//'; + const checkLocalhost = srvAddress.startsWith(prefix + 'localhost') || srvAddress.startsWith(prefix + '127.') || srvAddress.startsWith(prefix + '[::1]') + const srvLocalAddress = checkLocalhost ? apiClient.serverInfo().LocalAddress : srvAddress; message = Object.assign(message, { userId: apiClient.getCurrentUserId(), @@ -352,7 +353,7 @@ class CastPlayer { receiverName: receiverName }); - console.debug('cc: message{'+message.command+'; '+srvAddress+' -> '+srvLocalAddress+'}'); + console.debug('cc: message{' + message.command + '; ' + srvAddress + ' -> ' + srvLocalAddress + '}'); const bitrateSetting = appSettings.maxChromecastBitrate(); if (bitrateSetting) { From a42c31d6c038af29037e89a7cf16ee6f9c5f1855 Mon Sep 17 00:00:00 2001 From: Sky-High Date: Tue, 8 Aug 2023 22:19:30 +0200 Subject: [PATCH 06/49] fix-sonar-and-lint2 --- src/plugins/chromecastPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 7a5e971228..efae979c17 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -340,7 +340,7 @@ class CastPlayer { */ const srvAddress = apiClient.serverAddress(); const prefix = 'http' + ':' + '//'; - const checkLocalhost = srvAddress.startsWith(prefix + 'localhost') || srvAddress.startsWith(prefix + '127.') || srvAddress.startsWith(prefix + '[::1]') + const checkLocalhost = srvAddress.startsWith(prefix + 'localhost') || srvAddress.startsWith(prefix + '127.') || srvAddress.startsWith(prefix + '[::1]'); const srvLocalAddress = checkLocalhost ? apiClient.serverInfo().LocalAddress : srvAddress; message = Object.assign(message, { From 4076148580e831561bfce12afff490f60aa4149a Mon Sep 17 00:00:00 2001 From: Sky-High Date: Tue, 8 Aug 2023 22:47:41 +0200 Subject: [PATCH 07/49] fix-sonar-and-lint3 --- src/plugins/chromecastPlayer/plugin.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index efae979c17..ff773e2a2a 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -27,12 +27,10 @@ function sendConnectionResult(isOk) { if (resolve) { resolve(); } + } else if (reject) { + reject(); } else { - if (reject) { - reject(); - } else { - playbackManager.removeActivePlayer(PlayerName); - } + playbackManager.removeActivePlayer(PlayerName); } } @@ -294,7 +292,7 @@ class CastPlayer { loadMedia(options, command) { if (!this.session) { console.debug('no session'); - return Promise.reject(); + return Promise.reject(new Error('no session')); } // convert items to smaller stubs to send minimal amount of information @@ -602,7 +600,7 @@ class ChromecastPlayer { currentResolve = null; currentReject = null; - return Promise.reject(); + return Promise.reject(new Error('tryPair failed')); } } From a15af2095e785d7583932a0339d8fc6dcc0523f6 Mon Sep 17 00:00:00 2001 From: minigt Date: Fri, 11 Aug 2023 00:49:22 -0300 Subject: [PATCH 08/49] Fix regex so moto edge doesn't mislead the browser(as edge) --- src/scripts/browser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/browser.js b/src/scripts/browser.js index 47b0635d52..92137e2af9 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -187,13 +187,13 @@ function supportsCssAnimation(allowPrefix) { const uaMatch = function (ua) { ua = ua.toLowerCase(); - const match = /(edg)[ /]([\w.]+)/.exec(ua) + const match = /(chrome)[ /]([\w.]+)/.exec(ua) + || /(edg)[ /]([\w.]+)/.exec(ua) || /(edga)[ /]([\w.]+)/.exec(ua) || /(edgios)[ /]([\w.]+)/.exec(ua) || /(edge)[ /]([\w.]+)/.exec(ua) || /(opera)[ /]([\w.]+)/.exec(ua) || /(opr)[ /]([\w.]+)/.exec(ua) - || /(chrome)[ /]([\w.]+)/.exec(ua) || /(safari)[ /]([\w.]+)/.exec(ua) || /(firefox)[ /]([\w.]+)/.exec(ua) || ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) From 04c3afd9e5c0977a628d3d29b6fe1ce2b443d574 Mon Sep 17 00:00:00 2001 From: minigt Date: Fri, 18 Aug 2023 22:46:34 -0300 Subject: [PATCH 09/49] Fixed sonarcloud code smell --- src/scripts/browser.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/scripts/browser.js b/src/scripts/browser.js index 92137e2af9..15898f03b1 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -148,14 +148,11 @@ let _supportsCssAnimation; let _supportsCssAnimationWithPrefix; function supportsCssAnimation(allowPrefix) { // TODO: Assess if this is still needed, as all of our targets should natively support CSS animations. - if (allowPrefix) { - if (_supportsCssAnimationWithPrefix === true || _supportsCssAnimationWithPrefix === false) { - return _supportsCssAnimationWithPrefix; - } - } else { - if (_supportsCssAnimation === true || _supportsCssAnimation === false) { - return _supportsCssAnimation; - } + if (allowPrefix && (_supportsCssAnimationWithPrefix === true || _supportsCssAnimationWithPrefix === false)) { + return _supportsCssAnimationWithPrefix; + } + if (_supportsCssAnimation === true || _supportsCssAnimation === false) { + return _supportsCssAnimation; } let animation = false; From 8ff6bc14874133d42b787a90ad16af497784d150 Mon Sep 17 00:00:00 2001 From: MBR#0001 Date: Sun, 23 Jul 2023 20:53:53 +0200 Subject: [PATCH 10/49] Add support for displaying more sub info --- .../subtitleeditor/subtitleeditor.js | 40 ++++++++++++++++--- src/strings/en-us.json | 6 ++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 572cccc1fa..56d138de96 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -194,7 +194,8 @@ function renderSearchResults(context, results) { html += ''; - const bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + const hasAnyFlags = result.IsHashMatch || result.AiTranslated || result.MachineTranslated || result.Forced || result.HearingImpaired; + const bodyClass = result.Comment || hasAnyFlags ? 'three-line' : 'two-line'; html += '
'; @@ -206,16 +207,45 @@ function renderSearchResults(context, results) { } if (result.DownloadCount != null) { - html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; + html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; } + + if (result.FrameRate) { + html += '' + globalize.translate('Framerate') + ': ' + result.FrameRate + ''; + } + html += '
'; if (result.Comment) { - html += '
' + escapeHtml(result.Comment) + '
'; + html += '
' + escapeHtml(result.Comment) + '
'; } - if (result.IsHashMatch) { - html += '
' + globalize.translate('PerfectMatch') + '
'; + if (hasAnyFlags) { + html += '
'; + + const spanOpen = ''; + + if (result.IsHashMatch) { + html += spanOpen + globalize.translate('PerfectMatch') + ''; + } + + if (result.AiTranslated) { + html += spanOpen + globalize.translate('AiTranslated') + ''; + } + + if (result.MachineTranslated) { + html += spanOpen + globalize.translate('MachineTranslated') + ''; + } + + if (result.Forced) { + html += spanOpen + globalize.translate('ForeignPartsOnly') + ''; + } + + if (result.HearingImpaired) { + html += spanOpen.replace('margin-right:0.25em;', '') + globalize.translate('HearingImpairedShort') + ''; + } + + html += '
'; } html += ''; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 10639d58ef..56b02ef059 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1715,5 +1715,9 @@ "Unreleased": "Not yet released", "LabelTonemappingMode": "Tone mapping mode", "TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode.", - "Unknown": "Unknown" + "Unknown": "Unknown", + "AiTranslated": "AI Translated", + "MachineTranslated": "Machine Translated", + "ForeignPartsOnly": "Forced/Foreign parts only", + "HearingImpairedShort": "HI/SDH" } From 8ec943bc306e62941dd80600def666218d9d7689 Mon Sep 17 00:00:00 2001 From: TheMelmacian <76712303+TheMelmacian@users.noreply.github.com> Date: Sat, 9 Sep 2023 15:24:52 +0200 Subject: [PATCH 11/49] move function to get backdrop image to api-client utils --- src/components/playback/playbackmanager.js | 2 +- src/controllers/itemDetails/index.js | 2 +- src/utils/jellyfin-apiclient/BackdropImage.ts | 55 ++++++++++++++++++ src/utils/url.ts | 56 ------------------- 4 files changed, 57 insertions(+), 58 deletions(-) create mode 100644 src/utils/jellyfin-apiclient/BackdropImage.ts diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 29fe4aaedc..a8192885c6 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -14,7 +14,7 @@ import alert from '../alert'; import { PluginType } from '../../types/plugin.ts'; import { includesAny } from '../../utils/container.ts'; import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; -import { getItemBackdropImageUrl } from '../../utils/url'; +import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/BackdropImage'; const UNLIMITED_ITEMS = -1; diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 37c559f888..1de640f6ec 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -36,7 +36,7 @@ import Dashboard from '../../utils/dashboard'; import ServerConnections from '../../components/ServerConnections'; import confirm from '../../components/confirm/confirm'; import { download } from '../../scripts/fileDownloader'; -import { getItemBackdropImageUrl } from '../../utils/url'; +import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/BackdropImage'; function autoFocus(container) { import('../../components/autoFocuser').then(({ default: autoFocuser }) => { diff --git a/src/utils/jellyfin-apiclient/BackdropImage.ts b/src/utils/jellyfin-apiclient/BackdropImage.ts new file mode 100644 index 0000000000..2d23fd506f --- /dev/null +++ b/src/utils/jellyfin-apiclient/BackdropImage.ts @@ -0,0 +1,55 @@ +import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { randomInt } from '../number'; +import { ApiClient } from 'jellyfin-apiclient'; + +export interface ScaleImageOptions { + maxWidth?: number; + width?: number; + maxHeight?: number; + height?: number; + fillWidth?: number; + fillHeight?: number; + quality?: number; +} + +/** + * Returns the url of the first or a random backdrop image of an item. + * If the item has no backdrop image, the url of the first or a random backdrop image of the parent item is returned. + * Falls back to the primary image (cover) of the item, if neither the item nor it's parent have at least one backdrop image. + * Returns undefined if no usable image was found. + * @param apiClient The ApiClient to generate the url. + * @param item The item for which the backdrop image is requested. + * @param random If set to true and the item has more than one backdrop, a random image is returned. + * @param options Optional; allows to scale the backdrop image. + * @returns The url of the first or a random backdrop image of the provided item. + */ +export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, random = false, options: ScaleImageOptions = {}): string | undefined => { + let imgUrl; + + if (item.BackdropImageTags?.length) { + const backdropImgIndex = random ? randomInt(0, item.BackdropImageTags.length - 1) : 0; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + imgUrl = apiClient.getScaledImageUrl(item.Id!, { + type: 'Backdrop', + index: backdropImgIndex, + tag: item.BackdropImageTags[backdropImgIndex], + ...options + }); + } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags?.length) { + const backdropImgIndex = random ? randomInt(0, item.ParentBackdropImageTags.length - 1) : 0; + imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { + type: 'Backdrop', + index: backdropImgIndex, + tag: item.ParentBackdropImageTags[backdropImgIndex], + ...options + }); + } else if (item.ImageTags?.Primary) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + imgUrl = apiClient.getScaledImageUrl(item.Id!, { + type: 'Primary', + tag: item.ImageTags.Primary, + ...options + }); + } + return imgUrl; +}; diff --git a/src/utils/url.ts b/src/utils/url.ts index 1768807bc8..1c65b2343a 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -1,7 +1,3 @@ -import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; -import { randomInt } from './number'; -import { ApiClient } from 'jellyfin-apiclient'; - /** * Gets the url search string. * This function should be used instead of location.search alone, because the app router @@ -37,55 +33,3 @@ export const getParameterByName = (name: string, url?: string | null | undefined // eslint-disable-next-line compat/compat return new URLSearchParams(url).get(name) || ''; }; - -export interface ScaleImageOptions { - maxWidth?: number; - width?: number; - maxHeight?: number; - height?: number; - fillWidth?: number; - fillHeight?: number; - quality?: number; -} - -/** - * Returns the url of the first or a random backdrop image of an item. - * If the item has no backdrop image, the url of the first or a random backdrop image of the parent item is returned. - * Falls back to the primary image (cover) of the item, if neither the item nor it's parent have at least one backdrop image. - * Returns undefined if no usable image was found. - * @param apiClient The ApiClient to generate the url. - * @param item The item for which the backdrop image is requested. - * @param random If set to true and the item has more than one backdrop, a random image is returned. - * @param options Optional; allows to scale the backdrop image. - * @returns The url of the first or a random backdrop image of the provided item. - */ -export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, random = false, options: ScaleImageOptions = {}): string | undefined => { - let imgUrl; - - if (item.BackdropImageTags?.length) { - const backdropImgIndex = random ? randomInt(0, item.BackdropImageTags.length - 1) : 0; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - imgUrl = apiClient.getScaledImageUrl(item.Id!, { - type: 'Backdrop', - index: backdropImgIndex, - tag: item.BackdropImageTags[backdropImgIndex], - ...options - }); - } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags?.length) { - const backdropImgIndex = random ? randomInt(0, item.ParentBackdropImageTags.length - 1) : 0; - imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { - type: 'Backdrop', - index: backdropImgIndex, - tag: item.ParentBackdropImageTags[backdropImgIndex], - ...options - }); - } else if (item.ImageTags?.Primary) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - imgUrl = apiClient.getScaledImageUrl(item.Id!, { - type: 'Primary', - tag: item.ImageTags.Primary, - ...options - }); - } - return imgUrl; -}; From 2b547f5a5313b47c6fac9142ae65c8762dd6e734 Mon Sep 17 00:00:00 2001 From: TheMelmacian <76712303+TheMelmacian@users.noreply.github.com> Date: Sat, 9 Sep 2023 15:28:50 +0200 Subject: [PATCH 12/49] small improvements --- src/utils/jellyfin-apiclient/BackdropImage.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/utils/jellyfin-apiclient/BackdropImage.ts b/src/utils/jellyfin-apiclient/BackdropImage.ts index 2d23fd506f..82387b8a72 100644 --- a/src/utils/jellyfin-apiclient/BackdropImage.ts +++ b/src/utils/jellyfin-apiclient/BackdropImage.ts @@ -24,12 +24,9 @@ export interface ScaleImageOptions { * @returns The url of the first or a random backdrop image of the provided item. */ export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, random = false, options: ScaleImageOptions = {}): string | undefined => { - let imgUrl; - - if (item.BackdropImageTags?.length) { + if (item.Id && item.BackdropImageTags?.length) { const backdropImgIndex = random ? randomInt(0, item.BackdropImageTags.length - 1) : 0; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - imgUrl = apiClient.getScaledImageUrl(item.Id!, { + return apiClient.getScaledImageUrl(item.Id, { type: 'Backdrop', index: backdropImgIndex, tag: item.BackdropImageTags[backdropImgIndex], @@ -37,19 +34,19 @@ export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, }); } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags?.length) { const backdropImgIndex = random ? randomInt(0, item.ParentBackdropImageTags.length - 1) : 0; - imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { + return apiClient.getScaledImageUrl(item.ParentBackdropItemId, { type: 'Backdrop', index: backdropImgIndex, tag: item.ParentBackdropImageTags[backdropImgIndex], ...options }); - } else if (item.ImageTags?.Primary) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - imgUrl = apiClient.getScaledImageUrl(item.Id!, { + } else if (item.Id && item.ImageTags?.Primary) { + return apiClient.getScaledImageUrl(item.Id, { type: 'Primary', tag: item.ImageTags.Primary, ...options }); + } else { + return undefined; } - return imgUrl; }; From d77379043922c88465a065a7b8504c8dafec8d7b Mon Sep 17 00:00:00 2001 From: Sky-High Date: Sun, 10 Sep 2023 20:11:02 +0200 Subject: [PATCH 13/49] Use URL API for better readability --- src/plugins/chromecastPlayer/plugin.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index ff773e2a2a..77d0f27c98 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -336,22 +336,22 @@ class CastPlayer { /* If serverAddress is localhost,this address can not be used for the cast receiver device. * Use the local address (ULA, Unique Local Address) in that case. */ - const srvAddress = apiClient.serverAddress(); - const prefix = 'http' + ':' + '//'; - const checkLocalhost = srvAddress.startsWith(prefix + 'localhost') || srvAddress.startsWith(prefix + '127.') || srvAddress.startsWith(prefix + '[::1]'); - const srvLocalAddress = checkLocalhost ? apiClient.serverInfo().LocalAddress : srvAddress; + const serverAddress = apiClient.serverAddress(); + const hostname = (new URL(serverAddress)).hostname; + const isLocalhost = hostname === 'localhost' || hostname.startsWith('127.') || hostname === '[::1]'; + const serverLocalAddress = isLocalhost ? apiClient.serverInfo().LocalAddress : serverAddress; message = Object.assign(message, { userId: apiClient.getCurrentUserId(), deviceId: apiClient.deviceId(), accessToken: apiClient.accessToken(), - serverAddress: srvLocalAddress, + serverAddress: serverLocalAddress, serverId: apiClient.serverId(), serverVersion: apiClient.serverVersion(), receiverName: receiverName }); - console.debug('cc: message{' + message.command + '; ' + srvAddress + ' -> ' + srvLocalAddress + '}'); + console.debug('cc: message{' + message.command + '; ' + serverAddress + ' -> ' + serverLocalAddress + '}'); const bitrateSetting = appSettings.maxChromecastBitrate(); if (bitrateSetting) { From 70c1b5b6e4f46d37715c209f09d77fe1c2e90028 Mon Sep 17 00:00:00 2001 From: Sky-High Date: Sun, 10 Sep 2023 21:16:40 +0200 Subject: [PATCH 14/49] satisfy eslint --- src/plugins/chromecastPlayer/plugin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 77d0f27c98..732ae4e9da 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -337,6 +337,7 @@ class CastPlayer { * Use the local address (ULA, Unique Local Address) in that case. */ const serverAddress = apiClient.serverAddress(); + // eslint-disable-next-line compat/compat const hostname = (new URL(serverAddress)).hostname; const isLocalhost = hostname === 'localhost' || hostname.startsWith('127.') || hostname === '[::1]'; const serverLocalAddress = isLocalhost ? apiClient.serverInfo().LocalAddress : serverAddress; From 37fd4feb5573acd06d97beca86748f897bd744a4 Mon Sep 17 00:00:00 2001 From: Yasin Silavi Date: Wed, 26 Jul 2023 10:54:23 +0330 Subject: [PATCH 15/49] Remove useless assignment --- src/scripts/datetime.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/scripts/datetime.js b/src/scripts/datetime.js index 3903b58638..f1243a5076 100644 --- a/src/scripts/datetime.js +++ b/src/scripts/datetime.js @@ -230,7 +230,6 @@ export function getDisplayTime(date) { const timeLower = time.toLowerCase(); if (timeLower.indexOf('am') !== -1 || timeLower.indexOf('pm') !== -1) { - time = timeLower; let hour = date.getHours() % 12; const suffix = date.getHours() > 11 ? 'pm' : 'am'; if (!hour) { From 4dd8d7e73f2745fd083a119b967caa45890b5d7b Mon Sep 17 00:00:00 2001 From: Yasin Silavi Date: Wed, 26 Jul 2023 10:57:23 +0330 Subject: [PATCH 16/49] Refactor isEnabled function to always return boolean --- src/scripts/autocast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/autocast.js b/src/scripts/autocast.js index 9ed7de5df7..0f63854336 100644 --- a/src/scripts/autocast.js +++ b/src/scripts/autocast.js @@ -26,7 +26,7 @@ export function isEnabled() { const playerId = localStorage.getItem('autocastPlayerId'); const currentPlayerInfo = playbackManager.getPlayerInfo(); - return (currentPlayerInfo && playerId && currentPlayerInfo.id === playerId); + return currentPlayerInfo?.id === playerId } function onOpen() { From a8e6eaff48e20fab2eeaab6b061eba49ca610802 Mon Sep 17 00:00:00 2001 From: Yasin Silavi Date: Wed, 26 Jul 2023 10:58:37 +0330 Subject: [PATCH 17/49] Remove comment out code --- src/plugins/bookPlayer/template.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/plugins/bookPlayer/template.html b/src/plugins/bookPlayer/template.html index 55fd02c8a1..255b8283a2 100644 --- a/src/plugins/bookPlayer/template.html +++ b/src/plugins/bookPlayer/template.html @@ -2,12 +2,6 @@ - From e4db027bbbe9199fe768d6c11dd47a585a6b9e39 Mon Sep 17 00:00:00 2001 From: Yasin Silavi <59373143+sttatusx@users.noreply.github.com> Date: Wed, 26 Jul 2023 17:41:08 +0330 Subject: [PATCH 18/49] Update src/scripts/autocast.js Co-authored-by: Bill Thornton --- src/scripts/autocast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/autocast.js b/src/scripts/autocast.js index 0f63854336..95b694a3bc 100644 --- a/src/scripts/autocast.js +++ b/src/scripts/autocast.js @@ -26,7 +26,7 @@ export function isEnabled() { const playerId = localStorage.getItem('autocastPlayerId'); const currentPlayerInfo = playbackManager.getPlayerInfo(); - return currentPlayerInfo?.id === playerId + return currentPlayerInfo?.id === playerId; } function onOpen() { From ea47793820baf17a99583e1425947007ae624080 Mon Sep 17 00:00:00 2001 From: Yasin Silavi <59373143+sttatusx@users.noreply.github.com> Date: Sat, 9 Sep 2023 00:52:34 +0330 Subject: [PATCH 19/49] fix: check both player id and current player id Co-authored-by: Bill Thornton --- src/scripts/autocast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/autocast.js b/src/scripts/autocast.js index 95b694a3bc..2633587ba5 100644 --- a/src/scripts/autocast.js +++ b/src/scripts/autocast.js @@ -26,7 +26,7 @@ export function isEnabled() { const playerId = localStorage.getItem('autocastPlayerId'); const currentPlayerInfo = playbackManager.getPlayerInfo(); - return currentPlayerInfo?.id === playerId; + return playerId && currentPlayerInfo?.id === playerId; } function onOpen() { From 7244e19f76943185be8cf9ddb076d78ae616ead0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Mon, 11 Sep 2023 15:00:45 +0000 Subject: [PATCH 20/49] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index 359bf7d812..4cd89478cb 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1763,5 +1763,6 @@ "LabelSegmentKeepSeconds": "Doba ponechání částí", "LabelSegmentKeepSecondsHelp": "Čas v sekundách, po který budou části překódovaného souboru uloženy. Musí být delší než čas určený v \"Omezit po\". Funguje pouze při zapnuté funkci Odstranění částí.", "LabelBackdropScreensaverInterval": "Interval šetřiče \"Pozadí\"", - "LabelBackdropScreensaverIntervalHelp": "Čas v sekundách mezi změnou pozadí při použití šetřiče \"Pozadí\"." + "LabelBackdropScreensaverIntervalHelp": "Čas v sekundách mezi změnou pozadí při použití šetřiče \"Pozadí\".", + "AllowAv1Encoding": "Povolit kódování do formátu AV1" } From d26b361e53f29492f1633afbc3da5a5cbba221ba Mon Sep 17 00:00:00 2001 From: stanol Date: Mon, 11 Sep 2023 17:26:14 +0000 Subject: [PATCH 21/49] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index d74f61e673..c0bc6c9087 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1760,5 +1760,6 @@ "LabelSegmentKeepSecondsHelp": "Час у секундах, протягом якого сегменти мають зберігатися перед перезаписом. Має бути більшим за \"Обмежити після\". Працює тільки якщо увімкнено видалення сегментів.", "LabelSegmentKeepSeconds": "Час збереження сегментів", "LabelBackdropScreensaverIntervalHelp": "Час у секундах між різними фонами при використанні фонової заставки.", - "LabelBackdropScreensaverInterval": "Інтервал між фоновими заставками" + "LabelBackdropScreensaverInterval": "Інтервал між фоновими заставками", + "AllowAv1Encoding": "Дозволити кодування у форматі AV1" } From 793b8a98367f4228273a57e05932cfeaf6325062 Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Tue, 12 Sep 2023 03:34:34 +0000 Subject: [PATCH 22/49] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index 3d2b0a7e18..4c69d67800 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1232,7 +1232,7 @@ "EnableTonemapping": "Käytä sävykartoitusta", "EnableBlurHashHelp": "Kuvat, joita ladataan vielä, näytetään yksilöllisellä paikkamerkillä.", "EnableBlurHash": "Ota sumennetut paikkamerkit käyttöön kuville", - "AllowTonemappingHelp": "Sävykartoitus voi muuttaa videon dynaamisen alueen HDR:stä SDR:ksi säilyttäen samalla kuvan yksityiskohdat ja värit, jotka ovat kohtauksen alkuperäisen ilmeen kannalta erittäin tärkeitä. Toimii tällä hetkellä vain 10bit HDR10, HLG ja Dovi -videoiden kanssa ja edellyttää soveltuvaa OpenCL- tai CUDA-suoritusalustaa.", + "AllowTonemappingHelp": "Sävykartoitus voi muuttaa videon dynaamisen alueen HDR:stä SDR:ksi säilyttäen samalla kuvan yksityiskohdat ja värit, jotka ovat kohtauksen alkuperäisen ilmeen kannalta erittäin tärkeitä. Toimii tällä hetkellä vain 10-bit HDR10-, HLG- ja DoVi-videoiden kanssa ja edellyttää soveltuvaa OpenCL- tai CUDA-suoritusalustaa.", "LabelffmpegPathHelp": "FFmpeg-sovellustiedoston tai -kansion tiedostosijainti.", "LabelKodiMetadataEnablePathSubstitutionHelp": "Mahdollistaa kuvien tiedostosijaintien korvauksen palvelimen korvausasetuksien perusteella.", "ThumbCard": "Pienoiskortti", @@ -1761,5 +1761,6 @@ "LabelSegmentKeepSeconds": "Osioiden säilytysaika", "LabelSegmentKeepSecondsHelp": "Aika sekunteina, jonka osiot säilytetään ennen päällekirjoitusta. Oltava \"Rahoita kun on kulunut\" -aikaa suurempi. Toimii vain osioiden poiston ollessa käytössä.", "LabelBackdropScreensaverInterval": "Taustanäytönsäästäjän ajoitus", - "LabelBackdropScreensaverIntervalHelp": "Aika sekuntteina, jonka kuluttua kuva vaihtuu taustanäytönsäästäjää käytettäessä." + "LabelBackdropScreensaverIntervalHelp": "Aika sekuntteina, jonka kuluttua kuva vaihtuu taustanäytönsäästäjää käytettäessä.", + "AllowAv1Encoding": "Salli enkoodaus AV1-muodossa" } From 46f9a0fc8a3cffbfe70f59cc75eb91d84191e788 Mon Sep 17 00:00:00 2001 From: Bas Date: Tue, 12 Sep 2023 11:57:19 +0000 Subject: [PATCH 23/49] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index a69adfcf0b..ba9031fc88 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -169,7 +169,7 @@ "EnableThemeSongsHelp": "Speel titelmuziek af tijdens het bladeren door de bibliotheek.", "EnableThemeVideosHelp": "Speel titelfilms af op de achtergrond tijdens het bladeren door de bibliotheek.", "Ended": "Gestopt", - "EndsAtValue": "Eindigt om {0}", + "EndsAtValue": "Afgelopen om {0}", "Episodes": "Afleveringen", "ErrorAddingListingsToSchedulesDirect": "Er ging iets mis bij het toevoegen van de lineup aan uw Schedules Direct account. Schedules Direct staat maar een beperkt aantal lineups per account toe. Het kan nodig zijn dat u zich aan moet melden op de Schedules Direct-website en andere lineups moet verwijderen voordat u verder kunt.", "ErrorAddingMediaPathToVirtualFolder": "Er ging iets mis bij het toevoegen van het mediapad. Controleer of het pad klopt en of Jellyfin toegang heeft tot de locatie.", @@ -1762,5 +1762,6 @@ "LabelThrottleDelaySecondsHelp": "Tijd in seconden waarna de transcoder wordt afgeknepen. Deze tijd moet voldoende lang zijn zodat de cliënt een gezonde buffer in stand kan houden. Werkt alleen als afknijpen is ingeschakeld.", "LabelSegmentKeepSeconds": "Bewaartijd segmenten", "LabelBackdropScreensaverIntervalHelp": "Het aantal seconden dat een achtergrondafbeelding wordt getoond als onderdeel van de schermbeveiliging.", - "LabelBackdropScreensaverInterval": "Interval schermbeveiliging" + "LabelBackdropScreensaverInterval": "Interval schermbeveiliging", + "AllowAv1Encoding": "Coderen in AV1-formaat toestaan" } From 5cb181f68cec6a67846bdadff573160c281c7d05 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 12 Sep 2023 12:54:50 -0400 Subject: [PATCH 24/49] Add webpack config for analyzing builds and improve ts speed --- package-lock.json | 899 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 + webpack.analyze.js | 28 ++ webpack.common.js | 20 +- 4 files changed, 937 insertions(+), 14 deletions(-) create mode 100644 webpack.analyze.js diff --git a/package-lock.json b/package-lock.json index 02e521e4fd..2ecbd9d155 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,7 @@ "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-sonarjs": "0.19.0", "expose-loader": "4.1.0", + "fork-ts-checker-webpack-plugin": "8.0.0", "html-loader": "4.2.0", "html-webpack-plugin": "5.5.3", "mini-css-extract-plugin": "2.7.6", @@ -106,6 +107,7 @@ "sass": "1.62.1", "sass-loader": "13.3.2", "source-map-loader": "4.0.1", + "speed-measure-webpack-plugin": "1.5.0", "style-loader": "3.3.3", "stylelint": "15.6.2", "stylelint-config-rational-order": "0.1.2", @@ -115,6 +117,7 @@ "ts-loader": "9.4.4", "typescript": "5.0.4", "webpack": "5.88.1", + "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", "webpack-dev-server": "4.15.1", "webpack-merge": "5.9.0", @@ -2564,9 +2567,9 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", - "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -3397,6 +3400,12 @@ "node": ">= 8" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.23", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", + "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==", + "dev": true + }, "node_modules/@popperjs/core": { "version": "2.11.7", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", @@ -4497,6 +4506,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -6980,6 +6998,12 @@ "node": ">=8" } }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8852,6 +8876,241 @@ "node": ">=0.10.0" } }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", + "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -9259,6 +9518,21 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -11024,6 +11298,24 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.invokemap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", + "integrity": "sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==", + "dev": true + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -11036,6 +11328,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.pullall": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", + "integrity": "sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==", + "dev": true + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -11054,6 +11352,12 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, "node_modules/logform": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", @@ -11578,6 +11882,15 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -11677,6 +11990,12 @@ "tslib": "^2.0.3" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -12102,6 +12421,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -15322,6 +15650,20 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "dev": true }, + "node_modules/sirv": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -15724,6 +16066,91 @@ "specificity": "bin/specificity" } }, + "node_modules/speed-measure-webpack-plugin": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.5.0.tgz", + "integrity": "sha512-Re0wX5CtM6gW7bZA64ONOfEPEhwbiSF/vz6e2GvadjuaPrQcHTQdRGsD8+BE7iUOysXH8tIenkPCQBEcspXsNg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "webpack": "^1 || ^2 || ^3 || ^4 || ^5" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/speed-measure-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -18769,9 +19196,9 @@ } }, "node_modules/tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, "engines": { "node": ">=6" @@ -19044,6 +19471,15 @@ "node": ">=0.6" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -19763,6 +20199,79 @@ } } }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz", + "integrity": "sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "is-plain-object": "^5.0.0", + "lodash.debounce": "^4.0.8", + "lodash.escape": "^4.0.1", + "lodash.flatten": "^4.4.0", + "lodash.invokemap": "^4.6.0", + "lodash.pullall": "^4.2.0", + "lodash.uniqby": "^4.7.0", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/webpack-cli": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", @@ -22165,9 +22674,9 @@ } }, "@discoveryjs/json-ext": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", - "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, "@emotion/babel-plugin": { @@ -22731,6 +23240,12 @@ "fastq": "^1.6.0" } }, + "@polka/url": { + "version": "1.0.0-next.23", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", + "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==", + "dev": true + }, "@popperjs/core": { "version": "2.11.7", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", @@ -23600,6 +24115,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -25439,6 +25960,12 @@ "is-obj": "^2.0.0" } }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -26879,6 +27406,174 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "fork-ts-checker-webpack-plugin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", + "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -27188,6 +27883,15 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + } + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -28491,6 +29195,24 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "lodash.invokemap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", + "integrity": "sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -28503,6 +29225,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.pullall": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", + "integrity": "sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -28521,6 +29249,12 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, "logform": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", @@ -28905,6 +29639,12 @@ "minimist": "^1.2.5" } }, + "mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -28986,6 +29726,12 @@ "tslib": "^2.0.3" } }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, "node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -29310,6 +30056,12 @@ "is-wsl": "^2.2.0" } }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -31565,6 +32317,17 @@ } } }, + "sirv": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", + "dev": true, + "requires": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^3.0.0" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -31901,6 +32664,66 @@ "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", "dev": true }, + "speed-measure-webpack-plugin": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.5.0.tgz", + "integrity": "sha512-Re0wX5CtM6gW7bZA64ONOfEPEhwbiSF/vz6e2GvadjuaPrQcHTQdRGsD8+BE7iUOysXH8tIenkPCQBEcspXsNg==", + "dev": true, + "requires": { + "chalk": "^4.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -34271,9 +35094,9 @@ } }, "tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, "tar": { @@ -34471,6 +35294,12 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true + }, "tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -35060,6 +35889,52 @@ } } }, + "webpack-bundle-analyzer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz", + "integrity": "sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "is-plain-object": "^5.0.0", + "lodash.debounce": "^4.0.8", + "lodash.escape": "^4.0.1", + "lodash.flatten": "^4.4.0", + "lodash.invokemap": "^4.6.0", + "lodash.pullall": "^4.2.0", + "lodash.uniqby": "^4.7.0", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "requires": {} + } + } + }, "webpack-cli": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", diff --git a/package.json b/package.json index 5c9fd988a9..988c2a0215 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-sonarjs": "0.19.0", "expose-loader": "4.1.0", + "fork-ts-checker-webpack-plugin": "8.0.0", "html-loader": "4.2.0", "html-webpack-plugin": "5.5.3", "mini-css-extract-plugin": "2.7.6", @@ -48,6 +49,7 @@ "sass": "1.62.1", "sass-loader": "13.3.2", "source-map-loader": "4.0.1", + "speed-measure-webpack-plugin": "1.5.0", "style-loader": "3.3.3", "stylelint": "15.6.2", "stylelint-config-rational-order": "0.1.2", @@ -57,6 +59,7 @@ "ts-loader": "9.4.4", "typescript": "5.0.4", "webpack": "5.88.1", + "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", "webpack-dev-server": "4.15.1", "webpack-merge": "5.9.0", @@ -136,6 +139,7 @@ "scripts": { "start": "npm run serve", "serve": "webpack serve --config webpack.dev.js", + "build:analyze": "cross-env NODE_ENV=\"production\" webpack --config webpack.analyze.js", "build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js", "build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js", "build:check": "tsc --noEmit", diff --git a/webpack.analyze.js b/webpack.analyze.js new file mode 100644 index 0000000000..3bf6ab053d --- /dev/null +++ b/webpack.analyze.js @@ -0,0 +1,28 @@ +const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const { merge } = require('webpack-merge'); + +const prod = require('./webpack.prod'); + +const smp = new SpeedMeasurePlugin(); + +const config = merge(prod, { + plugins: [ + new BundleAnalyzerPlugin({ + excludeAssets: /-json\..*\.chunk\.js$/ + }) + ] +}); + +const searchPlugin = (name) => config.plugins.findIndex((e) => e.constructor.name === name); + +// NOTE: We need to re-add the mini css plugin to workaround this issue +// https://github.com/stephencookdev/speed-measure-webpack-plugin/issues/167 +const miniCssPluginIndex = searchPlugin('MiniCssExtractPlugin'); +const miniCssPlugin = config.plugins[miniCssPluginIndex]; + +const exportedConfig = smp.wrap(config); + +exportedConfig.plugins[miniCssPluginIndex] = miniCssPlugin; + +module.exports = exportedConfig; diff --git a/webpack.common.js b/webpack.common.js index 59d32fb952..8022569b88 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -1,6 +1,7 @@ const path = require('path'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { DefinePlugin } = require('webpack'); @@ -100,6 +101,11 @@ const config = { to: path.resolve(__dirname, './dist') }; }) + }), + new ForkTsCheckerWebpackPlugin({ + typescript: { + configFile: path.resolve(__dirname, 'tsconfig.json') + } }) ], output: { @@ -110,6 +116,8 @@ const config = { }, optimization: { runtimeChunk: 'single', + removeAvailableModules: false, + removeEmptyChunks: false, splitChunks: { chunks: 'all', maxInitialRequests: Infinity, @@ -216,14 +224,22 @@ const config = { exclude: /node_modules/, use: [ 'worker-loader', - 'ts-loader' + { + loader: 'ts-loader', + options: { + transpileOnly: true + } + } ] }, { test: /\.(ts|tsx)$/, exclude: /node_modules/, use: [{ - loader: 'ts-loader' + loader: 'ts-loader', + options: { + transpileOnly: true + } }] }, /* modules that Babel breaks when transforming to ESM */ From 24fad10f362319a004cf26e718a4d9d3546539df Mon Sep 17 00:00:00 2001 From: Yasin Silavi Date: Wed, 26 Jul 2023 10:27:40 +0330 Subject: [PATCH 25/49] Format alert module --- src/components/alert.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/alert.js b/src/components/alert.js index 6e654e9f3c..16b2c826bd 100644 --- a/src/components/alert.js +++ b/src/components/alert.js @@ -1,23 +1,25 @@ -import { appRouter } from './router/appRouter'; -import browser from '../scripts/browser'; -import dialog from './dialog/dialog'; -import globalize from '../scripts/globalize'; +import { appRouter } from "./router/appRouter"; +import browser from "../scripts/browser"; +import dialog from "./dialog/dialog"; +import globalize from "../scripts/globalize"; function useNativeAlert() { // webOS seems to block modals // Tizen 2.x seems to block modals - return !browser.web0s - && !(browser.tizenVersion && browser.tizenVersion < 3) - && browser.tv - && window.alert; + return ( + !browser.web0s && + !(browser.tizenVersion && browser.tizenVersion < 3) && + browser.tv && + window.alert + ); } export default async function (text, title) { let options; - if (typeof text === 'string') { + if (typeof text === "string") { options = { title: title, - text: text + text: text, }; } else { options = text; @@ -26,15 +28,15 @@ export default async function (text, title) { await appRouter.ready(); if (useNativeAlert()) { - alert((options.text || '').replaceAll('
', '\n')); + alert((options.text || "").replaceAll("
", "\n")); return Promise.resolve(); } else { const items = []; items.push({ - name: globalize.translate('ButtonGotIt'), - id: 'ok', - type: 'submit' + name: globalize.translate("ButtonGotIt"), + id: "ok", + type: "submit", }); options.buttons = items; From 0a878bfbcddb6e01a9f447d640b861873e18589f Mon Sep 17 00:00:00 2001 From: Yasin Silavi Date: Wed, 26 Jul 2023 10:28:14 +0330 Subject: [PATCH 26/49] Refactor alert module --- src/components/alert.js | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/components/alert.js b/src/components/alert.js index 16b2c826bd..46d9ea4d6b 100644 --- a/src/components/alert.js +++ b/src/components/alert.js @@ -3,43 +3,34 @@ import browser from "../scripts/browser"; import dialog from "./dialog/dialog"; import globalize from "../scripts/globalize"; -function useNativeAlert() { - // webOS seems to block modals - // Tizen 2.x seems to block modals - return ( +export default async function (text, title) { + // Modals seem to be blocked on Web OS and Tizen 2.x + const canUseNativeAlert = !!( !browser.web0s && !(browser.tizenVersion && browser.tizenVersion < 3) && browser.tv && window.alert ); -} -export default async function (text, title) { - let options; - if (typeof text === "string") { - options = { - title: title, - text: text, - }; - } else { - options = text; - } + let options = typeof text === "string" ? { title, text } : text; await appRouter.ready(); - if (useNativeAlert()) { + if (canUseNativeAlert) { alert((options.text || "").replaceAll("
", "\n")); - return Promise.resolve(); - } else { - const items = []; - items.push({ + return Promise.resolve(); + } + + const items = [ + { name: globalize.translate("ButtonGotIt"), id: "ok", type: "submit", - }); + }, + ]; - options.buttons = items; - return dialog.show(options); - } + options.buttons = items; + + return dialog.show(options); } From 6664b6ec62fac3ab89374c3ae2431b714f795cf1 Mon Sep 17 00:00:00 2001 From: Yasin Silavi Date: Sun, 10 Sep 2023 23:20:20 +0330 Subject: [PATCH 27/49] style: use singlequote for strings --- src/components/alert.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/alert.js b/src/components/alert.js index 46d9ea4d6b..de3c7c4447 100644 --- a/src/components/alert.js +++ b/src/components/alert.js @@ -1,33 +1,33 @@ -import { appRouter } from "./router/appRouter"; -import browser from "../scripts/browser"; -import dialog from "./dialog/dialog"; -import globalize from "../scripts/globalize"; +import { appRouter } from './router/appRouter'; +import browser from '../scripts/browser'; +import dialog from './dialog/dialog'; +import globalize from '../scripts/globalize'; export default async function (text, title) { // Modals seem to be blocked on Web OS and Tizen 2.x const canUseNativeAlert = !!( - !browser.web0s && - !(browser.tizenVersion && browser.tizenVersion < 3) && - browser.tv && - window.alert + !browser.web0s + && !(browser.tizenVersion && browser.tizenVersion < 3) + && browser.tv + && window.alert ); - let options = typeof text === "string" ? { title, text } : text; + const options = typeof text === 'string' ? { title, text } : text; await appRouter.ready(); if (canUseNativeAlert) { - alert((options.text || "").replaceAll("
", "\n")); + alert((options.text || '').replaceAll('
', '\n')); return Promise.resolve(); } const items = [ { - name: globalize.translate("ButtonGotIt"), - id: "ok", - type: "submit", - }, + name: globalize.translate('ButtonGotIt'), + id: 'ok', + type: 'submit' + } ]; options.buttons = items; From 082d9e55c62578dea3b06e2db50d89ef64fbf483 Mon Sep 17 00:00:00 2001 From: Yasin Silavi Date: Tue, 12 Sep 2023 22:04:18 +0330 Subject: [PATCH 28/49] refactor: remove useless assignment --- src/components/alert.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/alert.js b/src/components/alert.js index de3c7c4447..ebeed3d8fd 100644 --- a/src/components/alert.js +++ b/src/components/alert.js @@ -22,7 +22,7 @@ export default async function (text, title) { return Promise.resolve(); } - const items = [ + options.buttons = [ { name: globalize.translate('ButtonGotIt'), id: 'ok', @@ -30,7 +30,5 @@ export default async function (text, title) { } ]; - options.buttons = items; - return dialog.show(options); } From 29298cb823463a8116d66d3949feddbf5c1fb089 Mon Sep 17 00:00:00 2001 From: MBR#0001 Date: Tue, 12 Sep 2023 22:27:16 +0200 Subject: [PATCH 29/49] Move styles to class --- src/components/subtitleeditor/subtitleeditor.js | 2 +- src/components/subtitleeditor/subtitleeditor.scss | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 56d138de96..999fb217be 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -223,7 +223,7 @@ function renderSearchResults(context, results) { if (hasAnyFlags) { html += '
'; - const spanOpen = ''; + const spanOpen = ''; if (result.IsHashMatch) { html += spanOpen + globalize.translate('PerfectMatch') + ''; diff --git a/src/components/subtitleeditor/subtitleeditor.scss b/src/components/subtitleeditor/subtitleeditor.scss index 08e6faffba..d0d054cd09 100644 --- a/src/components/subtitleeditor/subtitleeditor.scss +++ b/src/components/subtitleeditor/subtitleeditor.scss @@ -1,3 +1,11 @@ .originalSubtitleFileLabel { margin-right: 1em; } + +.subtitleFeaturePillow { + background: #3388cc; + color: #fff; + padding: .3em 1em; + border-radius: 1000em; + margin-right: 0.25em; +} From 1f9cb0070fe3cc0df7aec8db9ed65c8c7035179b Mon Sep 17 00:00:00 2001 From: MBR-0001 <55142207+MBR-0001@users.noreply.github.com> Date: Tue, 12 Sep 2023 23:28:07 +0200 Subject: [PATCH 30/49] Update subtitleeditor.scss --- src/components/subtitleeditor/subtitleeditor.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/subtitleeditor/subtitleeditor.scss b/src/components/subtitleeditor/subtitleeditor.scss index d0d054cd09..16c2293cf1 100644 --- a/src/components/subtitleeditor/subtitleeditor.scss +++ b/src/components/subtitleeditor/subtitleeditor.scss @@ -3,9 +3,9 @@ } .subtitleFeaturePillow { - background: #3388cc; + background: #38c; color: #fff; - padding: .3em 1em; + padding: 0.3em 1em; border-radius: 1000em; margin-right: 0.25em; } From 31f035e3590ec6ef4a23d838c4baf14c6406db2b Mon Sep 17 00:00:00 2001 From: Sky-High Date: Tue, 12 Sep 2023 23:28:25 +0200 Subject: [PATCH 31/49] update module name in log message --- src/plugins/chromecastPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 732ae4e9da..e5663c2f08 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -352,7 +352,7 @@ class CastPlayer { receiverName: receiverName }); - console.debug('cc: message{' + message.command + '; ' + serverAddress + ' -> ' + serverLocalAddress + '}'); + console.debug('[chromecastPlayer] message{' + message.command + '; ' + serverAddress + ' -> ' + serverLocalAddress + '}'); const bitrateSetting = appSettings.maxChromecastBitrate(); if (bitrateSetting) { From 101abc762bded51e37a1a88bd4db49dec5077f3c Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 12 Sep 2023 17:28:53 -0400 Subject: [PATCH 32/49] Add eslint rule for consistent curly brackets --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 44fd0a3046..6fdd10c488 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,7 @@ module.exports = { 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }], 'comma-dangle': ['error', 'never'], 'comma-spacing': ['error'], + 'curly': ['error', 'multi-line', 'consistent'], 'default-case-last': ['error'], 'eol-last': ['error'], 'indent': ['error', 4, { 'SwitchCase': 1 }], From d6bcc7466bb7d92d4b8a87cd846c36d1232d0aed Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 12 Sep 2023 17:29:03 -0400 Subject: [PATCH 33/49] Fix curly bracket issues --- src/components/playback/playbackmanager.js | 24 +++++++------------ src/components/slideshow/slideshow.js | 6 +++-- src/controllers/dashboard/devices/devices.js | 5 ++-- src/controllers/dashboard/library.js | 4 +--- .../dashboard/plugins/installed/index.js | 5 ++-- .../scheduledtasks/scheduledtasks.js | 4 +--- src/controllers/playback/video/index.js | 3 +-- src/elements/emby-slider/emby-slider.js | 8 ++++--- src/libraries/navdrawer/navdrawer.js | 5 ++-- src/libraries/scroller.js | 3 ++- src/plugins/comicsPlayer/plugin.js | 10 ++++---- src/scripts/globalize.js | 3 ++- 12 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 308acd79f5..08c8361092 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -2345,30 +2345,23 @@ class PlaybackManager { let prevRelIndex = 0; for (const stream of prevSource.MediaStreams) { - if (stream.Type != streamType) - continue; + if (stream.Type != streamType) continue; - if (stream.Index == prevIndex) - break; + if (stream.Index == prevIndex) break; prevRelIndex += 1; } let newRelIndex = 0; for (const stream of mediaSource.MediaStreams) { - if (stream.Type != streamType) - continue; + if (stream.Type != streamType) continue; let score = 0; - if (prevStream.Codec == stream.Codec) - score += 1; - if (prevRelIndex == newRelIndex) - score += 1; - if (prevStream.DisplayTitle && prevStream.DisplayTitle == stream.DisplayTitle) - score += 2; - if (prevStream.Language && prevStream.Language != 'und' && prevStream.Language == stream.Language) - score += 2; + if (prevStream.Codec == stream.Codec) score += 1; + if (prevRelIndex == newRelIndex) score += 1; + if (prevStream.DisplayTitle && prevStream.DisplayTitle == stream.DisplayTitle) score += 2; + if (prevStream.Language && prevStream.Language != 'und' && prevStream.Language == stream.Language) score += 2; console.debug(`AutoSet ${streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`); if (score > bestStreamScore && score >= 3) { @@ -2388,8 +2381,9 @@ class PlaybackManager { mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex; } } - if (streamType == 'Audio') + if (streamType == 'Audio') { mediaSource.DefaultAudioStreamIndex = bestStreamIndex; + } } else { console.debug(`AutoSet ${streamType} - Threshold not met. Using default.`); } diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 0913ed25ff..c57886e0b0 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -514,10 +514,12 @@ export default function (options) { function toggleFullscreenButtons(isFullscreen) { const btnFullscreen = dialog.querySelector('.btnFullscreen'); const btnFullscreenExit = dialog.querySelector('.btnFullscreenExit'); - if (btnFullscreen) + if (btnFullscreen) { btnFullscreen.classList.toggle('hide', isFullscreen); - if (btnFullscreenExit) + } + if (btnFullscreenExit) { btnFullscreenExit.classList.toggle('hide', !isFullscreen); + } } /** diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index f28064e09a..1c5ede9531 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -110,10 +110,11 @@ function load(page, devices) { deviceHtml += '
'; if (canDelete(device.Id)) { - if (globalize.getIsRTL()) + if (globalize.getIsRTL()) { deviceHtml += '
'; - else + } else { deviceHtml += '
'; + } deviceHtml += ''; deviceHtml += '
'; } diff --git a/src/controllers/dashboard/library.js b/src/controllers/dashboard/library.js index 08203b76f6..51ca3af712 100644 --- a/src/controllers/dashboard/library.js +++ b/src/controllers/dashboard/library.js @@ -309,9 +309,7 @@ function getVirtualFolderHtml(page, virtualFolder, index) { html += '
'; // always show menu unless explicitly hidden if (virtualFolder.showMenu !== false) { - let dirTextAlign = 'right'; - if (globalize.getIsRTL()) - dirTextAlign = 'left'; + const dirTextAlign = globalize.getIsRTL() ? 'left' : 'right'; html += '
'; html += ''; html += '
'; diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index 13d0132c94..f6442710fc 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -83,10 +83,11 @@ function getPluginCardHtml(plugin, pluginConfigurationPages) { html += '
'; if (configPage || plugin.CanUninstall) { - if (globalize.getIsRTL()) + if (globalize.getIsRTL()) { html += '
'; - else + } else { html += '
'; + } html += ''; html += '
'; } diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index 8ae447aabc..f17bc5c935 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -57,9 +57,7 @@ function populateList(page, tasks) { html += ''; html += ''; html += '
'; - let textAlignStyle = 'left'; - if (globalize.getIsRTL()) - textAlignStyle = 'right'; + const textAlignStyle = globalize.getIsRTL() ? 'right' : 'left'; html += ""; html += "

" + task.Name + '

'; html += "
" + getTaskProgressHtml(task) + '
'; diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 0feb6e3752..80adc2db40 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -306,8 +306,7 @@ export default function (view) { function onHideAnimationComplete(e) { const elem = e.target; - if (elem != osdBottomElement) - return; + if (elem != osdBottomElement) return; elem.classList.add('hide'); dom.removeEventListener(elem, transitionEndEventName, onHideAnimationComplete, { once: true diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index ec93e61fd6..afd9490920 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -30,8 +30,9 @@ function mapClientToFraction(range, clientX) { const rect = range.sliderBubbleTrack.getBoundingClientRect(); let fraction = (clientX - rect.left) / rect.width; - if (globalize.getIsElementRTL(range)) + if (globalize.getIsElementRTL(range)) { fraction = (rect.right - clientX) / rect.width; + } // Snap to step const valueRange = range.max - range.min; @@ -490,10 +491,11 @@ EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { function setRange(elem, startPercent, endPercent) { const style = elem.style; - if (globalize.getIsRTL()) + if (globalize.getIsRTL()) { style.right = Math.max(startPercent, 0) + '%'; - else + } else { style.left = Math.max(startPercent, 0) + '%'; + } const widthPercent = endPercent - startPercent; style.width = Math.max(Math.min(widthPercent, 100), 0) + '%'; diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index ef6dd1e04f..a41085a5f3 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -206,10 +206,11 @@ class NavDrawer { options.target.classList.add('touch-menu-la'); options.target.style.width = options.width + 'px'; - if (globalize.getIsRTL()) + if (globalize.getIsRTL()) { options.target.style.right = -options.width + 'px'; - else + } else { options.target.style.left = -options.width + 'px'; + } if (!options.disableMask) { this.mask = document.createElement('div'); diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 12edc468cc..a6f72c7090 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -189,8 +189,9 @@ const scrollerFactory = function (frame, options) { // Set position limits & relatives self._pos.end = Math.max(slideeSize - frameSize, 0); - if (globalize.getIsRTL()) + if (globalize.getIsRTL()) { self._pos.end *= -1; + } } } diff --git a/src/plugins/comicsPlayer/plugin.js b/src/plugins/comicsPlayer/plugin.js index 58461ddc51..dd0f77da60 100644 --- a/src/plugins/comicsPlayer/plugin.js +++ b/src/plugins/comicsPlayer/plugin.js @@ -95,10 +95,11 @@ export class ComicsPlayer { onDirChanged = () => { let langDir = this.comicsPlayerSettings.langDir; - if (!langDir || langDir === 'ltr') + if (!langDir || langDir === 'ltr') { langDir = 'rtl'; - else + } else { langDir = 'ltr'; + } this.changeLanguageDirection(langDir); @@ -125,10 +126,11 @@ export class ComicsPlayer { onViewChanged = () => { let view = this.comicsPlayerSettings.pagesPerView; - if (!view || view === 1) + if (!view || view === 1) { view = 2; - else + } else { view = 1; + } this.changeView(view); diff --git a/src/scripts/globalize.js b/src/scripts/globalize.js index 21260e2be4..1743890e1b 100644 --- a/src/scripts/globalize.js +++ b/src/scripts/globalize.js @@ -64,8 +64,9 @@ function checkAndProcessDir(culture) { function setDocumentDirection(direction) { document.getElementsByTagName('body')[0].setAttribute('dir', direction); document.getElementsByTagName('html')[0].setAttribute('dir', direction); - if (direction === Direction.rtl) + if (direction === Direction.rtl) { import('../styles/rtl.scss'); + } } export function getIsElementRTL(element) { From 5d6756778b3236dcdfa7f8d2b0a1b21f4fb545d4 Mon Sep 17 00:00:00 2001 From: TheMelmacian <76712303+TheMelmacian@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:07:27 +0200 Subject: [PATCH 34/49] apply suggested changes from code review --- src/components/playback/playbackmanager.js | 4 ++-- src/controllers/itemDetails/index.js | 4 ++-- .../{BackdropImage.ts => backdropImage.ts} | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) rename src/utils/jellyfin-apiclient/{BackdropImage.ts => backdropImage.ts} (82%) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index a8192885c6..15de6a7e26 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -14,7 +14,7 @@ import alert from '../alert'; import { PluginType } from '../../types/plugin.ts'; import { includesAny } from '../../utils/container.ts'; import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; -import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/BackdropImage'; +import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage'; const UNLIMITED_ITEMS = -1; @@ -2672,7 +2672,7 @@ class PlaybackManager { title: item.Name }; - const backdropUrl = getItemBackdropImageUrl(apiClient, item, true); + const backdropUrl = getItemBackdropImageUrl(apiClient, item, {}, true); if (backdropUrl) { resultInfo.backdropUrl = backdropUrl; } diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 1de640f6ec..102940f65c 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -36,7 +36,7 @@ import Dashboard from '../../utils/dashboard'; import ServerConnections from '../../components/ServerConnections'; import confirm from '../../components/confirm/confirm'; import { download } from '../../scripts/fileDownloader'; -import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/BackdropImage'; +import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage'; function autoFocus(container) { import('../../components/autoFocuser').then(({ default: autoFocuser }) => { @@ -505,7 +505,7 @@ function renderDetailPageBackdrop(page, item, apiClient) { let hasbackdrop = false; const itemBackdropElement = page.querySelector('#itemBackdrop'); - const imgUrl = getItemBackdropImageUrl(apiClient, item, false, { maxWitdh: dom.getScreenWidth() }); + const imgUrl = getItemBackdropImageUrl(apiClient, item, { maxWitdh: dom.getScreenWidth() }, false); if (imgUrl) { imageLoader.lazyImage(itemBackdropElement, imgUrl); diff --git a/src/utils/jellyfin-apiclient/BackdropImage.ts b/src/utils/jellyfin-apiclient/backdropImage.ts similarity index 82% rename from src/utils/jellyfin-apiclient/BackdropImage.ts rename to src/utils/jellyfin-apiclient/backdropImage.ts index 82387b8a72..2f9b3c5da8 100644 --- a/src/utils/jellyfin-apiclient/BackdropImage.ts +++ b/src/utils/jellyfin-apiclient/backdropImage.ts @@ -1,6 +1,7 @@ -import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'; +import type { ApiClient } from 'jellyfin-apiclient'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; import { randomInt } from '../number'; -import { ApiClient } from 'jellyfin-apiclient'; export interface ScaleImageOptions { maxWidth?: number; @@ -19,15 +20,15 @@ export interface ScaleImageOptions { * Returns undefined if no usable image was found. * @param apiClient The ApiClient to generate the url. * @param item The item for which the backdrop image is requested. - * @param random If set to true and the item has more than one backdrop, a random image is returned. * @param options Optional; allows to scale the backdrop image. + * @param random If set to true and the item has more than one backdrop, a random image is returned. * @returns The url of the first or a random backdrop image of the provided item. */ -export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, random = false, options: ScaleImageOptions = {}): string | undefined => { +export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, options: ScaleImageOptions = {}, random = false): string | undefined => { if (item.Id && item.BackdropImageTags?.length) { const backdropImgIndex = random ? randomInt(0, item.BackdropImageTags.length - 1) : 0; return apiClient.getScaledImageUrl(item.Id, { - type: 'Backdrop', + type: ImageType.Backdrop, index: backdropImgIndex, tag: item.BackdropImageTags[backdropImgIndex], ...options @@ -35,18 +36,17 @@ export const getItemBackdropImageUrl = (apiClient: ApiClient, item: BaseItemDto, } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags?.length) { const backdropImgIndex = random ? randomInt(0, item.ParentBackdropImageTags.length - 1) : 0; return apiClient.getScaledImageUrl(item.ParentBackdropItemId, { - type: 'Backdrop', + type: ImageType.Backdrop, index: backdropImgIndex, tag: item.ParentBackdropImageTags[backdropImgIndex], ...options }); } else if (item.Id && item.ImageTags?.Primary) { return apiClient.getScaledImageUrl(item.Id, { - type: 'Primary', + type: ImageType.Primary, tag: item.ImageTags.Primary, ...options }); - } else { - return undefined; } + return undefined; }; From 1f1aa892dc06d837bbc853479796200ccb5ed1d5 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 13 Sep 2023 06:13:05 +0300 Subject: [PATCH 35/49] Add view layout settings components --- .../components/library/GridListViewButton.tsx | 62 ++++++ .../components/library/ViewSettingsButton.tsx | 198 ++++++++++++++++++ src/types/library.ts | 9 +- 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 src/apps/experimental/components/library/GridListViewButton.tsx create mode 100644 src/apps/experimental/components/library/ViewSettingsButton.tsx diff --git a/src/apps/experimental/components/library/GridListViewButton.tsx b/src/apps/experimental/components/library/GridListViewButton.tsx new file mode 100644 index 0000000000..26588b1883 --- /dev/null +++ b/src/apps/experimental/components/library/GridListViewButton.tsx @@ -0,0 +1,62 @@ +import React, { FC, useCallback } from 'react'; +import { ButtonGroup, IconButton } from '@mui/material'; +import ViewModuleIcon from '@mui/icons-material/ViewModule'; +import ViewListIcon from '@mui/icons-material/ViewList'; +import globalize from 'scripts/globalize'; +import { LibraryViewSettings, ViewMode } from 'types/library'; +import { LibraryTab } from 'types/libraryTab'; +import ViewSettingsButton from './ViewSettingsButton'; + +interface GridListViewButtonProps { + viewType: LibraryTab; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const GridListViewButton: FC = ({ + viewType, + libraryViewSettings, + setLibraryViewSettings +}) => { + const handleToggleCurrentView = useCallback(() => { + setLibraryViewSettings((prevState) => ({ + ...prevState, + ViewMode: + prevState.ViewMode === ViewMode.ListView ? ViewMode.GridView : ViewMode.ListView + })); + }, [setLibraryViewSettings]); + + const isGridView = libraryViewSettings.ViewMode === ViewMode.GridView; + + return ( + + {isGridView ? ( + + ) : ( + + + + )} + + + + + + ); +}; + +export default GridListViewButton; diff --git a/src/apps/experimental/components/library/ViewSettingsButton.tsx b/src/apps/experimental/components/library/ViewSettingsButton.tsx new file mode 100644 index 0000000000..cec5090acc --- /dev/null +++ b/src/apps/experimental/components/library/ViewSettingsButton.tsx @@ -0,0 +1,198 @@ +import { ImageType } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; + +import IconButton from '@mui/material/IconButton'; +import MenuItem from '@mui/material/MenuItem'; +import Checkbox from '@mui/material/Checkbox'; +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 FormControlLabel from '@mui/material/FormControlLabel'; +import FormGroup from '@mui/material/FormGroup'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import Popover from '@mui/material/Popover'; +import ViewComfyIcon from '@mui/icons-material/ViewComfy'; + +import globalize from 'scripts/globalize'; +import { LibraryViewSettings, ViewMode } from 'types/library'; +import { LibraryTab } from 'types/libraryTab'; + +const imageTypesOptions = [ + { label: 'Primary', value: ImageType.Primary }, + { label: 'Banner', value: ImageType.Banner }, + { label: 'Disc', value: ImageType.Disc }, + { label: 'Logo', value: ImageType.Logo }, + { label: 'Thumb', value: ImageType.Thumb } +]; + +interface ViewSettingsButtonProps { + viewType: LibraryTab; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const ViewSettingsButton: FC = ({ + viewType, + libraryViewSettings, + setLibraryViewSettings +}) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const id = open ? 'selectview-popover' : undefined; + + const handleClick = useCallback((event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }, []); + + const handleClose = useCallback(() => { + setAnchorEl(null); + }, []); + + const handleChange = useCallback( + (event: React.ChangeEvent) => { + const name = event.target.name; + + setLibraryViewSettings((prevState) => ({ + ...prevState, + [name]: event.target.checked + })); + }, + [setLibraryViewSettings] + ); + + const onSelectChange = useCallback( + (event: SelectChangeEvent) => { + setLibraryViewSettings((prevState) => ({ + ...prevState, + ImageType: event.target.value as ImageType + })); + }, + [setLibraryViewSettings] + ); + + const getVisibleImageType = () => { + const visibleImageType: ImageType[] = [ImageType.Primary]; + + if ( + viewType !== LibraryTab.Episodes + && viewType !== LibraryTab.Artists + && viewType !== LibraryTab.AlbumArtists + && viewType !== LibraryTab.Albums + ) { + visibleImageType.push(ImageType.Banner); + visibleImageType.push(ImageType.Disc); + visibleImageType.push(ImageType.Logo); + visibleImageType.push(ImageType.Thumb); + } + + return visibleImageType; + }; + + const isViewSettingsEnabled = () => { + return libraryViewSettings.ViewMode !== ViewMode.ListView; + }; + + return ( + + + + + + + + + {globalize.translate('LabelImageType')} + + + + + {isViewSettingsEnabled() && ( + <> + + + + + } + label={globalize.translate('ShowTitle')} + /> + + } + label={globalize.translate('ShowYear')} + /> + + } + label={globalize.translate( + 'EnableCardLayout' + )} + /> + + + + )} + + + ); +}; + +export default ViewSettingsButton; diff --git a/src/types/library.ts b/src/types/library.ts index 313a5ebe3e..2994dd3ca5 100644 --- a/src/types/library.ts +++ b/src/types/library.ts @@ -3,6 +3,7 @@ import type { VideoType } from '@jellyfin/sdk/lib/generated-client/models/video- import type { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; import type { SeriesStatus } from '@jellyfin/sdk/lib/generated-client/models/series-status'; import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client'; export type ParentId = string | null | undefined; @@ -23,12 +24,18 @@ interface Filters { Years?: number[]; } +export enum ViewMode { + GridView = 'grid', + ListView = 'list', +} + export interface LibraryViewSettings { SortBy: ItemSortBy; SortOrder: SortOrder; StartIndex: number; CardLayout: boolean; - ImageType: string; + ImageType: ImageType; + ViewMode: ViewMode; ShowTitle: boolean; ShowYear?: boolean; Filters?: Filters; From 5f66abe01fa948288446d849856e8c9647b2555c Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 13 Sep 2023 06:32:53 +0300 Subject: [PATCH 36/49] add strings --- src/strings/en-us.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index bc6dc0da08..67e4d5d5ca 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -298,6 +298,7 @@ "Genres": "Genres", "GetThePlugin": "Get the Plugin", "GoogleCastUnsupported": "Google Cast Unsupported", + "GridView": "Grid View", "GroupBySeries": "Group by series", "GroupVersions": "Group versions", "GuestStar": "Guest star", @@ -999,6 +1000,7 @@ "LeaveBlankToNotSetAPassword": "You can leave this field blank to set no password.", "LibraryAccessHelp": "Select the libraries to share with this user. Administrators will be able to edit all folders using the metadata manager.", "List": "List", + "ListView": "List View", "ListPaging": "{0}-{1} of {2}", "Live": "Live", "LiveBroadcasts": "Live broadcasts", From aef99ce247d927ead97d07d2d9c389839e945d6d Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 12 Sep 2023 17:01:34 -0400 Subject: [PATCH 37/49] Add eslint rule to prevent "lonely" if statements --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 44fd0a3046..cbf9bfda79 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -48,6 +48,7 @@ module.exports = { 'no-empty-function': ['error'], 'no-extend-native': ['error'], 'no-floating-decimal': ['error'], + 'no-lonely-if': ['error'], 'no-multi-spaces': ['error'], 'no-multiple-empty-lines': ['error', { 'max': 1 }], 'no-nested-ternary': ['error'], From 20381bd3ec2f23907ed53d7da568cae921b05506 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 12 Sep 2023 17:02:06 -0400 Subject: [PATCH 38/49] Fix all loneliness --- src/components/cardbuilder/cardBuilder.js | 30 +++++------ src/components/guide/guide.js | 8 ++- src/components/homesections/homesections.js | 12 ++--- src/components/htmlMediaHelper.js | 22 ++++---- .../imageDownloader/imageDownloader.js | 32 +++++------- src/components/imageeditor/imageeditor.js | 6 +-- src/components/listview/listview.js | 14 +++-- src/components/nowPlayingBar/nowPlayingBar.js | 6 +-- src/components/playback/playbackmanager.js | 14 +++-- .../recordingcreator/recordingfields.js | 28 +++++----- src/components/viewContainer.js | 8 ++- src/controllers/dashboard/dashboard.js | 12 ++--- src/controllers/dashboard/dlna/profile.js | 18 +++---- src/controllers/playback/video/index.js | 8 ++- .../emby-itemscontainer.js | 6 +-- src/index.jsx | 6 +-- src/libraries/navdrawer/navdrawer.js | 52 ++++++++----------- src/libraries/scroller.js | 18 +++---- src/plugins/htmlVideoPlayer/plugin.js | 27 ++++------ src/plugins/youtubePlayer/plugin.js | 6 +-- src/scripts/libraryMenu.js | 28 +++++----- src/scripts/scrollHelper.js | 16 +++--- 22 files changed, 154 insertions(+), 223 deletions(-) diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index d5b19c0210..96d7edb06a 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -803,19 +803,17 @@ function getCardFooterText(item, apiClient, options, footerClass, progressHtml, } else { lines.push(escapeHtml(item.SeriesName)); } + } else if (isUsingLiveTvNaming(item)) { + lines.push(escapeHtml(item.Name)); + + if (!item.EpisodeTitle && !item.IndexNumber) { + titleAdded = true; + } } else { - if (isUsingLiveTvNaming(item)) { - lines.push(escapeHtml(item.Name)); + const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; - if (!item.EpisodeTitle && !item.IndexNumber) { - titleAdded = true; - } - } else { - const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; - - if (parentTitle || showTitle) { - lines.push(escapeHtml(parentTitle)); - } + if (parentTitle || showTitle) { + lines.push(escapeHtml(parentTitle)); } } } @@ -898,13 +896,11 @@ function getCardFooterText(item, apiClient, options, footerClass, progressHtml, if (item.Type === 'Series') { if (item.Status === 'Continuing') { lines.push(globalize.translate('SeriesYearToPresent', productionYear || '')); + } else if (item.EndDate && item.ProductionYear) { + const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); + lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); } else { - if (item.EndDate && item.ProductionYear) { - const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); - lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); - } else { - lines.push(productionYear || ''); - } + lines.push(productionYear || ''); } } else { lines.push(productionYear || ''); diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 9384ba2056..76c54693f9 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -762,12 +762,10 @@ function Guide(options) { } else { container.scrollTo(0, pos); } + } else if (horizontal) { + container.scrollLeft = Math.round(pos); } else { - if (horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } + container.scrollTop = Math.round(pos); } } diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 2bb9bb7928..1b26f8624a 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -216,14 +216,12 @@ function getFetchLatestItemsFn(serverId, parentId, collectionType) { if (collectionType === 'music') { limit = 30; } + } else if (collectionType === 'tvshows') { + limit = 5; + } else if (collectionType === 'music') { + limit = 9; } else { - if (collectionType === 'tvshows') { - limit = 5; - } else if (collectionType === 'music') { - limit = 9; - } else { - limit = 8; - } + limit = 8; } const options = { diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index 5db0428cda..c63eb29db0 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -76,20 +76,18 @@ export function handleHlsJsMediaError(instance, reject) { recoverDecodingErrorDate = now; console.debug('try to recover media Error ...'); hlsPlayer.recoverMediaError(); + } else if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { + recoverSwapAudioCodecDate = now; + console.debug('try to swap Audio Codec and recover media Error ...'); + hlsPlayer.swapAudioCodec(); + hlsPlayer.recoverMediaError(); } else { - if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { - recoverSwapAudioCodecDate = now; - console.debug('try to swap Audio Codec and recover media Error ...'); - hlsPlayer.swapAudioCodec(); - hlsPlayer.recoverMediaError(); - } else { - console.error('cannot recover, last media error recovery failed ...'); + console.error('cannot recover, last media error recovery failed ...'); - if (reject) { - reject(); - } else { - onErrorInternal(instance, 'mediadecodeerror'); - } + if (reject) { + reject(); + } else { + onErrorInternal(instance, 'mediadecodeerror'); } } } diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index 1955896180..380803d664 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -171,14 +171,12 @@ function getRemoteImageHtml(image, imageType) { shape = 'banner'; } else if (imageType === 'Disc') { shape = 'square'; + } else if (currentItemType === 'Episode') { + shape = 'backdrop'; + } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { + shape = 'square'; } else { - if (currentItemType === 'Episode') { - shape = 'backdrop'; - } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { - shape = 'square'; - } else { - shape = 'portrait'; - } + shape = 'portrait'; } cssClass += ' ' + shape + 'Card ' + shape + 'Card-scalable'; @@ -230,10 +228,8 @@ function getRemoteImageHtml(image, imageType) { if (image.Language) { html += ' • ' + image.Language; } - } else { - if (image.Language) { - html += image.Language; - } + } else if (image.Language) { + html += image.Language; } html += '
'; @@ -244,16 +240,14 @@ function getRemoteImageHtml(image, imageType) { if (image.RatingType === 'Likes') { html += image.CommunityRating + (image.CommunityRating === 1 ? ' like' : ' likes'); - } else { - if (image.CommunityRating) { - html += image.CommunityRating.toFixed(1); + } else if (image.CommunityRating) { + html += image.CommunityRating.toFixed(1); - if (image.VoteCount) { - html += ' • ' + image.VoteCount + (image.VoteCount === 1 ? ' vote' : ' votes'); - } - } else { - html += 'Unrated'; + if (image.VoteCount) { + html += ' • ' + image.VoteCount + (image.VoteCount === 1 ? ' vote' : ' votes'); } + } else { + html += 'Unrated'; } html += '
'; diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index 6a7982cca9..9e7a9d5710 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -164,10 +164,8 @@ function getCardHtml(image, apiClient, options) { } else { html += ''; } - } else { - if (options.imageProviders.length) { - html += ''; - } + } else if (options.imageProviders.length) { + html += ''; } html += ''; diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js index d35bdc3527..1a230c31ed 100644 --- a/src/components/listview/listview.js +++ b/src/components/listview/listview.js @@ -374,14 +374,12 @@ export function getListViewHtml(options) { if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') { textlines.push(item.AlbumArtist); } - } else { - if (options.artist) { - const artistItems = item.ArtistItems; - if (artistItems && item.Type !== 'MusicAlbum') { - textlines.push(artistItems.map(a => { - return a.Name; - }).join(', ')); - } + } else if (options.artist) { + const artistItems = item.ArtistItems; + if (artistItems && item.Type !== 'MusicAlbum') { + textlines.push(artistItems.map(a => { + return a.Name; + }).join(', ')); } } diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 37400559f2..fec43db3e8 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -649,10 +649,8 @@ function onPlaybackStopped(e, state) { if (state.NextMediaType !== 'Audio') { hideNowPlayingBar(); } - } else { - if (!state.NextMediaType) { - hideNowPlayingBar(); - } + } else if (!state.NextMediaType) { + hideNowPlayingBar(); } } diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 308acd79f5..26548bee3f 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1437,15 +1437,13 @@ class PlaybackManager { if (Screenfull.isEnabled) { Screenfull.toggle(); - } else { + } else if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { // iOS Safari - if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { - document.webkitCancelFullscreen(); - } else { - const elem = document.querySelector('video'); - if (elem?.webkitEnterFullscreen) { - elem.webkitEnterFullscreen(); - } + document.webkitCancelFullscreen(); + } else { + const elem = document.querySelector('video'); + if (elem?.webkitEnterFullscreen) { + elem.webkitEnterFullscreen(); } } }; diff --git a/src/components/recordingcreator/recordingfields.js b/src/components/recordingcreator/recordingfields.js index d890148c3b..2fb9764f2b 100644 --- a/src/components/recordingcreator/recordingfields.js +++ b/src/components/recordingcreator/recordingfields.js @@ -191,15 +191,13 @@ function onRecordChange(e) { loading.hide(); }); } - } else { - if (hasEnabledTimer) { - loading.show(); - recordingHelper.cancelTimer(apiClient, this.TimerId, true).then(function () { - Events.trigger(self, 'recordingchanged'); - fetchData(self); - loading.hide(); - }); - } + } else if (hasEnabledTimer) { + loading.show(); + recordingHelper.cancelTimer(apiClient, this.TimerId, true).then(function () { + Events.trigger(self, 'recordingchanged'); + fetchData(self); + loading.hide(); + }); } } @@ -223,13 +221,11 @@ function onRecordSeriesChange(e) { fetchData(self); }); } - } else { - if (this.SeriesTimerId) { - apiClient.cancelLiveTvSeriesTimer(this.SeriesTimerId).then(function () { - toast(globalize.translate('RecordingCancelled')); - fetchData(self); - }); - } + } else if (this.SeriesTimerId) { + apiClient.cancelLiveTvSeriesTimer(this.SeriesTimerId).then(function () { + toast(globalize.translate('RecordingCancelled')); + fetchData(self); + }); } } diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index a5547517a7..c2156a7d58 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -73,12 +73,10 @@ export function loadView(options) { } else { mainAnimatedPages.replaceChild(view, currentPage); } + } else if (newViewInfo.hasScript && window.$) { + view = $(view).appendTo(mainAnimatedPages)[0]; } else { - if (newViewInfo.hasScript && window.$) { - view = $(view).appendTo(mainAnimatedPages)[0]; - } else { - mainAnimatedPages.appendChild(view); - } + mainAnimatedPages.appendChild(view); } if (options.type) { diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index 372000a685..bae3880f28 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -484,13 +484,11 @@ window.DashboardPage = { if (nowPlayingItem.Artists?.length) { bottomText = topText; topText = escapeHtml(nowPlayingItem.Artists[0]); - } else { - if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { - bottomText = topText; - topText = escapeHtml(nowPlayingItem.SeriesName || nowPlayingItem.Album); - } else if (nowPlayingItem.ProductionYear) { - bottomText = nowPlayingItem.ProductionYear; - } + } else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { + bottomText = topText; + topText = escapeHtml(nowPlayingItem.SeriesName || nowPlayingItem.Album); + } else if (nowPlayingItem.ProductionYear) { + bottomText = nowPlayingItem.ProductionYear; } if (nowPlayingItem.ImageTags?.Logo) { diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index 47583f2510..0a88f4214e 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -272,10 +272,8 @@ function renderDirectPlayProfiles(page, profiles) { if (profile.Type == 'Video') { html += '

' + globalize.translate('ValueVideoCodec', profile.VideoCodec || allText) + '

'; html += '

' + globalize.translate('ValueAudioCodec', profile.AudioCodec || allText) + '

'; - } else { - if (profile.Type == 'Audio') { - html += '

' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '

'; - } + } else if (profile.Type == 'Audio') { + html += '

' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '

'; } html += '
'; @@ -333,10 +331,8 @@ function renderTranscodingProfiles(page, profiles) { if (profile.Type == 'Video') { html += '

' + globalize.translate('ValueVideoCodec', profile.VideoCodec || allText) + '

'; html += '

' + globalize.translate('ValueAudioCodec', profile.AudioCodec || allText) + '

'; - } else { - if (profile.Type == 'Audio') { - html += '

' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '

'; - } + } else if (profile.Type == 'Audio') { + html += '

' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '

'; } html += ''; @@ -561,10 +557,8 @@ function renderResponseProfiles(page, profiles) { if (profile.Type == 'Video') { html += '

' + globalize.translate('ValueVideoCodec', profile.VideoCodec || allText) + '

'; html += '

' + globalize.translate('ValueAudioCodec', profile.AudioCodec || allText) + '

'; - } else { - if (profile.Type == 'Audio') { - html += '

' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '

'; - } + } else if (profile.Type == 'Audio') { + html += '

' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '

'; } if (profile.Conditions?.length) { diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 0feb6e3752..ba066f1333 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -392,11 +392,9 @@ export default function (view) { case 'left': if (currentVisibleMenu === 'osd') { showOsd(); - } else { - if (!currentVisibleMenu) { - e.preventDefault(); - playbackManager.rewind(player); - } + } else if (!currentVisibleMenu) { + e.preventDefault(); + playbackManager.rewind(player); } break; diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index b951fa7d2f..dd40a80531 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -285,10 +285,8 @@ ItemsContainerPrototype.attachedCallback = function () { if (browser.touch) { this.addEventListener('contextmenu', disableEvent); - } else { - if (this.getAttribute('data-contextmenu') !== 'false') { - this.addEventListener('contextmenu', onContextMenu); - } + } else if (this.getAttribute('data-contextmenu') !== 'false') { + this.addEventListener('contextmenu', onContextMenu); } if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') { diff --git a/src/index.jsx b/src/index.jsx index 9bcbffa05c..49d95c56c6 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -227,10 +227,8 @@ async function onAppReady() { document.body.appendChild(localStyle); } localStyle.textContent = localCss; - } else { - if (localStyle) { - localStyle.textContent = ''; - } + } else if (localStyle) { + localStyle.textContent = ''; } }; diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index ef6dd1e04f..9bd33b5edf 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -130,17 +130,15 @@ class NavDrawer { if (this.isPeeking) { this.onMenuTouchMove(e); - } else { - if ((getTouches(e)[0]?.clientX || 0) <= options.handleSize) { - this.isPeeking = true; + } else if ((getTouches(e)[0]?.clientX || 0) <= options.handleSize) { + this.isPeeking = true; - if (e.type === 'touchstart') { - dom.removeEventListener(this.edgeContainer, 'touchmove', this.onEdgeTouchMove, {}); - dom.addEventListener(this.edgeContainer, 'touchmove', this.onEdgeTouchMove, {}); - } - - this.onMenuTouchStart(e); + if (e.type === 'touchstart') { + dom.removeEventListener(this.edgeContainer, 'touchmove', this.onEdgeTouchMove, {}); + dom.addEventListener(this.edgeContainer, 'touchmove', this.onEdgeTouchMove, {}); } + + this.onMenuTouchStart(e); } }; @@ -246,14 +244,10 @@ class NavDrawer { } else { this.close(); } - } else { - if (this.newPos >= 100) { - this.open(); - } else { - if (this.newPos) { - this.close(); - } - } + } else if (this.newPos >= 100) { + this.open(); + } else if (this.newPos) { + this.close(); } } @@ -320,19 +314,17 @@ class NavDrawer { passive: true }); } - } else { - if (this._edgeSwipeEnabled) { - this._edgeSwipeEnabled = false; - dom.removeEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, { - passive: true - }); - dom.removeEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, { - passive: true - }); - dom.removeEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, { - passive: true - }); - } + } else if (this._edgeSwipeEnabled) { + this._edgeSwipeEnabled = false; + dom.removeEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, { + passive: true + }); + dom.removeEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, { + passive: true + }); + dom.removeEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, { + passive: true + }); } } } diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 12edc468cc..7e72141864 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -257,12 +257,10 @@ const scrollerFactory = function (frame, options) { } else { container.scrollTo(0, Math.round(pos)); } + } else if (o.horizontal) { + container.scrollLeft = Math.round(pos); } else { - if (o.horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } + container.scrollTop = Math.round(pos); } } @@ -506,14 +504,12 @@ const scrollerFactory = function (frame, options) { // If the pointer was released, the path will not become longer and it's // definitely not a drag. If not released yet, decide on next iteration return dragging.released ? dragEnd() : undefined; - } else { + } else if (o.horizontal ? Math.abs(dragging.pathX) > Math.abs(dragging.pathY) : Math.abs(dragging.pathX) < Math.abs(dragging.pathY)) { // If dragging path is sufficiently long we can confidently start a drag // if drag is in different direction than scroll, ignore it - if (o.horizontal ? Math.abs(dragging.pathX) > Math.abs(dragging.pathY) : Math.abs(dragging.pathX) < Math.abs(dragging.pathY)) { - dragging.init = 1; - } else { - return dragEnd(); - } + dragging.init = 1; + } else { + return dragEnd(); } } diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index c6d9eeb68b..a3d9420c6e 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -864,11 +864,9 @@ export class HtmlVideoPlayer { if (Screenfull.isEnabled) { Screenfull.exit(); - } else { + } else if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { // iOS Safari - if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { - document.webkitCancelFullscreen(); - } + document.webkitCancelFullscreen(); } } @@ -1106,15 +1104,14 @@ export class HtmlVideoPlayer { tryRemoveElement(this.#videoSecondarySubtitlesElem); this.#videoSecondarySubtitlesElem = null; } - } else { // destroy all - if (this.#videoSubtitlesElem) { - const subtitlesContainer = this.#videoSubtitlesElem.parentNode; - if (subtitlesContainer) { - tryRemoveElement(subtitlesContainer); - } - this.#videoSubtitlesElem = null; - this.#videoSecondarySubtitlesElem = null; + } else if (this.#videoSubtitlesElem) { + // destroy all + const subtitlesContainer = this.#videoSubtitlesElem.parentNode; + if (subtitlesContainer) { + tryRemoveElement(subtitlesContainer); } + this.#videoSubtitlesElem = null; + this.#videoSecondarySubtitlesElem = null; } } @@ -1812,10 +1809,8 @@ export class HtmlVideoPlayer { } else { Windows.UI.ViewManagement.ApplicationView.getForCurrentView().tryEnterViewModeAsync(Windows.UI.ViewManagement.ApplicationViewMode.default); } - } else { - if (video?.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') { - video.webkitSetPresentationMode(isEnabled ? 'picture-in-picture' : 'inline'); - } + } else if (video?.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') { + video.webkitSetPresentationMode(isEnabled ? 'picture-in-picture' : 'inline'); } } diff --git a/src/plugins/youtubePlayer/plugin.js b/src/plugins/youtubePlayer/plugin.js index 47492468aa..7c9b68cd95 100644 --- a/src/plugins/youtubePlayer/plugin.js +++ b/src/plugins/youtubePlayer/plugin.js @@ -352,10 +352,8 @@ class YoutubePlayer { if (currentYoutubePlayer) { currentYoutubePlayer.mute(); } - } else { - if (currentYoutubePlayer) { - currentYoutubePlayer.unMute(); - } + } else if (currentYoutubePlayer) { + currentYoutubePlayer.unMute(); } } isMuted() { diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index b77dfa92e1..0f3b4a73e1 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -835,26 +835,24 @@ function updateMenuForPageType(isDashboardPage, isLibraryPage) { bodyClassList.remove('dashboardDocument'); bodyClassList.remove('hideMainDrawer'); + if (navDrawerInstance) { + navDrawerInstance.setEdgeSwipeEnabled(true); + } + } else if (isDashboardPage) { + bodyClassList.remove('libraryDocument'); + bodyClassList.add('dashboardDocument'); + bodyClassList.remove('hideMainDrawer'); + if (navDrawerInstance) { navDrawerInstance.setEdgeSwipeEnabled(true); } } else { - if (isDashboardPage) { - bodyClassList.remove('libraryDocument'); - bodyClassList.add('dashboardDocument'); - bodyClassList.remove('hideMainDrawer'); + bodyClassList.remove('libraryDocument'); + bodyClassList.remove('dashboardDocument'); + bodyClassList.add('hideMainDrawer'); - if (navDrawerInstance) { - navDrawerInstance.setEdgeSwipeEnabled(true); - } - } else { - bodyClassList.remove('libraryDocument'); - bodyClassList.remove('dashboardDocument'); - bodyClassList.add('hideMainDrawer'); - - if (navDrawerInstance) { - navDrawerInstance.setEdgeSwipeEnabled(false); - } + if (navDrawerInstance) { + navDrawerInstance.setEdgeSwipeEnabled(false); } } } diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js index 725df86434..9f6a870ed3 100644 --- a/src/scripts/scrollHelper.js +++ b/src/scripts/scrollHelper.js @@ -54,12 +54,10 @@ export function toCenter(container, elem, horizontal, skipWhenVisible) { } else { container.scrollTo(0, pos.center); } + } else if (horizontal) { + container.scrollLeft = Math.round(pos.center); } else { - if (horizontal) { - container.scrollLeft = Math.round(pos.center); - } else { - container.scrollTop = Math.round(pos.center); - } + container.scrollTop = Math.round(pos.center); } } @@ -76,12 +74,10 @@ export function toStart(container, elem, horizontal, skipWhenVisible) { } else { container.scrollTo(0, pos.start); } + } else if (horizontal) { + container.scrollLeft = Math.round(pos.start); } else { - if (horizontal) { - container.scrollLeft = Math.round(pos.start); - } else { - container.scrollTop = Math.round(pos.start); - } + container.scrollTop = Math.round(pos.start); } } From fddee0607fbad7da4650422d0f5b7e79216d528a Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 12 Sep 2023 23:53:39 -0400 Subject: [PATCH 39/49] Add eslint rule to prevent unused private class members --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 44fd0a3046..b0ef3ebb86 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -63,6 +63,7 @@ module.exports = { 'no-trailing-spaces': ['error'], 'no-unused-expressions': ['off'], '@typescript-eslint/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], + 'no-unused-private-class-members': ['error'], 'no-useless-constructor': ['off'], '@typescript-eslint/no-useless-constructor': ['error'], 'no-var': ['error'], From dd5199c544a7bc58f8de0f79d523b3931bfbe960 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 12 Sep 2023 23:56:08 -0400 Subject: [PATCH 40/49] Remove unused private class members --- src/components/pluginManager.js | 16 ---------------- src/plugins/htmlVideoPlayer/plugin.js | 4 ---- 2 files changed, 20 deletions(-) diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js index 948912cbd8..847e2bc579 100644 --- a/src/components/pluginManager.js +++ b/src/components/pluginManager.js @@ -129,22 +129,6 @@ class PluginManager { .sort((p1, p2) => (p1.priority || 0) - (p2.priority || 0))[0]; } - #mapRoute(plugin, route) { - if (typeof plugin === 'string') { - plugin = this.pluginsList.filter((p) => { - return (p.id || p.packageName) === plugin; - })[0]; - } - - route = route.path || route; - - if (route.toLowerCase().startsWith('http')) { - return route; - } - - return '/plugins/' + plugin.id + '/' + route; - } - mapPath(plugin, path, addCacheParam) { if (typeof plugin === 'string') { plugin = this.pluginsList.filter((p) => { diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index c6d9eeb68b..e43e44d127 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -277,10 +277,6 @@ export class HtmlVideoPlayer { * @type {number | null | undefined} */ #currentTime; - /** - * @type {any | undefined} - */ - #flvPlayer; /** * @private (used in other files) * @type {any | undefined} From 171c38176893ec9a1ecfa0f433160b8459320ed8 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 27 Aug 2023 01:02:21 +0300 Subject: [PATCH 41/49] Add HLS direct play profile --- src/scripts/browserDeviceProfile.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index ff8bcfcca2..c00e188d4b 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -718,6 +718,15 @@ export default function (options) { enableFmp4Hls = false; } if (hlsInFmp4VideoCodecs.length && hlsInFmp4VideoAudioCodecs.length && enableFmp4Hls) { + // HACK: Since there is no filter for TS/MP4 in the API, specify HLS support in general and rely on retry after DirectPlay error + // FIXME: Need support for {Container: 'mp4', Protocol: 'hls'} or {Container: 'hls', SubContainer: 'mp4'} + profile.DirectPlayProfiles.push({ + Container: 'hls', + Type: 'Video', + VideoCodec: hlsInFmp4VideoCodecs.join(','), + AudioCodec: hlsInFmp4VideoAudioCodecs.join(',') + }); + profile.TranscodingProfiles.push({ Container: 'mp4', Type: 'Video', @@ -732,6 +741,15 @@ export default function (options) { } if (hlsInTsVideoCodecs.length && hlsInTsVideoAudioCodecs.length) { + // HACK: Since there is no filter for TS/MP4 in the API, specify HLS support in general and rely on retry after DirectPlay error + // FIXME: Need support for {Container: 'ts', Protocol: 'hls'} or {Container: 'hls', SubContainer: 'ts'} + profile.DirectPlayProfiles.push({ + Container: 'hls', + Type: 'Video', + VideoCodec: hlsInTsVideoCodecs.join(','), + AudioCodec: hlsInTsVideoAudioCodecs.join(',') + }); + profile.TranscodingProfiles.push({ Container: 'ts', Type: 'Video', From b2c7d93498c36eff5d9fe75bf854d2c26aa733f5 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 27 Aug 2023 01:33:35 +0300 Subject: [PATCH 42/49] Fix HLS detection --- src/plugins/htmlVideoPlayer/plugin.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index c6d9eeb68b..9a928709ac 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -69,12 +69,12 @@ function tryRemoveElement(elem) { } } -function enableNativeTrackSupport(currentSrc, track) { +function enableNativeTrackSupport(mediaSource, track) { if (track?.DeliveryMethod === 'Embed') { return true; } - if (browser.firefox && (currentSrc || '').toLowerCase().includes('.m3u8')) { + if (browser.firefox && (mediaSource?.TranscodingSubProtocol || mediaSource?.Container) === 'hls') { return false; } @@ -341,11 +341,11 @@ export class HtmlVideoPlayer { * @private */ updateVideoUrl(streamInfo) { - const isHls = streamInfo.url.toLowerCase().includes('.m3u8'); - const mediaSource = streamInfo.mediaSource; const item = streamInfo.item; + const isHls = (mediaSource?.TranscodingSubProtocol || mediaSource?.Container) === 'hls'; + // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts // This will start the transcoding process before actually feeding the video url into the player // Edit: Also seeing stalls from hls.js @@ -513,7 +513,7 @@ export class HtmlVideoPlayer { elem.crossOrigin = crossOrigin; } - if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.includes('.m3u8')) { + if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && (options.mediaSource.TranscodingSubProtocol || options.mediaSource.Container) === 'hls') { return this.setSrcWithHlsJs(elem, options, val); } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { return this.setSrcWithFlvJs(elem, options, val); @@ -1561,7 +1561,7 @@ export class HtmlVideoPlayer { })[0]; this.setTrackForDisplay(this.#mediaElement, track, targetTextTrackIndex); - if (enableNativeTrackSupport(this.#currentSrc, track)) { + if (enableNativeTrackSupport(this._currentPlayOptions?.mediaSource, track)) { if (streamIndex !== -1) { this.setCueAppearance(); } From 0f62cd9e520f6fed0bc4790d6daa353dc1a91447 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 14 Sep 2023 01:44:12 -0400 Subject: [PATCH 43/49] Fix flvPlayer variable name --- src/plugins/htmlVideoPlayer/plugin.js | 143 ++++++++++++++------------ 1 file changed, 75 insertions(+), 68 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index e43e44d127..0365f0f4e5 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -168,133 +168,140 @@ const SECONDARY_TEXT_TRACK_INDEX = 1; export class HtmlVideoPlayer { /** - * @type {string} - */ + * @type {string} + */ name; /** - * @type {string} - */ + * @type {string} + */ type = PluginType.MediaPlayer; /** - * @type {string} - */ + * @type {string} + */ id = 'htmlvideoplayer'; /** - * Let any players created by plugins take priority - * - * @type {number} - */ + * Let any players created by plugins take priority + * + * @type {number} + */ priority = 1; /** - * @type {boolean} - */ + * @type {boolean} + */ isFetching = false; /** - * @type {HTMLDivElement | null | undefined} - */ + * @type {HTMLDivElement | null | undefined} + */ #videoDialog; /** - * @type {number | undefined} - */ + * @type {number | undefined} + */ #subtitleTrackIndexToSetOnPlaying; /** - * @type {number | undefined} - */ + * @type {number | undefined} + */ #secondarySubtitleTrackIndexToSetOnPlaying; /** - * @type {number | null} - */ + * @type {number | null} + */ #audioTrackIndexToSetOnPlaying; /** - * @type {null | undefined} - */ + * @type {null | undefined} + */ #currentClock; /** - * @type {any | null | undefined} - */ + * @type {any | null | undefined} + */ #currentAssRenderer; /** - * @type {null | undefined} - */ + * @type {null | undefined} + */ #customTrackIndex; /** - * @type {number | undefined} - */ + * @type {number | undefined} + */ #customSecondaryTrackIndex; /** - * @type {boolean | undefined} - */ + * @type {boolean | undefined} + */ #showTrackOffset; /** - * @type {number | undefined} - */ + * @type {number | undefined} + */ #currentTrackOffset; /** - * @type {HTMLElement | null | undefined} - */ + * @type {HTMLElement | null | undefined} + */ #secondaryTrackOffset; /** - * @type {HTMLElement | null | undefined} - */ + * @type {HTMLElement | null | undefined} + */ #videoSubtitlesElem; /** - * @type {HTMLElement | null | undefined} - */ + * @type {HTMLElement | null | undefined} + */ #videoSecondarySubtitlesElem; /** - * @type {any | null | undefined} - */ + * @type {any | null | undefined} + */ #currentTrackEvents; /** - * @type {any | null | undefined} - */ + * @type {any | null | undefined} + */ #currentSecondaryTrackEvents; /** - * @type {string[] | undefined} - */ + * @type {string[] | undefined} + */ #supportedFeatures; /** - * @type {HTMLVideoElement | null | undefined} - */ + * @type {HTMLVideoElement | null | undefined} + */ #mediaElement; /** - * @type {number} - */ + * @type {number} + */ #fetchQueue = 0; /** - * @type {string | undefined} - */ + * @type {string | undefined} + */ #currentSrc; /** - * @type {boolean | undefined} - */ + * @type {boolean | undefined} + */ #started; /** - * @type {boolean | undefined} - */ + * @type {boolean | undefined} + */ #timeUpdated; /** - * @type {number | null | undefined} - */ + * @type {number | null | undefined} + */ #currentTime; + /** - * @private (used in other files) - * @type {any | undefined} - */ + * @private (used in other files) + * @type {any | undefined} + */ + _flvPlayer; + + /** + * @private (used in other files) + * @type {any | undefined} + */ _hlsPlayer; /** - * @private (used in other files) - * @type {any | null | undefined} - */ + * @private (used in other files) + * @type {any | null | undefined} + */ _castPlayer; /** - * @private (used in other files) - * @type {any | undefined} - */ + * @private (used in other files) + * @type {any | undefined} + */ _currentPlayOptions; /** - * @type {any | undefined} - */ + * @type {any | undefined} + */ #lastProfile; constructor() { @@ -404,7 +411,7 @@ export class HtmlVideoPlayer { flvPlayer.attachMediaElement(elem); flvPlayer.load(); - this.#flvPlayer = flvPlayer; + this._flvPlayer = flvPlayer; // This is needed in setCurrentTrackElement this.#currentSrc = url; From dca2594aaeb32eb7980845458b59d1a1601e3067 Mon Sep 17 00:00:00 2001 From: MBR-0001 <55142207+MBR-0001@users.noreply.github.com> Date: Thu, 14 Sep 2023 08:38:02 +0200 Subject: [PATCH 44/49] Update src/components/subtitleeditor/subtitleeditor.scss Co-authored-by: Bill Thornton --- src/components/subtitleeditor/subtitleeditor.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/subtitleeditor/subtitleeditor.scss b/src/components/subtitleeditor/subtitleeditor.scss index 16c2293cf1..578e82ddd2 100644 --- a/src/components/subtitleeditor/subtitleeditor.scss +++ b/src/components/subtitleeditor/subtitleeditor.scss @@ -3,8 +3,8 @@ } .subtitleFeaturePillow { - background: #38c; - color: #fff; + background: #00a4dc; + color: #000; padding: 0.3em 1em; border-radius: 1000em; margin-right: 0.25em; From c4cc6dbed1f40ca0ed4a639f80ec58e296efc111 Mon Sep 17 00:00:00 2001 From: MBR-0001 <55142207+MBR-0001@users.noreply.github.com> Date: Thu, 14 Sep 2023 08:38:14 +0200 Subject: [PATCH 45/49] Update src/components/subtitleeditor/subtitleeditor.js Co-authored-by: Bill Thornton --- src/components/subtitleeditor/subtitleeditor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 999fb217be..ffe05ae5c3 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -242,7 +242,7 @@ function renderSearchResults(context, results) { } if (result.HearingImpaired) { - html += spanOpen.replace('margin-right:0.25em;', '') + globalize.translate('HearingImpairedShort') + ''; + html += spanOpen + globalize.translate('HearingImpairedShort') + ''; } html += '
'; From 1684acd0ca4c6d95e488a23de28f53b065f2143d Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Thu, 14 Sep 2023 00:14:10 +0300 Subject: [PATCH 46/49] Extract HLS stream test function --- src/plugins/htmlVideoPlayer/plugin.js | 9 ++++----- src/utils/mediaSource.ts | 10 ++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 src/utils/mediaSource.ts diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 9a928709ac..e26569bb0d 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -35,6 +35,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/ba import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; import { includesAny } from '../../utils/container.ts'; +import { isHls } from '../../utils/mediaSource.ts'; import debounce from 'lodash-es/debounce'; /** @@ -74,7 +75,7 @@ function enableNativeTrackSupport(mediaSource, track) { return true; } - if (browser.firefox && (mediaSource?.TranscodingSubProtocol || mediaSource?.Container) === 'hls') { + if (browser.firefox && isHls(mediaSource)) { return false; } @@ -344,12 +345,10 @@ export class HtmlVideoPlayer { const mediaSource = streamInfo.mediaSource; const item = streamInfo.item; - const isHls = (mediaSource?.TranscodingSubProtocol || mediaSource?.Container) === 'hls'; - // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts // This will start the transcoding process before actually feeding the video url into the player // Edit: Also seeing stalls from hls.js - if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { + if (mediaSource && item && !mediaSource.RunTimeTicks && isHls(mediaSource) && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { const hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); loading.show(); @@ -513,7 +512,7 @@ export class HtmlVideoPlayer { elem.crossOrigin = crossOrigin; } - if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && (options.mediaSource.TranscodingSubProtocol || options.mediaSource.Container) === 'hls') { + if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && isHls(options.mediaSource)) { return this.setSrcWithHlsJs(elem, options, val); } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { return this.setSrcWithFlvJs(elem, options, val); diff --git a/src/utils/mediaSource.ts b/src/utils/mediaSource.ts new file mode 100644 index 0000000000..8238ef1d4d --- /dev/null +++ b/src/utils/mediaSource.ts @@ -0,0 +1,10 @@ +import type { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client'; + +/** + * Checks if the media source is an HLS stream. + * @param mediaSource The media source. + * @returns _true_ if the media source is an HLS stream, _false_ otherwise. + */ +export function isHls(mediaSource: MediaSourceInfo|null|undefined): boolean { + return (mediaSource?.TranscodingSubProtocol || mediaSource?.Container) === 'hls'; +} From 4b7418d29df7380acb65cb0189cde7946121269e Mon Sep 17 00:00:00 2001 From: hcharbonnier Date: Thu, 14 Sep 2023 19:41:06 +0000 Subject: [PATCH 47/49] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 1f206c563b..ea1d558809 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1384,7 +1384,7 @@ "LabelTonemappingRange": "Gamme de mappage tonal", "TonemappingAlgorithmHelp": "Le mappage tonal peut être affiné. Si vous n'êtes pas familier avec ces réglages, il est conseillé de choisir les valeurs recommandées. L'algorithme de mappage recommandé est 'BT.2390'.", "LabelTonemappingAlgorithm": "Sélectionner l'algorithme de mappage tonal à utiliser", - "AllowTonemappingHelp": "Le mappage tonal est capable de convertir une gamme dynamique HDR en SDR tout en maintenant les détails et les couleurs d'image si importants au rendu de la scène originale. Pour le moment, ne fonctionne qu'avec les vidéos 10bits HDR10, HLG, DoVi et requiert les environnements d'exécution OpenCL et CUDA correspondant.", + "AllowTonemappingHelp": "Le mappage tonal est capable de convertir une gamme dynamique HDR en SDR tout en maintenant les détails et les couleurs d'image si importants au rendu de la scène originale. Pour le moment, ne fonctionne qu'avec les vidéos HDR10 10bits, HLG, DoVi et requiert les environnements d'exécution OpenCL et CUDA correspondant.", "EnableTonemapping": "Activer le mappage tonal", "LabelOpenclDeviceHelp": "Ce dispositif OpenCL est utilisé pour le mappage tonal. La partie à gauche du point est le numéro de plate-forme et la partie à droite est le numéro du dispositif sur la plate-forme. La valeur par défaut est 0,0. Le fichier de l'application FFmpeg contenant l'accélération matérielle OpenCL est nécessaire.", "LabelOpenclDevice": "Dispositif OpenCL", @@ -1748,7 +1748,7 @@ "LabelLevel": "Niveau", "LabelMediaDetails": "Détails du média", "LabelSystem": "Système", - "LogLevel.Trace": "Trace", + "LogLevel.Trace": "Suivi", "LogLevel.Debug": "Debug", "LogLevel.Information": "Information", "LogLevel.Warning": "Avertissement", @@ -1763,5 +1763,6 @@ "LabelSegmentKeepSeconds": "Durée de conservation des segments", "LabelSegmentKeepSecondsHelp": "Durée en secondes de conservation des segments avant écrasement. La valeur doit être supérieure au délai d'ajustement. Ne fonctionne que si la suppression des segments est activée.", "LabelBackdropScreensaverIntervalHelp": "Le temps en secondes entre différents fonds d'écran lors de l'utilisation de l'économiseur d'écran à fonds d'écran.", - "LabelBackdropScreensaverInterval": "Intervalle de l'économiseur d'écran à fonds d'écran" + "LabelBackdropScreensaverInterval": "Intervalle de l'économiseur d'écran à fonds d'écran", + "AllowAv1Encoding": "Autoriser l'encodage au format AV1" } From 424f421281c7afe9236942e2d57c9e3728010392 Mon Sep 17 00:00:00 2001 From: Nikolay Babanov Date: Thu, 14 Sep 2023 23:24:57 +0000 Subject: [PATCH 48/49] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/bg/ --- src/strings/bg-bg.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strings/bg-bg.json b/src/strings/bg-bg.json index 4af828da24..f9f61bb285 100644 --- a/src/strings/bg-bg.json +++ b/src/strings/bg-bg.json @@ -1474,5 +1474,9 @@ "AllowSegmentDeletionHelp": "Изтриване на сегментите след изпращане към клиента. Това предотвратява нуждата за съхраняване на целия транскодиран файл на диска. Работи само с включено подтискане. Изключете тази функция ако изпитате проблеми с възпоизвеждането.", "LabelThrottleDelaySecondsHelp": "Време в секунди след което транскодера ще бъде подтиснат. Трябва да е достатъчно голям за клиента да поддържа здрав буфер. Работи само ако подтискането е включено.", "LabelSegmentKeepSeconds": "Време в което да се запазят сегменти", - "LabelSegmentKeepSecondsHelp": "Време в секунди за което сегментите трябва да се пазят след презаписване. Трябва да е повече от \"Подтискане след\". Работи само ако 'Изтриване на сегменти' е включено." + "LabelSegmentKeepSecondsHelp": "Време в секунди за което сегментите трябва да се пазят след презаписване. Трябва да е повече от \"Подтискане след\". Работи само ако 'Изтриване на сегменти' е включено.", + "EnableAudioNormalizationHelp": "Нормализацията на звука ще усили сигналът за да поддържа средните честоти на желано ниво (-18dB).", + "EnableAudioNormalization": "Нормализация на звука", + "Unknown": "Неизвестен", + "LabelThrottleDelaySeconds": "Ограничи след" } From 331b7a1539a4130be26c9c2a7b9ce9d617a79d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Fri, 15 Sep 2023 17:15:52 +0000 Subject: [PATCH 49/49] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index 4cd89478cb..f80641a27f 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1764,5 +1764,11 @@ "LabelSegmentKeepSecondsHelp": "Čas v sekundách, po který budou části překódovaného souboru uloženy. Musí být delší než čas určený v \"Omezit po\". Funguje pouze při zapnuté funkci Odstranění částí.", "LabelBackdropScreensaverInterval": "Interval šetřiče \"Pozadí\"", "LabelBackdropScreensaverIntervalHelp": "Čas v sekundách mezi změnou pozadí při použití šetřiče \"Pozadí\".", - "AllowAv1Encoding": "Povolit kódování do formátu AV1" + "AllowAv1Encoding": "Povolit kódování do formátu AV1", + "GridView": "Mřížka", + "ListView": "Seznam", + "MachineTranslated": "Strojově přeloženo", + "ForeignPartsOnly": "Pouze vynucené", + "HearingImpairedShort": "Titulky pro neslyšící", + "AiTranslated": "Přeložené pomocí UI" }