diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx index 16c81771cc..44b8ad2caf 100644 --- a/src/apps/dashboard/AppLayout.tsx +++ b/src/apps/dashboard/AppLayout.tsx @@ -11,6 +11,7 @@ import AppBody from 'components/AppBody'; import AppToolbar from 'components/toolbar/AppToolbar'; import ElevationScroll from 'components/ElevationScroll'; import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; +import ThemeCss from 'components/ThemeCss'; import { useApi } from 'hooks/useApi'; import { useLocale } from 'hooks/useLocale'; @@ -101,6 +102,7 @@ export const Component: FC = () => { + ); }; diff --git a/src/apps/experimental/AppLayout.tsx b/src/apps/experimental/AppLayout.tsx index 167a96eaff..6412115302 100644 --- a/src/apps/experimental/AppLayout.tsx +++ b/src/apps/experimental/AppLayout.tsx @@ -6,8 +6,10 @@ import useMediaQuery from '@mui/material/useMediaQuery'; import { Outlet, useLocation } from 'react-router-dom'; import AppBody from 'components/AppBody'; +import CustomCss from 'components/CustomCss'; import ElevationScroll from 'components/ElevationScroll'; import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; +import ThemeCss from 'components/ThemeCss'; import { useApi } from 'hooks/useApi'; import AppToolbar from './components/AppToolbar'; @@ -29,52 +31,56 @@ export const Component = () => { }, [ isDrawerActive, setIsDrawerActive ]); return ( - - - - - - - + <> + + + + + + + - { - isDrawerAvailable && ( - - ) - } - + { + isDrawerAvailable && ( + + ) + } + - - - - + + + + + - + + + ); }; diff --git a/src/apps/stable/AppLayout.tsx b/src/apps/stable/AppLayout.tsx index f3b186c8db..1e653ee1a8 100644 --- a/src/apps/stable/AppLayout.tsx +++ b/src/apps/stable/AppLayout.tsx @@ -2,11 +2,17 @@ import React from 'react'; import { Outlet } from 'react-router-dom'; import AppBody from 'components/AppBody'; +import ThemeCss from 'components/ThemeCss'; +import CustomCss from 'components/CustomCss'; export default function AppLayout() { return ( - - - + <> + + + + + + ); } diff --git a/src/components/CustomCss.tsx b/src/components/CustomCss.tsx new file mode 100644 index 0000000000..b9c60b48e6 --- /dev/null +++ b/src/components/CustomCss.tsx @@ -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(); + + useEffect(() => { + if (!api) return; + + setBrandingCssUrl(api.getUri('/Branding/Css.css')); + }, [ api ]); + + if (!api) return null; + + return ( + <> + {!disableCustomCss && brandingCssUrl && ( + + )} + {userCustomCss && ( + + )} + + ); +}; + +export default CustomCss; diff --git a/src/components/ThemeCss.tsx b/src/components/ThemeCss.tsx new file mode 100644 index 0000000000..53defc1ddf --- /dev/null +++ b/src/components/ThemeCss.tsx @@ -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 = ({ + 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 ( + + ); +}; + +export default ThemeCss; diff --git a/src/hooks/useUserSettings.tsx b/src/hooks/useUserSettings.tsx index 84eb49117e..289588669e 100644 --- a/src/hooks/useUserSettings.tsx +++ b/src/hooks/useUserSettings.tsx @@ -7,6 +7,8 @@ import Events, { type Event } from 'utils/events'; import { useApi } from './useApi'; interface UserSettings { + customCss?: string + disableCustomCss: boolean theme?: string dashboardTheme?: string dateTimeLocale?: string @@ -15,6 +17,9 @@ interface UserSettings { // NOTE: This is an incomplete list of only the settings that are currently being used const UserSettingField = { + // Custom CSS + CustomCss: 'customCss', + DisableCustomCss: 'disableCustomCss', // Theme settings Theme: 'appTheme', DashboardTheme: 'dashboardTheme', @@ -23,11 +28,15 @@ const UserSettingField = { Language: 'language' }; -const UserSettingsContext = createContext({}); +const UserSettingsContext = createContext({ + disableCustomCss: false +}); export const useUserSettings = () => useContext(UserSettingsContext); export const UserSettingsProvider: FC> = ({ children }) => { + const [ customCss, setCustomCss ] = useState(); + const [ disableCustomCss, setDisableCustomCss ] = useState(false); const [ theme, setTheme ] = useState(); const [ dashboardTheme, setDashboardTheme ] = useState(); const [ dateTimeLocale, setDateTimeLocale ] = useState(); @@ -36,14 +45,25 @@ export const UserSettingsProvider: FC> = ({ children const { user } = useApi(); const context = useMemo(() => ({ + customCss, + disableCustomCss, theme, dashboardTheme, dateTimeLocale, locale: language - }), [ theme, dashboardTheme, dateTimeLocale, language ]); + }), [ + customCss, + disableCustomCss, + theme, + dashboardTheme, + dateTimeLocale, + language + ]); // Update the values of the user settings const updateUserSettings = useCallback(() => { + setCustomCss(userSettings.customCss()); + setDisableCustomCss(userSettings.disableCustomCss()); setTheme(userSettings.theme()); setDashboardTheme(userSettings.dashboardTheme()); setDateTimeLocale(userSettings.dateTimeLocale()); diff --git a/src/index.jsx b/src/index.jsx index 169f44857c..81627c4ad8 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -17,7 +17,6 @@ import { loadCoreDictionary } from 'lib/globalize/loader'; import { initialize as initializeAutoCast } from 'scripts/autocast'; import browser from './scripts/browser'; import keyboardNavigation from './scripts/keyboardNavigation'; -import { currentSettings } from './scripts/settings/userSettings'; import { getPlugins } from './scripts/settings/webSettings'; import taskButton from './scripts/taskbutton'; import { pageClassOn, serverAddress } from './utils/dashboard'; @@ -116,9 +115,6 @@ build: ${__JF_BUILD_VERSION__}`); // Load platform specific features loadPlatformFeatures(); - // Load custom CSS styles - loadCustomCss(); - // Enable navigation controls keyboardNavigation.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() { - /* eslint-disable compat/compat */ if (navigator.serviceWorker && window.appMode !== 'cordova' && window.appMode !== 'android') { navigator.serviceWorker.register('serviceworker.js').then(() => console.log('serviceWorker registered') @@ -248,7 +197,6 @@ function registerServiceWorker() { } else { console.warn('serviceWorker unsupported'); } - /* eslint-enable compat/compat */ } async function renderApp() { diff --git a/src/scripts/themeManager.js b/src/scripts/themeManager.js index 70f4810041..f976dba82e 100644 --- a/src/scripts/themeManager.js +++ b/src/scripts/themeManager.js @@ -1,16 +1,7 @@ import { getDefaultTheme, getThemes as getConfiguredThemes } from './settings/webSettings'; -let themeStyleElement = document.querySelector('#cssTheme'); let currentThemeId; -function unloadTheme() { - const elem = themeStyleElement; - if (elem) { - elem.removeAttribute('href'); - currentThemeId = null; - } -} - function getThemes() { return getConfiguredThemes(); } @@ -29,11 +20,7 @@ function getThemeStylesheetInfo(id) { theme = getDefaultTheme(); } - return { - stylesheetPath: 'themes/' + theme.id + '/theme.css', - themeId: theme.id, - color: theme.color - }; + return theme; }); } @@ -45,36 +32,12 @@ function setTheme(id) { } getThemeStylesheetInfo(id).then(function (info) { - if (currentThemeId && currentThemeId === info.themeId) { + if (currentThemeId && currentThemeId === info.id) { resolve(); return; } - const linkUrl = info.stylesheetPath; - 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; + currentThemeId = info.id; document.getElementById('themeColor').content = info.color; }); @@ -82,6 +45,6 @@ function setTheme(id) { } export default { - getThemes: getThemes, - setTheme: setTheme + getThemes, + setTheme };