From 8cae67d9b908fc25380f35ad5eb35ae08fb8ae68 Mon Sep 17 00:00:00 2001 From: Ian Walton Date: Sat, 24 Apr 2021 14:33:07 -0400 Subject: [PATCH 1/4] Allow client custom css and disabling global server custom css. --- .../displaySettings/displaySettings.js | 7 ++ .../displaySettings.template.html | 13 ++++ src/elements/emby-textarea/emby-textarea.js | 1 - src/scripts/settings/userSettings.js | 45 +++++++++++++ src/scripts/site.js | 64 ++++++++++++------- src/strings/en-us.json | 3 + 6 files changed, 108 insertions(+), 25 deletions(-) diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index 261c25a89c..77d584d850 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -11,6 +11,7 @@ import { Events } from 'jellyfin-apiclient'; import '../../elements/emby-select/emby-select'; import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-button/emby-button'; +import '../../elements/emby-textarea/emby-textarea'; import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './displaySettings.template.html'; @@ -123,6 +124,9 @@ import template from './displaySettings.template.html'; context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); + context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss(); + context.querySelector('#txtLocalCustomCss').value = userSettings.customCss(); + context.querySelector('#selectLanguage').value = userSettings.language() || ''; context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; @@ -157,6 +161,9 @@ import template from './displaySettings.template.html'; userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); + userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked); + userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value); + if (user.Id === apiClient.getCurrentUserId()) { skinManager.setTheme(userSettingsInstance.theme()); } diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index f79155b7fd..930b3b2947 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -156,6 +156,19 @@ +
+ +
${LabelDisableCustomCss}
+
+ +
+ +
${LabelLocalCustomCss}
+
+
diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index 5a34c2e8b8..309164ddde 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -56,7 +56,6 @@ import '../emby-input/emby-input'; textarea.style.height = 'auto'; newHeight = textarea.scrollHeight/* - offset*/; } - $('.customCssContainer').css('height', newHeight + 'px'); textarea.style.height = newHeight + 'px'; } diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index a548f0cef5..417571d9af 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -21,6 +21,9 @@ const defaultSubtitleAppearanceSettings = { export class UserSettings { constructor() { + this._userSetPromise = new Promise(resolve => { + this._userSetPromiseResolve = resolve; + }); } /** @@ -36,6 +39,11 @@ export class UserSettings { this.currentUserId = userId; this.currentApiClient = apiClient; + if (this._userSetPromiseResolve) { + this._userSetPromiseResolve(); + this._userSetPromiseResolve = null; + } + if (!userId) { this.displayPrefs = null; return Promise.resolve(); @@ -59,6 +67,14 @@ export class UserSettings { this.displayPrefs = instance.getData(); } + /** + * Allows waiting until the user id is set. + * @returns {Promise} + */ + userIsSet() { + return this._userSetPromise; + } + // FIXME: 'appSettings.set' doesn't return any value /** * Set value of setting. @@ -239,6 +255,33 @@ export class UserSettings { return val === 'true'; } + /** + * Get or set 'disableCustomCss' state. + * @param {boolean|undefined} val - Flag to enable 'disableCustomCss' or undefined. + * @return {boolean} 'disableCustomCss' state. + */ + disableCustomCss(val) { + if (val !== undefined) { + return this.set('disableCustomCss', val.toString(), false); + } + + val = this.get('disableCustomCss', false); + return val === 'true'; + } + + /** + * Get or set customCss. + * @param {string|undefined} val - Language. + * @return {string} Language. + */ + customCss(val) { + if (val !== undefined) { + return this.set('customCss', val.toString(), false); + } + + return this.get('customCss', false); + } + /** * Get or set 'Details Banner' state. * @param {boolean|undefined} val - Flag to enable 'Details Banner' or undefined. @@ -511,3 +554,5 @@ export const getSubtitleAppearanceSettings = currentSettings.getSubtitleAppearan export const setSubtitleAppearanceSettings = currentSettings.setSubtitleAppearanceSettings.bind(currentSettings); export const setFilter = currentSettings.setFilter.bind(currentSettings); export const getFilter = currentSettings.getFilter.bind(currentSettings); +export const customCss = currentSettings.customCss.bind(currentSettings); +export const disableCustomCss = currentSettings.disableCustomCss.bind(currentSettings); diff --git a/src/scripts/site.js b/src/scripts/site.js index 03084aa4c5..550c1283aa 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -37,6 +37,7 @@ import SyncPlayToasts from '../components/syncPlay/ui/syncPlayToasts'; import SyncPlayNoActivePlayer from '../components/syncPlay/ui/players/NoActivePlayer'; import SyncPlayHtmlVideoPlayer from '../components/syncPlay/ui/players/HtmlVideoPlayer'; import SyncPlayHtmlAudioPlayer from '../components/syncPlay/ui/players/HtmlAudioPlayer'; +import { currentSettings } from './settings/userSettings'; // TODO: Move this elsewhere window.getWindowLocationSearch = function(win) { @@ -216,30 +217,45 @@ function onAppReady() { } } - const apiClient = ServerConnections.currentApiClient(); - if (apiClient) { - fetch(apiClient.getUrl('Branding/Css')) - .then(function(response) { - if (!response.ok) { - throw new Error(response.status + ' ' + response.statusText); - } - return response.text(); - }) - .then(function(css) { - let style = document.querySelector('#cssBranding'); - if (!style) { - // Inject the branding css as a dom element in body so it will take - // precedence over other stylesheets - style = document.createElement('style'); - style.id = 'cssBranding'; - document.body.appendChild(style); - } - style.textContent = css; - }) - .catch(function(err) { - console.warn('Error applying custom css', err); - }); - } + currentSettings.userIsSet().then(() => { + const apiClient = ServerConnections.currentApiClient(); + if (apiClient && !currentSettings.disableCustomCss()) { + fetch(apiClient.getUrl('Branding/Css')) + .then(function(response) { + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + return response.text(); + }) + .then(function(css) { + let style = document.querySelector('#cssBranding'); + if (!style) { + // Inject the branding css as a dom element in body so it will take + // precedence over other stylesheets + style = document.createElement('style'); + style.id = 'cssBranding'; + document.body.appendChild(style); + } + style.textContent = css; + }) + .catch(function(err) { + console.warn('Error applying custom css', err); + }); + } + + const localCss = currentSettings.customCss(); + if (localCss) { + let style = document.querySelector('#localCssBranding'); + if (!style) { + // Inject the branding css as a dom element in body so it will take + // precedence over other stylesheets + style = document.createElement('style'); + style.id = 'localCssBranding'; + document.body.appendChild(style); + } + style.textContent = localCss; + } + }); } function registerServiceWorker() { diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 64dbf801ab..d57fcd021a 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -181,6 +181,7 @@ "DirectStreamHelp2": "Power consumed by direct streaming usually depends on the audio profile. Only the video stream is lossless.", "DirectStreaming": "Direct streaming", "EnablePlugin": "Enable", + "DisableCustomCss": "Disable Server-Provided Custom CSS", "DisablePlugin": "Disable", "Disc": "Disc", "Disconnect": "Disconnect", @@ -577,6 +578,7 @@ "LabelDeinterlaceMethod": "Deinterlacing method:", "LabelDeviceDescription": "Device description", "LabelDidlMode": "DIDL mode:", + "LabelDisableCustomCss": "Disable custom CSS theming/branding provided from the server.", "LabelDiscNumber": "Disc number:", "LabelDisplayLanguage": "Display language:", "LabelDisplayLanguageHelp": "Translating Jellyfin is an ongoing project.", @@ -678,6 +680,7 @@ "LabelLibraryPageSize": "Library page size:", "LabelLibraryPageSizeHelp": "Sets the amount of items to show on a library page. Set to 0 in order to disable paging.", "LabelLineup": "Lineup:", + "LabelLocalCustomCss": "Custom CSS styling which applies to this client only. You may want to disable server custom CSS.", "LabelLocalHttpServerPortNumber": "Local HTTP port number:", "LabelLocalHttpServerPortNumberHelp": "The TCP port number for the HTTP server.", "LabelLockItemToPreventChanges": "Lock this item to prevent future changes", From b8f8633ffd1345173e2222ae14126ef4865a774b Mon Sep 17 00:00:00 2001 From: Ian Walton Date: Sat, 24 Apr 2021 15:23:02 -0400 Subject: [PATCH 2/4] Appease SonarCloud Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/settings/userSettings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 417571d9af..762b896380 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -265,8 +265,7 @@ export class UserSettings { return this.set('disableCustomCss', val.toString(), false); } - val = this.get('disableCustomCss', false); - return val === 'true'; + return this.get('disableCustomCss', false) === 'true'; } /** From 99c902a1a11a62e0a42f113fba36f054c4d84d99 Mon Sep 17 00:00:00 2001 From: Ian Walton Date: Sat, 24 Apr 2021 15:49:49 -0400 Subject: [PATCH 3/4] Enable custom css on login pages until a user with it disabled logs in. --- src/scripts/site.js | 58 +++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/scripts/site.js b/src/scripts/site.js index 550c1283aa..cbc80389c2 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -217,30 +217,42 @@ function onAppReady() { } } + let cssHasLoadedTrigger; + const cssHasLoadedPromise = new Promise(resolve => { + cssHasLoadedTrigger = resolve; + }); + + const apiClient = ServerConnections.currentApiClient(); + if (apiClient) { + fetch(apiClient.getUrl('Branding/Css')) + .then(function(response) { + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + return response.text(); + }) + .then(function(css) { + let style = document.querySelector('#cssBranding'); + if (!style) { + // Inject the branding css as a dom element in body so it will take + // precedence over other stylesheets + style = document.createElement('style'); + style.id = 'cssBranding'; + document.body.appendChild(style); + } + style.textContent = css; + cssHasLoadedTrigger(style); + }) + .catch(function(err) { + console.warn('Error applying custom css', err); + }); + } + currentSettings.userIsSet().then(() => { - const apiClient = ServerConnections.currentApiClient(); - if (apiClient && !currentSettings.disableCustomCss()) { - fetch(apiClient.getUrl('Branding/Css')) - .then(function(response) { - if (!response.ok) { - throw new Error(response.status + ' ' + response.statusText); - } - return response.text(); - }) - .then(function(css) { - let style = document.querySelector('#cssBranding'); - if (!style) { - // Inject the branding css as a dom element in body so it will take - // precedence over other stylesheets - style = document.createElement('style'); - style.id = 'cssBranding'; - document.body.appendChild(style); - } - style.textContent = css; - }) - .catch(function(err) { - console.warn('Error applying custom css', err); - }); + if (currentSettings.disableCustomCss()) { + cssHasLoadedPromise.then(style => { + style.textContent = ''; + }); } const localCss = currentSettings.customCss(); From 8d59d2a0febcb1217eb638b400e48841d4031d56 Mon Sep 17 00:00:00 2001 From: Ian Walton Date: Sat, 24 Apr 2021 16:42:21 -0400 Subject: [PATCH 4/4] Apply styles using user events and also immediately when changed. --- src/scripts/settings/userSettings.js | 16 ----- src/scripts/site.js | 87 +++++++++++++++------------- 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 762b896380..fc37907854 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -21,9 +21,6 @@ const defaultSubtitleAppearanceSettings = { export class UserSettings { constructor() { - this._userSetPromise = new Promise(resolve => { - this._userSetPromiseResolve = resolve; - }); } /** @@ -39,11 +36,6 @@ export class UserSettings { this.currentUserId = userId; this.currentApiClient = apiClient; - if (this._userSetPromiseResolve) { - this._userSetPromiseResolve(); - this._userSetPromiseResolve = null; - } - if (!userId) { this.displayPrefs = null; return Promise.resolve(); @@ -67,14 +59,6 @@ export class UserSettings { this.displayPrefs = instance.getData(); } - /** - * Allows waiting until the user id is set. - * @returns {Promise} - */ - userIsSet() { - return this._userSetPromise; - } - // FIXME: 'appSettings.set' doesn't return any value /** * Set value of setting. diff --git a/src/scripts/site.js b/src/scripts/site.js index cbc80389c2..83eadbf229 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -176,7 +176,7 @@ function initSyncPlay() { Events.on(ServerConnections, 'apiclientcreated', (e, newApiClient) => SyncPlay.Manager.init(newApiClient)); } -function onAppReady() { +async function onAppReady() { console.debug('begin onAppReady'); console.debug('onAppReady: loading dependencies'); @@ -217,57 +217,66 @@ function onAppReady() { } } - let cssHasLoadedTrigger; - const cssHasLoadedPromise = new Promise(resolve => { - cssHasLoadedTrigger = resolve; - }); - const apiClient = ServerConnections.currentApiClient(); if (apiClient) { - fetch(apiClient.getUrl('Branding/Css')) + const updateStyle = (css) => { + let style = document.querySelector('#cssBranding'); + if (!style) { + // Inject the branding css as a dom element in body so it will take + // precedence over other stylesheets + style = document.createElement('style'); + style.id = 'cssBranding'; + document.body.appendChild(style); + } + style.textContent = css; + }; + + const style = fetch(apiClient.getUrl('Branding/Css')) .then(function(response) { if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); } return response.text(); }) - .then(function(css) { - let style = document.querySelector('#cssBranding'); - if (!style) { - // Inject the branding css as a dom element in body so it will take - // precedence over other stylesheets - style = document.createElement('style'); - style.id = 'cssBranding'; - document.body.appendChild(style); - } - style.textContent = css; - cssHasLoadedTrigger(style); - }) .catch(function(err) { console.warn('Error applying custom css', err); }); - } - currentSettings.userIsSet().then(() => { - if (currentSettings.disableCustomCss()) { - cssHasLoadedPromise.then(style => { - style.textContent = ''; - }); - } - - const localCss = currentSettings.customCss(); - if (localCss) { - let style = document.querySelector('#localCssBranding'); - if (!style) { - // Inject the branding css as a dom element in body so it will take - // precedence over other stylesheets - style = document.createElement('style'); - style.id = 'localCssBranding'; - document.body.appendChild(style); + const handleStyleChange = async () => { + if (currentSettings.disableCustomCss()) { + updateStyle(''); + } else { + updateStyle(await style); } - style.textContent = localCss; - } - }); + + const localCss = currentSettings.customCss(); + let localStyle = document.querySelector('#localCssBranding'); + if (localCss) { + if (!localStyle) { + // Inject the branding css as a dom element in body so it will take + // precedence over other stylesheets + localStyle = document.createElement('style'); + localStyle.id = 'localCssBranding'; + document.body.appendChild(localStyle); + } + localStyle.textContent = localCss; + } else { + if (localStyle) { + localStyle.textContent = ''; + } + } + }; + + Events.on(ServerConnections, 'localusersignedin', handleStyleChange); + Events.on(ServerConnections, 'localusersignedout', handleStyleChange); + Events.on(currentSettings, 'change', (e, prop) => { + if (prop == 'disableCustomCss' || prop == 'customCss') { + handleStyleChange(); + } + }); + + style.then(updateStyle); + } } function registerServiceWorker() {