mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Allow client custom css and disabling global server custom css.
This commit is contained in:
parent
1794ab53fe
commit
8cae67d9b9
6 changed files with 108 additions and 25 deletions
|
@ -11,6 +11,7 @@ import { Events } from 'jellyfin-apiclient';
|
||||||
import '../../elements/emby-select/emby-select';
|
import '../../elements/emby-select/emby-select';
|
||||||
import '../../elements/emby-checkbox/emby-checkbox';
|
import '../../elements/emby-checkbox/emby-checkbox';
|
||||||
import '../../elements/emby-button/emby-button';
|
import '../../elements/emby-button/emby-button';
|
||||||
|
import '../../elements/emby-textarea/emby-textarea';
|
||||||
import ServerConnections from '../ServerConnections';
|
import ServerConnections from '../ServerConnections';
|
||||||
import toast from '../toast/toast';
|
import toast from '../toast/toast';
|
||||||
import template from './displaySettings.template.html';
|
import template from './displaySettings.template.html';
|
||||||
|
@ -123,6 +124,9 @@ import template from './displaySettings.template.html';
|
||||||
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
|
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
|
||||||
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
|
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('#selectLanguage').value = userSettings.language() || '';
|
||||||
context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || '';
|
context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || '';
|
||||||
|
|
||||||
|
@ -157,6 +161,9 @@ import template from './displaySettings.template.html';
|
||||||
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
|
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
|
||||||
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
|
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
|
||||||
|
|
||||||
|
userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked);
|
||||||
|
userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value);
|
||||||
|
|
||||||
if (user.Id === apiClient.getCurrentUserId()) {
|
if (user.Id === apiClient.getCurrentUserId()) {
|
||||||
skinManager.setTheme(userSettingsInstance.theme());
|
skinManager.setTheme(userSettingsInstance.theme());
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,19 @@
|
||||||
<select id="selectTheme" is="emby-select" label="${LabelTheme}"></select>
|
<select id="selectTheme" is="emby-select" label="${LabelTheme}"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" id="chkDisableCustomCss" />
|
||||||
|
<span>${DisableCustomCss}</span>
|
||||||
|
</label>
|
||||||
|
<div class="fieldDescription checkboxFieldDescription">${LabelDisableCustomCss}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inputContainer customCssContainer">
|
||||||
|
<textarea is="emby-textarea" id="txtLocalCustomCss" label="${LabelCustomCss}" class="textarea-mono"></textarea>
|
||||||
|
<div class="fieldDescription">${LabelLocalCustomCss}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="selectContainer selectDashboardThemeContainer hide">
|
<div class="selectContainer selectDashboardThemeContainer hide">
|
||||||
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
|
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,7 +56,6 @@ import '../emby-input/emby-input';
|
||||||
textarea.style.height = 'auto';
|
textarea.style.height = 'auto';
|
||||||
newHeight = textarea.scrollHeight/* - offset*/;
|
newHeight = textarea.scrollHeight/* - offset*/;
|
||||||
}
|
}
|
||||||
$('.customCssContainer').css('height', newHeight + 'px');
|
|
||||||
textarea.style.height = newHeight + 'px';
|
textarea.style.height = newHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,9 @@ const defaultSubtitleAppearanceSettings = {
|
||||||
|
|
||||||
export class UserSettings {
|
export class UserSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this._userSetPromise = new Promise(resolve => {
|
||||||
|
this._userSetPromiseResolve = resolve;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,6 +39,11 @@ export class UserSettings {
|
||||||
this.currentUserId = userId;
|
this.currentUserId = userId;
|
||||||
this.currentApiClient = apiClient;
|
this.currentApiClient = apiClient;
|
||||||
|
|
||||||
|
if (this._userSetPromiseResolve) {
|
||||||
|
this._userSetPromiseResolve();
|
||||||
|
this._userSetPromiseResolve = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
this.displayPrefs = null;
|
this.displayPrefs = null;
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -59,6 +67,14 @@ export class UserSettings {
|
||||||
this.displayPrefs = instance.getData();
|
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
|
// FIXME: 'appSettings.set' doesn't return any value
|
||||||
/**
|
/**
|
||||||
* Set value of setting.
|
* Set value of setting.
|
||||||
|
@ -239,6 +255,33 @@ export class UserSettings {
|
||||||
return val === 'true';
|
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.
|
* Get or set 'Details Banner' state.
|
||||||
* @param {boolean|undefined} val - Flag to enable 'Details Banner' or undefined.
|
* @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 setSubtitleAppearanceSettings = currentSettings.setSubtitleAppearanceSettings.bind(currentSettings);
|
||||||
export const setFilter = currentSettings.setFilter.bind(currentSettings);
|
export const setFilter = currentSettings.setFilter.bind(currentSettings);
|
||||||
export const getFilter = currentSettings.getFilter.bind(currentSettings);
|
export const getFilter = currentSettings.getFilter.bind(currentSettings);
|
||||||
|
export const customCss = currentSettings.customCss.bind(currentSettings);
|
||||||
|
export const disableCustomCss = currentSettings.disableCustomCss.bind(currentSettings);
|
||||||
|
|
|
@ -37,6 +37,7 @@ import SyncPlayToasts from '../components/syncPlay/ui/syncPlayToasts';
|
||||||
import SyncPlayNoActivePlayer from '../components/syncPlay/ui/players/NoActivePlayer';
|
import SyncPlayNoActivePlayer from '../components/syncPlay/ui/players/NoActivePlayer';
|
||||||
import SyncPlayHtmlVideoPlayer from '../components/syncPlay/ui/players/HtmlVideoPlayer';
|
import SyncPlayHtmlVideoPlayer from '../components/syncPlay/ui/players/HtmlVideoPlayer';
|
||||||
import SyncPlayHtmlAudioPlayer from '../components/syncPlay/ui/players/HtmlAudioPlayer';
|
import SyncPlayHtmlAudioPlayer from '../components/syncPlay/ui/players/HtmlAudioPlayer';
|
||||||
|
import { currentSettings } from './settings/userSettings';
|
||||||
|
|
||||||
// TODO: Move this elsewhere
|
// TODO: Move this elsewhere
|
||||||
window.getWindowLocationSearch = function(win) {
|
window.getWindowLocationSearch = function(win) {
|
||||||
|
@ -216,30 +217,45 @@ function onAppReady() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiClient = ServerConnections.currentApiClient();
|
currentSettings.userIsSet().then(() => {
|
||||||
if (apiClient) {
|
const apiClient = ServerConnections.currentApiClient();
|
||||||
fetch(apiClient.getUrl('Branding/Css'))
|
if (apiClient && !currentSettings.disableCustomCss()) {
|
||||||
.then(function(response) {
|
fetch(apiClient.getUrl('Branding/Css'))
|
||||||
if (!response.ok) {
|
.then(function(response) {
|
||||||
throw new Error(response.status + ' ' + response.statusText);
|
if (!response.ok) {
|
||||||
}
|
throw new Error(response.status + ' ' + response.statusText);
|
||||||
return response.text();
|
}
|
||||||
})
|
return response.text();
|
||||||
.then(function(css) {
|
})
|
||||||
let style = document.querySelector('#cssBranding');
|
.then(function(css) {
|
||||||
if (!style) {
|
let style = document.querySelector('#cssBranding');
|
||||||
// Inject the branding css as a dom element in body so it will take
|
if (!style) {
|
||||||
// precedence over other stylesheets
|
// Inject the branding css as a dom element in body so it will take
|
||||||
style = document.createElement('style');
|
// precedence over other stylesheets
|
||||||
style.id = 'cssBranding';
|
style = document.createElement('style');
|
||||||
document.body.appendChild(style);
|
style.id = 'cssBranding';
|
||||||
}
|
document.body.appendChild(style);
|
||||||
style.textContent = css;
|
}
|
||||||
})
|
style.textContent = css;
|
||||||
.catch(function(err) {
|
})
|
||||||
console.warn('Error applying custom css', err);
|
.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() {
|
function registerServiceWorker() {
|
||||||
|
|
|
@ -181,6 +181,7 @@
|
||||||
"DirectStreamHelp2": "Power consumed by direct streaming usually depends on the audio profile. Only the video stream is lossless.",
|
"DirectStreamHelp2": "Power consumed by direct streaming usually depends on the audio profile. Only the video stream is lossless.",
|
||||||
"DirectStreaming": "Direct streaming",
|
"DirectStreaming": "Direct streaming",
|
||||||
"EnablePlugin": "Enable",
|
"EnablePlugin": "Enable",
|
||||||
|
"DisableCustomCss": "Disable Server-Provided Custom CSS",
|
||||||
"DisablePlugin": "Disable",
|
"DisablePlugin": "Disable",
|
||||||
"Disc": "Disc",
|
"Disc": "Disc",
|
||||||
"Disconnect": "Disconnect",
|
"Disconnect": "Disconnect",
|
||||||
|
@ -577,6 +578,7 @@
|
||||||
"LabelDeinterlaceMethod": "Deinterlacing method:",
|
"LabelDeinterlaceMethod": "Deinterlacing method:",
|
||||||
"LabelDeviceDescription": "Device description",
|
"LabelDeviceDescription": "Device description",
|
||||||
"LabelDidlMode": "DIDL mode:",
|
"LabelDidlMode": "DIDL mode:",
|
||||||
|
"LabelDisableCustomCss": "Disable custom CSS theming/branding provided from the server.",
|
||||||
"LabelDiscNumber": "Disc number:",
|
"LabelDiscNumber": "Disc number:",
|
||||||
"LabelDisplayLanguage": "Display language:",
|
"LabelDisplayLanguage": "Display language:",
|
||||||
"LabelDisplayLanguageHelp": "Translating Jellyfin is an ongoing project.",
|
"LabelDisplayLanguageHelp": "Translating Jellyfin is an ongoing project.",
|
||||||
|
@ -678,6 +680,7 @@
|
||||||
"LabelLibraryPageSize": "Library page size:",
|
"LabelLibraryPageSize": "Library page size:",
|
||||||
"LabelLibraryPageSizeHelp": "Sets the amount of items to show on a library page. Set to 0 in order to disable paging.",
|
"LabelLibraryPageSizeHelp": "Sets the amount of items to show on a library page. Set to 0 in order to disable paging.",
|
||||||
"LabelLineup": "Lineup:",
|
"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:",
|
"LabelLocalHttpServerPortNumber": "Local HTTP port number:",
|
||||||
"LabelLocalHttpServerPortNumberHelp": "The TCP port number for the HTTP server.",
|
"LabelLocalHttpServerPortNumberHelp": "The TCP port number for the HTTP server.",
|
||||||
"LabelLockItemToPreventChanges": "Lock this item to prevent future changes",
|
"LabelLockItemToPreventChanges": "Lock this item to prevent future changes",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue