Merge pull request #7170 from thornbill/fix-css-theming
This commit is contained in:
@@ -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} />
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 ]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user