1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #2618 from iwalton3/client-custom-css

Allow client custom css and disabling global server custom css.
This commit is contained in:
Bill Thornton 2021-07-30 22:34:32 -07:00 committed by GitHub
commit c4fc8e0145
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 14 deletions

View file

@ -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';
@ -124,6 +125,9 @@ import template from './displaySettings.template.html';
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume(); context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume();
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() || '';
@ -159,6 +163,9 @@ import template from './displaySettings.template.html';
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked); userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').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());
} }

View file

@ -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>

View file

@ -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';
} }

View file

@ -239,6 +239,32 @@ 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);
}
return this.get('disableCustomCss', false) === '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.
@ -526,3 +552,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);

View file

@ -36,6 +36,7 @@ import { playbackManager } from '../components/playback/playbackmanager';
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) {
@ -173,7 +174,7 @@ function initSyncPlay() {
Events.on(ServerConnections, 'apiclientcreated', (e, newApiClient) => SyncPlay.Manager.init(newApiClient)); Events.on(ServerConnections, 'apiclientcreated', (e, newApiClient) => SyncPlay.Manager.init(newApiClient));
} }
function onAppReady() { async function onAppReady() {
console.debug('begin onAppReady'); console.debug('begin onAppReady');
console.debug('onAppReady: loading dependencies'); console.debug('onAppReady: loading dependencies');
@ -216,27 +217,63 @@ function onAppReady() {
const apiClient = ServerConnections.currentApiClient(); const apiClient = ServerConnections.currentApiClient();
if (apiClient) { 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) { .then(function(response) {
if (!response.ok) { if (!response.ok) {
throw new Error(response.status + ' ' + response.statusText); throw new Error(response.status + ' ' + response.statusText);
} }
return response.text(); 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) { .catch(function(err) {
console.warn('Error applying custom css', err); console.warn('Error applying custom css', err);
}); });
const handleStyleChange = async () => {
if (currentSettings.disableCustomCss()) {
updateStyle('');
} else {
updateStyle(await style);
}
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);
} }
} }

View file

@ -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",
@ -578,6 +579,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.",
@ -679,6 +681,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",