diff --git a/src/RootAppRouter.tsx b/src/RootAppRouter.tsx index 4ce26864d..9c2205d33 100644 --- a/src/RootAppRouter.tsx +++ b/src/RootAppRouter.tsx @@ -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() { diff --git a/src/apps/dashboard/components/table/TablePage.tsx b/src/apps/dashboard/components/table/TablePage.tsx index 3b68ab851..a29061624 100644 --- a/src/apps/dashboard/components/table/TablePage.tsx +++ b/src/apps/dashboard/components/table/TablePage.tsx @@ -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 extends PageProps { table: MRT_TableInstance } -export const DEFAULT_TABLE_OPTIONS = { +export const DEFAULT_TABLE_OPTIONS: Partial> = { // Enable custom features enableColumnPinning: true, enableColumnResizing: true, diff --git a/src/apps/dashboard/routes/activity/index.tsx b/src/apps/dashboard/routes/activity/index.tsx index 09547af0b..fa11a45bd 100644 --- a/src/apps/dashboard/routes/activity/index.tsx +++ b/src/apps/dashboard/routes/activity/index.tsx @@ -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>(() => ({ + baseBackgroundColor: theme.palette.background.paper + }), [ theme ]); + const table = useMaterialReactTable({ ...DEFAULT_TABLE_OPTIONS, + mrtTheme, columns, data: logEntries, diff --git a/src/apps/dashboard/routes/devices/index.tsx b/src/apps/dashboard/routes/devices/index.tsx index e7961ccdb..e2571f582 100644 --- a/src/apps/dashboard/routes/devices/index.tsx +++ b/src/apps/dashboard/routes/devices/index.tsx @@ -6,7 +6,7 @@ import Button from '@mui/material/Button/Button'; import IconButton from '@mui/material/IconButton'; 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'; @@ -21,6 +21,8 @@ import ConfirmDialog from 'components/ConfirmDialog'; import { useApi } from 'hooks/useApi'; import { type UsersRecords, useUsersDetails } from 'hooks/useUsers'; import globalize from 'lib/globalize'; +import { COLOR_SCHEMES } from 'themes/themes'; +import { useColorScheme, useTheme } from '@mui/material'; const getUserCell = (users: UsersRecords) => function UserCell({ renderedCellValue, row }: DeviceInfoCell) { return ( @@ -41,6 +43,8 @@ export const Component = () => { data?.Items || [] ), [ data ]); const { usersById: users, names: userNames, isLoading: isUsersLoading } = useUsersDetails(); + const { colorScheme } = useColorScheme(); + const theme = useTheme(); const [ isDeleteConfirmOpen, setIsDeleteConfirmOpen ] = useState(false); const [ isDeleteAllConfirmOpen, setIsDeleteAllConfirmOpen ] = useState(false); @@ -137,8 +141,13 @@ export const Component = () => { } ], [ UserCell, userNames ]); + const mrtTheme = useMemo>(() => ({ + 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 ( - + table.setEditingRow(row)} > diff --git a/src/apps/dashboard/routes/keys/index.tsx b/src/apps/dashboard/routes/keys/index.tsx index 1abb66884..1036c13eb 100644 --- a/src/apps/dashboard/routes/keys/index.tsx +++ b/src/apps/dashboard/routes/keys/index.tsx @@ -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[]>(() => [ { @@ -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>(() => ({ + baseBackgroundColor: theme.palette.background.paper + }), [ theme ]); + const table = useMaterialReactTable({ ...DEFAULT_TABLE_OPTIONS, + mrtTheme, columns, data: keys, diff --git a/src/apps/dashboard/routes/tasks/task.tsx b/src/apps/dashboard/routes/tasks/task.tsx index 2aa00f886..ecceff6d1 100644 --- a/src/apps/dashboard/routes/tasks/task.tsx +++ b/src/apps/dashboard/routes/tasks/task.tsx @@ -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(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>(() => ({ + baseBackgroundColor: theme.palette.background.paper + }), [ theme ]); + const table = useMaterialReactTable({ + mrtTheme, + columns, data: task?.Triggers || [], diff --git a/src/scripts/themeManager.js b/src/scripts/themeManager.js index ecf153967..3b317e748 100644 --- a/src/scripts/themeManager.js +++ b/src/scripts/themeManager.js @@ -1,3 +1,6 @@ +import Events from 'utils/events'; +import { EventType } from 'types/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 ]); }); }); } diff --git a/src/themes/themeStorageManager.ts b/src/themes/themeStorageManager.ts new file mode 100644 index 000000000..e9a090537 --- /dev/null +++ b/src/themes/themeStorageManager.ts @@ -0,0 +1,21 @@ +import { StorageManager } from '@mui/material/styles'; + +import Events, { type Event } from 'utils/events'; +import { EventType } from 'types/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); + } +}); diff --git a/src/types/eventType.ts b/src/types/eventType.ts index 06487e982..c9edaed8a 100644 --- a/src/types/eventType.ts +++ b/src/types/eventType.ts @@ -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' }