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}
+
+
+
+
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",