mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Move theme and custom css to react components
This commit is contained in:
parent
27f4b8a7e5
commit
88b247596a
8 changed files with 159 additions and 143 deletions
|
@ -11,6 +11,7 @@ import AppBody from 'components/AppBody';
|
||||||
import AppToolbar from 'components/toolbar/AppToolbar';
|
import AppToolbar from 'components/toolbar/AppToolbar';
|
||||||
import ElevationScroll from 'components/ElevationScroll';
|
import ElevationScroll from 'components/ElevationScroll';
|
||||||
import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
|
import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
|
||||||
|
import ThemeCss from 'components/ThemeCss';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { useLocale } from 'hooks/useLocale';
|
import { useLocale } from 'hooks/useLocale';
|
||||||
|
|
||||||
|
@ -101,6 +102,7 @@ export const Component: FC = () => {
|
||||||
</AppBody>
|
</AppBody>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
<ThemeCss dashboard />
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,10 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import AppBody from 'components/AppBody';
|
import AppBody from 'components/AppBody';
|
||||||
|
import CustomCss from 'components/CustomCss';
|
||||||
import ElevationScroll from 'components/ElevationScroll';
|
import ElevationScroll from 'components/ElevationScroll';
|
||||||
import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
|
import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
|
||||||
|
import ThemeCss from 'components/ThemeCss';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
|
|
||||||
import AppToolbar from './components/AppToolbar';
|
import AppToolbar from './components/AppToolbar';
|
||||||
|
@ -29,6 +31,7 @@ export const Component = () => {
|
||||||
}, [ isDrawerActive, setIsDrawerActive ]);
|
}, [ isDrawerActive, setIsDrawerActive ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Box sx={{ position: 'relative', display: 'flex', height: '100%' }}>
|
<Box sx={{ position: 'relative', display: 'flex', height: '100%' }}>
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ElevationScroll elevate={false}>
|
<ElevationScroll elevate={false}>
|
||||||
|
@ -76,5 +79,8 @@ export const Component = () => {
|
||||||
</AppBody>
|
</AppBody>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
<ThemeCss />
|
||||||
|
<CustomCss />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,11 +2,17 @@ import React from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
import AppBody from 'components/AppBody';
|
import AppBody from 'components/AppBody';
|
||||||
|
import ThemeCss from 'components/ThemeCss';
|
||||||
|
import CustomCss from 'components/CustomCss';
|
||||||
|
|
||||||
export default function AppLayout() {
|
export default function AppLayout() {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<AppBody>
|
<AppBody>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</AppBody>
|
</AppBody>
|
||||||
|
<ThemeCss />
|
||||||
|
<CustomCss />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
37
src/components/CustomCss.tsx
Normal file
37
src/components/CustomCss.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { useUserSettings } from 'hooks/useUserSettings';
|
||||||
|
|
||||||
|
const CustomCss: FC = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
const { customCss: userCustomCss, disableCustomCss } = useUserSettings();
|
||||||
|
const [ brandingCssUrl, setBrandingCssUrl ] = useState<string>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!api) return;
|
||||||
|
|
||||||
|
setBrandingCssUrl(api.getUri('/Branding/Css.css'));
|
||||||
|
}, [ api ]);
|
||||||
|
|
||||||
|
if (!api) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!disableCustomCss && brandingCssUrl && (
|
||||||
|
<link
|
||||||
|
rel='stylesheet'
|
||||||
|
type='text/css'
|
||||||
|
href={brandingCssUrl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{userCustomCss && (
|
||||||
|
<style>
|
||||||
|
{userCustomCss}
|
||||||
|
</style>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomCss;
|
34
src/components/ThemeCss.tsx
Normal file
34
src/components/ThemeCss.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useUserTheme } from 'hooks/useUserTheme';
|
||||||
|
import { getDefaultTheme } from 'scripts/settings/webSettings';
|
||||||
|
|
||||||
|
interface ThemeCssProps {
|
||||||
|
dashboard?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const getThemeUrl = (id: string) => `themes/${id}/theme.css`;;
|
||||||
|
|
||||||
|
const DEFAULT_THEME_URL = getThemeUrl(getDefaultTheme().id);
|
||||||
|
|
||||||
|
const ThemeCss: FC<ThemeCssProps> = ({
|
||||||
|
dashboard = false
|
||||||
|
}) => {
|
||||||
|
const { theme, dashboardTheme } = useUserTheme();
|
||||||
|
const [ themeUrl, setThemeUrl ] = useState(DEFAULT_THEME_URL);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const id = dashboard ? dashboardTheme : theme;
|
||||||
|
if (id) setThemeUrl(getThemeUrl(id));
|
||||||
|
}, [dashboard, dashboardTheme, theme]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<link
|
||||||
|
rel='stylesheet'
|
||||||
|
type='text/css'
|
||||||
|
href={themeUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeCss;
|
|
@ -7,6 +7,8 @@ import Events, { type Event } from 'utils/events';
|
||||||
import { useApi } from './useApi';
|
import { useApi } from './useApi';
|
||||||
|
|
||||||
interface UserSettings {
|
interface UserSettings {
|
||||||
|
customCss?: string
|
||||||
|
disableCustomCss: boolean
|
||||||
theme?: string
|
theme?: string
|
||||||
dashboardTheme?: string
|
dashboardTheme?: string
|
||||||
dateTimeLocale?: string
|
dateTimeLocale?: string
|
||||||
|
@ -15,6 +17,9 @@ interface UserSettings {
|
||||||
|
|
||||||
// NOTE: This is an incomplete list of only the settings that are currently being used
|
// NOTE: This is an incomplete list of only the settings that are currently being used
|
||||||
const UserSettingField = {
|
const UserSettingField = {
|
||||||
|
// Custom CSS
|
||||||
|
CustomCss: 'customCss',
|
||||||
|
DisableCustomCss: 'disableCustomCss',
|
||||||
// Theme settings
|
// Theme settings
|
||||||
Theme: 'appTheme',
|
Theme: 'appTheme',
|
||||||
DashboardTheme: 'dashboardTheme',
|
DashboardTheme: 'dashboardTheme',
|
||||||
|
@ -23,11 +28,15 @@ const UserSettingField = {
|
||||||
Language: 'language'
|
Language: 'language'
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserSettingsContext = createContext<UserSettings>({});
|
const UserSettingsContext = createContext<UserSettings>({
|
||||||
|
disableCustomCss: false
|
||||||
|
});
|
||||||
|
|
||||||
export const useUserSettings = () => useContext(UserSettingsContext);
|
export const useUserSettings = () => useContext(UserSettingsContext);
|
||||||
|
|
||||||
export const UserSettingsProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
export const UserSettingsProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
|
const [ customCss, setCustomCss ] = useState<string>();
|
||||||
|
const [ disableCustomCss, setDisableCustomCss ] = useState(false);
|
||||||
const [ theme, setTheme ] = useState<string>();
|
const [ theme, setTheme ] = useState<string>();
|
||||||
const [ dashboardTheme, setDashboardTheme ] = useState<string>();
|
const [ dashboardTheme, setDashboardTheme ] = useState<string>();
|
||||||
const [ dateTimeLocale, setDateTimeLocale ] = useState<string>();
|
const [ dateTimeLocale, setDateTimeLocale ] = useState<string>();
|
||||||
|
@ -36,14 +45,25 @@ export const UserSettingsProvider: FC<PropsWithChildren<unknown>> = ({ children
|
||||||
const { user } = useApi();
|
const { user } = useApi();
|
||||||
|
|
||||||
const context = useMemo<UserSettings>(() => ({
|
const context = useMemo<UserSettings>(() => ({
|
||||||
|
customCss,
|
||||||
|
disableCustomCss,
|
||||||
theme,
|
theme,
|
||||||
dashboardTheme,
|
dashboardTheme,
|
||||||
dateTimeLocale,
|
dateTimeLocale,
|
||||||
locale: language
|
locale: language
|
||||||
}), [ theme, dashboardTheme, dateTimeLocale, language ]);
|
}), [
|
||||||
|
customCss,
|
||||||
|
disableCustomCss,
|
||||||
|
theme,
|
||||||
|
dashboardTheme,
|
||||||
|
dateTimeLocale,
|
||||||
|
language
|
||||||
|
]);
|
||||||
|
|
||||||
// Update the values of the user settings
|
// Update the values of the user settings
|
||||||
const updateUserSettings = useCallback(() => {
|
const updateUserSettings = useCallback(() => {
|
||||||
|
setCustomCss(userSettings.customCss());
|
||||||
|
setDisableCustomCss(userSettings.disableCustomCss());
|
||||||
setTheme(userSettings.theme());
|
setTheme(userSettings.theme());
|
||||||
setDashboardTheme(userSettings.dashboardTheme());
|
setDashboardTheme(userSettings.dashboardTheme());
|
||||||
setDateTimeLocale(userSettings.dateTimeLocale());
|
setDateTimeLocale(userSettings.dateTimeLocale());
|
||||||
|
|
|
@ -17,7 +17,6 @@ import { loadCoreDictionary } from 'lib/globalize/loader';
|
||||||
import { initialize as initializeAutoCast } from 'scripts/autocast';
|
import { initialize as initializeAutoCast } from 'scripts/autocast';
|
||||||
import browser from './scripts/browser';
|
import browser from './scripts/browser';
|
||||||
import keyboardNavigation from './scripts/keyboardNavigation';
|
import keyboardNavigation from './scripts/keyboardNavigation';
|
||||||
import { currentSettings } from './scripts/settings/userSettings';
|
|
||||||
import { getPlugins } from './scripts/settings/webSettings';
|
import { getPlugins } from './scripts/settings/webSettings';
|
||||||
import taskButton from './scripts/taskbutton';
|
import taskButton from './scripts/taskbutton';
|
||||||
import { pageClassOn, serverAddress } from './utils/dashboard';
|
import { pageClassOn, serverAddress } from './utils/dashboard';
|
||||||
|
@ -116,9 +115,6 @@ build: ${__JF_BUILD_VERSION__}`);
|
||||||
// Load platform specific features
|
// Load platform specific features
|
||||||
loadPlatformFeatures();
|
loadPlatformFeatures();
|
||||||
|
|
||||||
// Load custom CSS styles
|
|
||||||
loadCustomCss();
|
|
||||||
|
|
||||||
// Enable navigation controls
|
// Enable navigation controls
|
||||||
keyboardNavigation.enable();
|
keyboardNavigation.enable();
|
||||||
autoFocuser.enable();
|
autoFocuser.enable();
|
||||||
|
@ -191,54 +187,7 @@ function loadPlatformFeatures() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadCustomCss() {
|
|
||||||
// Apply custom CSS
|
|
||||||
const apiClient = ServerConnections.currentApiClient();
|
|
||||||
if (apiClient) {
|
|
||||||
const brandingCss = fetch(apiClient.getUrl('Branding/Css'))
|
|
||||||
.then(function(response) {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(response.status + ' ' + response.statusText);
|
|
||||||
}
|
|
||||||
return response.text();
|
|
||||||
})
|
|
||||||
.catch(function(err) {
|
|
||||||
console.warn('Error applying custom css', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleStyleChange = async () => {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
const css = [];
|
|
||||||
// Only add branding CSS when enabled
|
|
||||||
if (!currentSettings.disableCustomCss()) css.push(await brandingCss);
|
|
||||||
// Always add user CSS
|
|
||||||
css.push(currentSettings.customCss());
|
|
||||||
|
|
||||||
style.textContent = css.join('\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
Events.on(ServerConnections, 'localusersignedin', handleStyleChange);
|
|
||||||
Events.on(ServerConnections, 'localusersignedout', handleStyleChange);
|
|
||||||
Events.on(currentSettings, 'change', (e, prop) => {
|
|
||||||
if (prop == 'disableCustomCss' || prop == 'customCss') {
|
|
||||||
handleStyleChange();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
handleStyleChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerServiceWorker() {
|
function registerServiceWorker() {
|
||||||
/* eslint-disable compat/compat */
|
|
||||||
if (navigator.serviceWorker && window.appMode !== 'cordova' && window.appMode !== 'android') {
|
if (navigator.serviceWorker && window.appMode !== 'cordova' && window.appMode !== 'android') {
|
||||||
navigator.serviceWorker.register('serviceworker.js').then(() =>
|
navigator.serviceWorker.register('serviceworker.js').then(() =>
|
||||||
console.log('serviceWorker registered')
|
console.log('serviceWorker registered')
|
||||||
|
@ -248,7 +197,6 @@ function registerServiceWorker() {
|
||||||
} else {
|
} else {
|
||||||
console.warn('serviceWorker unsupported');
|
console.warn('serviceWorker unsupported');
|
||||||
}
|
}
|
||||||
/* eslint-enable compat/compat */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderApp() {
|
async function renderApp() {
|
||||||
|
|
|
@ -1,16 +1,7 @@
|
||||||
import { getDefaultTheme, getThemes as getConfiguredThemes } from './settings/webSettings';
|
import { getDefaultTheme, getThemes as getConfiguredThemes } from './settings/webSettings';
|
||||||
|
|
||||||
let themeStyleElement = document.querySelector('#cssTheme');
|
|
||||||
let currentThemeId;
|
let currentThemeId;
|
||||||
|
|
||||||
function unloadTheme() {
|
|
||||||
const elem = themeStyleElement;
|
|
||||||
if (elem) {
|
|
||||||
elem.removeAttribute('href');
|
|
||||||
currentThemeId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getThemes() {
|
function getThemes() {
|
||||||
return getConfiguredThemes();
|
return getConfiguredThemes();
|
||||||
}
|
}
|
||||||
|
@ -29,11 +20,7 @@ function getThemeStylesheetInfo(id) {
|
||||||
theme = getDefaultTheme();
|
theme = getDefaultTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return theme;
|
||||||
stylesheetPath: 'themes/' + theme.id + '/theme.css',
|
|
||||||
themeId: theme.id,
|
|
||||||
color: theme.color
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,36 +32,12 @@ function setTheme(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getThemeStylesheetInfo(id).then(function (info) {
|
getThemeStylesheetInfo(id).then(function (info) {
|
||||||
if (currentThemeId && currentThemeId === info.themeId) {
|
if (currentThemeId && currentThemeId === info.id) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkUrl = info.stylesheetPath;
|
currentThemeId = info.id;
|
||||||
unloadTheme();
|
|
||||||
|
|
||||||
let link = themeStyleElement;
|
|
||||||
|
|
||||||
if (!link) {
|
|
||||||
// Inject the theme css as a dom element in body so it will take
|
|
||||||
// precedence over other stylesheets
|
|
||||||
link = document.createElement('link');
|
|
||||||
link.id = 'cssTheme';
|
|
||||||
link.setAttribute('rel', 'stylesheet');
|
|
||||||
link.setAttribute('type', 'text/css');
|
|
||||||
document.body.appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLoad = function (e) {
|
|
||||||
e.target.removeEventListener('load', onLoad);
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
link.addEventListener('load', onLoad);
|
|
||||||
|
|
||||||
link.setAttribute('href', linkUrl);
|
|
||||||
themeStyleElement = link;
|
|
||||||
currentThemeId = info.themeId;
|
|
||||||
|
|
||||||
document.getElementById('themeColor').content = info.color;
|
document.getElementById('themeColor').content = info.color;
|
||||||
});
|
});
|
||||||
|
@ -82,6 +45,6 @@ function setTheme(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getThemes: getThemes,
|
getThemes,
|
||||||
setTheme: setTheme
|
setTheme
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue