Merge pull request #7170 from thornbill/fix-css-theming

This commit is contained in:
Bill Thornton
2025-10-04 14:08:09 -04:00
committed by GitHub
19 changed files with 116 additions and 39 deletions
+2 -2
View File
@@ -16,6 +16,7 @@ import Backdrop from 'components/Backdrop';
import BangRedirect from 'components/router/BangRedirect';
import { createRouterHistory } from 'components/router/routerHistory';
import appTheme from 'themes/themes';
import { ThemeStorageManager } from 'themes/themeStorageManager';
const layoutMode = localStorage.getItem('layout');
const isExperimentalLayout = layoutMode === 'experimental';
@@ -54,8 +55,7 @@ function RootAppLayout() {
<ThemeProvider
theme={appTheme}
defaultMode='dark'
// Disable mui's default saving to local storage
storageManager={null}
storageManager={ThemeStorageManager}
>
<Backdrop />
<AppHeader isHidden={isExperimentalLayout || isNewLayoutPath} />
+1 -1
View File
@@ -7,7 +7,7 @@ import isEqual from 'lodash-es/isEqual';
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { EventType } from 'types/eventType';
import { EventType } from 'constants/eventType';
import Events, { type Event } from 'utils/events';
interface AppTabsParams {
@@ -1,7 +1,8 @@
import Box from '@mui/material/Box/Box';
import Stack from '@mui/material/Stack/Stack';
import type {} from '@mui/material/themeCssVarsAugmentation';
import Typography from '@mui/material/Typography/Typography';
import { type MRT_RowData, type MRT_TableInstance, MaterialReactTable } from 'material-react-table';
import { type MRT_RowData, type MRT_TableInstance, type MRT_TableOptions, MaterialReactTable } from 'material-react-table';
import React from 'react';
import Page, { type PageProps } from 'components/Page';
@@ -12,7 +13,7 @@ interface TablePageProps<T extends MRT_RowData> extends PageProps {
table: MRT_TableInstance<T>
}
export const DEFAULT_TABLE_OPTIONS = {
export const DEFAULT_TABLE_OPTIONS: Partial<MRT_TableOptions<MRT_RowData>> = {
// Enable custom features
enableColumnPinning: true,
enableColumnResizing: true,
+11 -1
View File
@@ -2,9 +2,10 @@ import parseISO from 'date-fns/parseISO';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
import { useTheme } from '@mui/material/styles';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import { type MRT_ColumnDef, useMaterialReactTable } from 'material-react-table';
import { type MRT_ColumnDef, type MRT_Theme, useMaterialReactTable } from 'material-react-table';
import { useSearchParams } from 'react-router-dom';
import DateTimeCell from 'apps/dashboard/components/table/DateTimeCell';
@@ -53,6 +54,8 @@ export const Component = () => {
const { usersById: users, names: userNames, isLoading: isUsersLoading } = useUsersDetails();
const theme = useTheme();
const UserCell = getUserCell(users);
const activityParams = useMemo(() => ({
@@ -156,8 +159,15 @@ export const Component = () => {
}
}, [ activityView, searchParams, setSearchParams ]);
// NOTE: We need to provide a custom theme due to a MRT bug causing the initial theme to always be used
// https://github.com/KevinVandy/material-react-table/issues/1429
const mrtTheme = useMemo<Partial<MRT_Theme>>(() => ({
baseBackgroundColor: theme.palette.background.paper
}), [ theme ]);
const table = useMaterialReactTable({
...DEFAULT_TABLE_OPTIONS,
mrtTheme,
columns,
data: logEntries,
+20 -3
View File
@@ -4,9 +4,10 @@ import Edit from '@mui/icons-material/Edit';
import Box from '@mui/material/Box/Box';
import Button from '@mui/material/Button/Button';
import IconButton from '@mui/material/IconButton';
import { useTheme } from '@mui/material/styles';
import Tooltip from '@mui/material/Tooltip/Tooltip';
import parseISO from 'date-fns/parseISO';
import { type MRT_ColumnDef, useMaterialReactTable } from 'material-react-table';
import { type MRT_ColumnDef, type MRT_Theme, useMaterialReactTable } from 'material-react-table';
import React, { useCallback, useMemo, useState } from 'react';
import DateTimeCell from 'apps/dashboard/components/table/DateTimeCell';
@@ -41,6 +42,7 @@ export const Component = () => {
data?.Items || []
), [ data ]);
const { usersById: users, names: userNames, isLoading: isUsersLoading } = useUsersDetails();
const theme = useTheme();
const [ isDeleteConfirmOpen, setIsDeleteConfirmOpen ] = useState(false);
const [ isDeleteAllConfirmOpen, setIsDeleteAllConfirmOpen ] = useState(false);
@@ -137,8 +139,15 @@ export const Component = () => {
}
], [ UserCell, userNames ]);
// NOTE: We need to provide a custom theme due to a MRT bug causing the initial theme to always be used
// https://github.com/KevinVandy/material-react-table/issues/1429
const mrtTheme = useMemo<Partial<MRT_Theme>>(() => ({
baseBackgroundColor: theme.palette.background.paper
}), [ theme ]);
const mrTable = useMaterialReactTable({
...DEFAULT_TABLE_OPTIONS,
mrtTheme,
columns,
data: devices,
@@ -190,10 +199,18 @@ export const Component = () => {
renderRowActions: ({ row, table }) => {
const isDeletable = api && row.original.Id && api.deviceInfo.id === row.original.Id;
return (
<Box sx={{ display: 'flex', gap: 1 }}>
<Box
sx={{
display: 'flex',
gap: 1,
'&&': {
backgroundColor: 'transparent !important'
}
}}
>
<Tooltip title={globalize.translate('Edit')}>
<IconButton
// eslint-disable-next-line react/jsx-no-bind
// eslint-disable-next-line react/jsx-no-bind
onClick={() => table.setEditingRow(row)}
>
<Edit />
+10 -1
View File
@@ -2,11 +2,12 @@ import type { AuthenticationInfo } from '@jellyfin/sdk/lib/generated-client/mode
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import { useTheme } from '@mui/material/styles';
import Tooltip from '@mui/material/Tooltip';
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/Delete';
import parseISO from 'date-fns/parseISO';
import { type MRT_ColumnDef, useMaterialReactTable } from 'material-react-table';
import { type MRT_ColumnDef, type MRT_Theme, useMaterialReactTable } from 'material-react-table';
import React, { useCallback, useMemo } from 'react';
import DateTimeCell from 'apps/dashboard/components/table/DateTimeCell';
@@ -27,6 +28,7 @@ export const Component = () => {
), [ data ]);
const revokeKey = useRevokeKey();
const createKey = useCreateKey();
const theme = useTheme();
const columns = useMemo<MRT_ColumnDef<AuthenticationInfo>[]>(() => [
{
@@ -49,8 +51,15 @@ export const Component = () => {
}
], []);
// NOTE: We need to provide a custom theme due to a MRT bug causing the initial theme to always be used
// https://github.com/KevinVandy/material-react-table/issues/1429
const mrtTheme = useMemo<Partial<MRT_Theme>>(() => ({
baseBackgroundColor: theme.palette.background.paper
}), [ theme ]);
const table = useMaterialReactTable({
...DEFAULT_TABLE_OPTIONS,
mrtTheme,
columns,
data: keys,
+11 -1
View File
@@ -7,10 +7,11 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import AddIcon from '@mui/icons-material/Add';
import IconButton from '@mui/material/IconButton';
import { useTheme } from '@mui/material/styles';
import Tooltip from '@mui/material/Tooltip';
import RemoveCircleIcon from '@mui/icons-material/RemoveCircle';
import Loading from 'components/loading/LoadingComponent';
import { MRT_ColumnDef, MRT_Table, useMaterialReactTable } from 'material-react-table';
import { type MRT_ColumnDef, MRT_Table, type MRT_Theme, useMaterialReactTable } from 'material-react-table';
import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info';
import globalize from '../../../../lib/globalize';
import { useTask } from 'apps/dashboard/features/tasks/api/useTask';
@@ -26,6 +27,7 @@ export const Component = () => {
const [ isAddTriggerDialogOpen, setIsAddTriggerDialogOpen ] = useState(false);
const [ isRemoveConfirmOpen, setIsRemoveConfirmOpen ] = useState(false);
const [ pendingDeleteTrigger, setPendingDeleteTrigger ] = useState<TaskTriggerInfo | null>(null);
const theme = useTheme();
const onCloseRemoveConfirmDialog = useCallback(() => {
setPendingDeleteTrigger(null);
@@ -80,7 +82,15 @@ export const Component = () => {
}
], []);
// NOTE: We need to provide a custom theme due to a MRT bug causing the initial theme to always be used
// https://github.com/KevinVandy/material-react-table/issues/1429
const mrtTheme = useMemo<Partial<MRT_Theme>>(() => ({
baseBackgroundColor: theme.palette.background.paper
}), [ theme ]);
const table = useMaterialReactTable({
mrtTheme,
columns,
data: task?.Triggers || [],
@@ -4,7 +4,7 @@ import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Cast from '@mui/icons-material/Cast';
import IconButton from '@mui/material/IconButton';
import { useTheme } from '@mui/material/styles';
import type {} from '@mui/material/themeCssVarsAugmentation';
import Tooltip from '@mui/material/Tooltip';
import { playbackManager } from 'components/playback/playbackmanager';
@@ -15,7 +15,6 @@ import RemotePlayMenu, { ID } from './menus/RemotePlayMenu';
import RemotePlayActiveMenu, { ID as ACTIVE_ID } from './menus/RemotePlayActiveMenu';
const RemotePlayButton = () => {
const theme = useTheme();
const [ playerInfo, setPlayerInfo ] = useState(playbackManager.getPlayerInfo());
const updatePlayerInfo = useCallback(() => {
@@ -70,9 +69,10 @@ const RemotePlayButton = () => {
aria-haspopup='true'
onClick={onRemotePlayActiveButtonClick}
color='inherit'
sx={{
color: theme.palette.primary.main
}}
// eslint-disable-next-line react/jsx-no-bind
sx={(theme) => ({
color: theme.vars.palette.primary.main
})}
>
{playerInfo.deviceName || playerInfo.name}
</Button>
+1 -1
View File
@@ -5,7 +5,7 @@ import globalize from '../../../lib/globalize';
import { clearBackdrop } from '../../../components/backdrop/backdrop';
import layoutManager from '../../../components/layoutManager';
import Page from '../../../components/Page';
import { EventType } from 'types/eventType';
import { EventType } from 'constants/eventType';
import Events from 'utils/events';
import '../../../elements/emby-tabs/emby-tabs';
+1 -1
View File
@@ -6,7 +6,7 @@ import RemotePlayButton from 'apps/experimental/components/AppToolbar/RemotePlay
import SyncPlayButton from 'apps/experimental/components/AppToolbar/SyncPlayButton';
import AppToolbar from 'components/toolbar/AppToolbar';
import ViewManagerPage from 'components/viewManager/ViewManagerPage';
import { EventType } from 'types/eventType';
import { EventType } from 'constants/eventType';
import Events, { type Event } from 'utils/events';
/**
+7 -7
View File
@@ -1,7 +1,7 @@
import React, { FC } from 'react';
import React, { type FC } from 'react';
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import Avatar from '@mui/material/Avatar';
import { useTheme } from '@mui/material/styles';
import type {} from '@mui/material/themeCssVarsAugmentation';
import { useApi } from 'hooks/useApi';
@@ -11,7 +11,6 @@ interface UserAvatarProps {
const UserAvatar: FC<UserAvatarProps> = ({ user }) => {
const { api } = useApi();
const theme = useTheme();
return user ? (
<Avatar
@@ -21,12 +20,13 @@ const UserAvatar: FC<UserAvatarProps> = ({ user }) => {
`${api.basePath}/Users/${user.Id}/Images/Primary?tag=${user.PrimaryImageTag}` :
undefined
}
sx={{
// eslint-disable-next-line react/jsx-no-bind
sx={(theme) => ({
bgcolor: api && user.Id && user.PrimaryImageTag ?
theme.palette.background.paper :
theme.palette.primary.dark,
theme.vars.palette.background.paper :
theme.vars.palette.primary.dark,
color: 'inherit'
}}
})}
/>
) : null;
};
+5 -5
View File
@@ -2,7 +2,7 @@ import React, { type FC } from 'react';
import classNames from 'classnames';
import StarIcon from '@mui/icons-material/Star';
import Box from '@mui/material/Box';
import { useTheme } from '@mui/material/styles';
import type {} from '@mui/material/themeCssVarsAugmentation';
interface StarIconsProps {
className?: string;
@@ -10,7 +10,6 @@ interface StarIconsProps {
}
const StarIcons: FC<StarIconsProps> = ({ className, communityRating }) => {
const theme = useTheme();
const cssClass = classNames(
'mediaInfoItem',
'starRatingContainer',
@@ -21,9 +20,10 @@ const StarIcons: FC<StarIconsProps> = ({ className, communityRating }) => {
<Box className={cssClass}>
<StarIcon
fontSize={'small'}
sx={{
color: theme.palette.starIcon.main
}}
// eslint-disable-next-line react/jsx-no-bind
sx={(theme) => ({
color: theme.vars.palette.starIcon.main
})}
/>
{communityRating.toFixed(1)}
</Box>
+1 -1
View File
@@ -4,7 +4,7 @@ import type { MediaSegmentDto } from '@jellyfin/sdk/lib/generated-client/models/
import { PlaybackSubscriber } from 'apps/stable/features/playback/utils/playbackSubscriber';
import { isInSegment } from 'apps/stable/features/playback/utils/mediaSegments';
import Events, { type Event } from 'utils/events';
import { EventType } from 'types/eventType';
import { EventType } from 'constants/eventType';
import './skipbutton.scss';
import dom from 'utils/dom';
import globalize from 'lib/globalize';
@@ -4,5 +4,6 @@
export enum EventType {
HEADER_RENDERED = 'HEADER_RENDERED',
SET_TABS = 'SET_TABS',
SHOW_VIDEO_OSD = 'SHOW_VIDEO_OSD'
SHOW_VIDEO_OSD = 'SHOW_VIDEO_OSD',
THEME_CHANGE = 'THEME_CHANGE'
}
+1 -1
View File
@@ -3,7 +3,7 @@ import escapeHtml from 'escape-html';
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
import { AppFeature } from 'constants/appFeature';
import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time';
import { EventType } from 'types/eventType';
import { EventType } from 'constants/eventType';
import { playbackManager } from '../../../components/playback/playbackmanager';
import browser from '../../../scripts/browser';
@@ -1,7 +1,8 @@
import React, { type FC, useCallback, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress';
import { useTheme } from '@mui/material/styles';
import type {} from '@mui/material/themeCssVarsAugmentation';
import type { ProgressOptions } from 'types/progressOptions';
interface AutoTimeProgressBarProps {
@@ -23,7 +24,6 @@ const AutoTimeProgressBar: FC<AutoTimeProgressBarProps> = ({
}) => {
const [progress, setProgress] = useState(pct);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const theme = useTheme();
const onAutoTimeProgress = useCallback(() => {
const start = parseInt(starTtime.toString(), 10);
@@ -66,12 +66,15 @@ const AutoTimeProgressBar: FC<AutoTimeProgressBarProps> = ({
className={progressBarClass}
variant='determinate'
value={progress}
sx={{
// eslint-disable-next-line react/jsx-no-bind
sx={(theme) => ({
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 5,
backgroundColor: isRecording ? theme.palette.error.main : theme.palette.primary.main
backgroundColor: isRecording ?
theme.vars.palette.error.main :
theme.vars.palette.primary.main
}
}}
})}
/>
);
};
+1 -1
View File
@@ -8,7 +8,7 @@ import { AppFeature } from 'constants/appFeature';
import { getUserViewsQuery } from 'hooks/useUserViews';
import globalize from 'lib/globalize';
import { ServerConnections } from 'lib/jellyfin-apiclient';
import { EventType } from 'types/eventType';
import { EventType } from 'constants/eventType';
import { toApi } from 'utils/jellyfin-apiclient/compat';
import { queryClient } from 'utils/query/queryClient';
+5
View File
@@ -1,3 +1,6 @@
import Events from 'utils/events';
import { EventType } from 'constants/eventType';
import { getDefaultTheme, getThemes as getConfiguredThemes } from './settings/webSettings';
let currentThemeId;
@@ -44,6 +47,8 @@ function setTheme(id) {
// set the meta theme color
document.getElementById('themeColor').content = info.color;
Events.trigger(document, EventType.THEME_CHANGE, [ info.id ]);
});
});
}
+21
View File
@@ -0,0 +1,21 @@
import { StorageManager } from '@mui/material/styles';
import Events, { type Event } from 'utils/events';
import { EventType } from 'constants/eventType';
/**
* A custom MUI StorageManager.
*
* Since we switch the theme based on the current page, we handle getting/setting the current theme via autoTheme +
* themeManager. We need to implement `subscribe` so MUI is aware of theme changes though otherwise the `useTheme` hook
* will always return the default theme.
*/
export const ThemeStorageManager: StorageManager = () => ({
get: defaultValue => defaultValue,
set: () => { /* no-op */ },
subscribe: handler => {
const wrappedHandler = (_e: Event, value: string) => handler(value);
Events.on(document, EventType.THEME_CHANGE, wrappedHandler);
return () => Events.off(document, EventType.THEME_CHANGE, wrappedHandler);
}
});