Skip to content

Commit

Permalink
Backport pull request #5589 from jellyfin-web/release-10.9.z
Browse files Browse the repository at this point in the history
Add support for user themes for mui components

Original-merge: 61976b8

Merged-by: thornbill <thornbill@users.noreply.github.com>

Backported-by: Joshua M. Boniface <joshua@boniface.me>
  • Loading branch information
thornbill authored and joshuaboniface committed May 26, 2024
1 parent 669784b commit 1929ba8
Show file tree
Hide file tree
Showing 16 changed files with 298 additions and 62 deletions.
12 changes: 4 additions & 8 deletions src/RootApp.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import loadable from '@loadable/component';
import { ThemeProvider } from '@mui/material/styles';
import { History } from '@remix-run/router';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import React from 'react';

import { ApiProvider } from 'hooks/useApi';
import { WebConfigProvider } from 'hooks/useWebConfig';
import theme from 'themes/theme';
import { queryClient } from 'utils/query/queryClient';

const StableAppRouter = loadable(() => import('./apps/stable/AppRouter'));
Expand All @@ -21,12 +19,10 @@ const RootApp = ({ history }: Readonly<{ history: History }>) => {
<QueryClientProvider client={queryClient}>
<ApiProvider>
<WebConfigProvider>
<ThemeProvider theme={theme}>
{isExperimentalLayout ?
<RootAppRouter history={history} /> :
<StableAppRouter history={history} />
}
</ThemeProvider>
{isExperimentalLayout ?
<RootAppRouter history={history} /> :
<StableAppRouter history={history} />
}
</WebConfigProvider>
</ApiProvider>
<ReactQueryDevtools initialIsOpen={false} />
Expand Down
5 changes: 3 additions & 2 deletions src/RootAppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AppHeader from 'components/AppHeader';
import Backdrop from 'components/Backdrop';
import { useLegacyRouterSync } from 'hooks/useLegacyRouterSync';
import { DASHBOARD_APP_ROUTES } from 'apps/dashboard/routes/routes';
import UserThemeProvider from 'themes/UserThemeProvider';

const router = createHashRouter([
{
Expand All @@ -35,11 +36,11 @@ export default function RootAppRouter({ history }: Readonly<{ history: History}>
*/
function RootAppLayout() {
return (
<>
<UserThemeProvider>
<Backdrop />
<AppHeader isHidden />

<Outlet />
</>
</UserThemeProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import React, { Fragment } from 'react';

import { appHost } from 'components/apphost';
import { useApi } from 'hooks/useApi';
import { useThemes } from 'hooks/useThemes';
import globalize from 'scripts/globalize';

import { DisplaySettingsValues } from './types';
import { useScreensavers } from './hooks/useScreensavers';
import { useServerThemes } from './hooks/useServerThemes';

interface DisplayPreferencesProps {
onChange: (event: SelectChangeEvent | React.SyntheticEvent) => void;
Expand All @@ -25,7 +26,7 @@ interface DisplayPreferencesProps {
export function DisplayPreferences({ onChange, values }: Readonly<DisplayPreferencesProps>) {
const { user } = useApi();
const { screensavers } = useScreensavers();
const { themes } = useServerThemes();
const { themes } = useThemes();

return (
<Stack spacing={3}>
Expand Down
32 changes: 0 additions & 32 deletions src/apps/experimental/routes/user/display/hooks/useServerThemes.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/apps/stable/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { STABLE_APP_ROUTES } from './routes/routes';
import Backdrop from 'components/Backdrop';
import AppHeader from 'components/AppHeader';
import { DASHBOARD_APP_PATHS, DASHBOARD_APP_ROUTES } from 'apps/dashboard/routes/routes';
import UserThemeProvider from 'themes/UserThemeProvider';

const router = createHashRouter([{
element: <StableAppLayout />,
Expand All @@ -32,11 +33,11 @@ function StableAppLayout() {
.some(path => location.pathname.startsWith(`/${path}`));

return (
<>
<UserThemeProvider>
<Backdrop />
<AppHeader isHidden={isNewLayoutPath} />

<Outlet />
</>
</UserThemeProvider>
);
}
16 changes: 16 additions & 0 deletions src/hooks/useThemes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useMemo } from 'react';

import { useWebConfig } from './useWebConfig';

export function useThemes() {
const { themes } = useWebConfig();

const defaultTheme = useMemo(() => {
return themes?.find(theme => theme.default);
}, [ themes ]);

return {
themes: themes || [],
defaultTheme
};
}
57 changes: 57 additions & 0 deletions src/hooks/useUserTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useCallback, useEffect, useState } from 'react';

import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import Events from 'utils/events';

import { useApi } from './useApi';
import { useThemes } from './useThemes';

const THEME_FIELD_NAMES = [ 'appTheme', 'dashboardTheme' ];

export function useUserTheme() {
const [ theme, setTheme ] = useState<string>();
const [ dashboardTheme, setDashboardTheme ] = useState<string>();

const { user } = useApi();
const { defaultTheme } = useThemes();

useEffect(() => {
if (defaultTheme) {
if (!theme) setTheme(defaultTheme.id);
if (!dashboardTheme) setDashboardTheme(defaultTheme.id);
}
}, [ dashboardTheme, defaultTheme, theme ]);

// Update the current themes with values from user settings
const updateThemesFromSettings = useCallback(() => {
const userTheme = userSettings.theme();
if (userTheme) setTheme(userTheme);
const userDashboardTheme = userSettings.dashboardTheme();
if (userDashboardTheme) setDashboardTheme(userDashboardTheme);
}, []);

const onUserSettingsChange = useCallback((_e, name?: string) => {
if (name && THEME_FIELD_NAMES.includes(name)) {
updateThemesFromSettings();
}
}, [ updateThemesFromSettings ]);

// Handle user settings changes
useEffect(() => {
Events.on(userSettings, 'change', onUserSettingsChange);

return () => {
Events.off(userSettings, 'change', onUserSettingsChange);
};
}, [ onUserSettingsChange ]);

// Update the theme if the user changes
useEffect(() => {
updateThemesFromSettings();
}, [ updateThemesFromSettings, user ]);

return {
theme,
dashboardTheme
};
}
43 changes: 43 additions & 0 deletions src/themes/UserThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ThemeProvider } from '@mui/material';
import React, { type FC, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';

import { DASHBOARD_APP_PATHS } from 'apps/dashboard/routes/routes';
import { useUserTheme } from 'hooks/useUserTheme';

import { DEFAULT_THEME, getTheme } from './themes';

const isDashboardThemePage = (pathname: string) => [
// NOTE: The metadata manager doesn't seem to use the dashboard theme
DASHBOARD_APP_PATHS.Dashboard,
DASHBOARD_APP_PATHS.PluginConfig
].some(path => pathname.startsWith(`/${path}`));

const UserThemeProvider: FC = ({ children }) => {
const [ isDashboard, setIsDashboard ] = useState(false);
const [ muiTheme, setMuiTheme ] = useState(DEFAULT_THEME);

const location = useLocation();
const { theme, dashboardTheme } = useUserTheme();

// Check if we are on a dashboard page when the path changes
useEffect(() => {
setIsDashboard(isDashboardThemePage(location.pathname));
}, [ location.pathname ]);

useEffect(() => {
if (isDashboard) {
setMuiTheme(getTheme(dashboardTheme));
} else {
setMuiTheme(getTheme(theme));
}
}, [ dashboardTheme, isDashboard, theme ]);

return (
<ThemeProvider theme={muiTheme}>
{children}
</ThemeProvider>
);
};

export default UserThemeProvider;
27 changes: 27 additions & 0 deletions src/themes/appletv/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';

import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';

const themeOptions: ThemeOptions = {
palette: {
mode: 'light',
background: {
default: '#d5e9f2',
paper: '#fff'
}
},
components: {
MuiAppBar: {
styleOverrides: {
colorPrimary: {
backgroundColor: '#bcbcbc'
}
}
}
}
};

const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, themeOptions));

export default theme;
16 changes: 16 additions & 0 deletions src/themes/blueradiance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';

import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';

const options: ThemeOptions = {
palette: {
background: {
paper: '#011432'
}
}
};

const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, options));

export default theme;
7 changes: 7 additions & 0 deletions src/themes/dark/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import createTheme from '@mui/material/styles/createTheme';

import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';

const theme = createTheme(DEFAULT_THEME_OPTIONS);

export default theme;
19 changes: 3 additions & 16 deletions src/themes/theme.ts → src/themes/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import { createTheme } from '@mui/material/styles';

declare module '@mui/material/styles' {
interface Palette {
starIcon: Palette['primary'];
}

interface PaletteOptions {
starIcon?: PaletteOptions['primary'];
}
}
import type { ThemeOptions } from '@mui/material/styles/createTheme';

const LIST_ICON_WIDTH = 36;

/** The default Jellyfin app theme for mui */
const theme = createTheme({
export const DEFAULT_THEME_OPTIONS: ThemeOptions = {
palette: {
mode: 'dark',
primary: {
Expand Down Expand Up @@ -109,6 +98,4 @@ const theme = createTheme({
}
}
}
});

export default theme;
};
30 changes: 30 additions & 0 deletions src/themes/light/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';

import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';

const options: ThemeOptions = {
palette: {
mode: 'light',
background: {
default: '#f2f2f2',
// NOTE: The original theme uses #303030 for the drawer and app bar but we would need the drawer to use
// dark mode for a color that dark to work properly which would require a separate ThemeProvider just for
// the drawer... which is not worth the trouble in my opinion
paper: '#e8e8e8'
}
},
components: {
MuiAppBar: {
styleOverrides: {
colorPrimary: {
backgroundColor: '#e8e8e8'
}
}
}
}
};

const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, options));

export default theme;
22 changes: 22 additions & 0 deletions src/themes/purplehaze/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';

import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';

const options: ThemeOptions = {
palette: {
background: {
paper: '#000420'
},
primary: {
main: '#48c3c8'
},
secondary: {
main: '#ff77f1'
}
}
};

const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, options));

export default theme;
Loading

0 comments on commit 1929ba8

Please sign in to comment.