diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx index ce74f4989b..ca3827f966 100644 --- a/src/apps/dashboard/AppLayout.tsx +++ b/src/apps/dashboard/AppLayout.tsx @@ -1,7 +1,8 @@ import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; -import { useTheme } from '@mui/material/styles'; -import React, { FC, useCallback, useEffect, useState } from 'react'; +import { type Theme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import React, { FC, useCallback, useState } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; import AppBody from 'components/AppBody'; @@ -9,7 +10,6 @@ import AppToolbar from 'components/toolbar/AppToolbar'; import ElevationScroll from 'components/ElevationScroll'; import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; import { useApi } from 'hooks/useApi'; -import { useLocalStorage } from 'hooks/useLocalStorage'; import AppDrawer from './components/drawer/AppDrawer'; @@ -19,34 +19,17 @@ interface AppLayoutProps { drawerlessPaths: string[] } -interface DashboardAppSettings { - isDrawerPinned: boolean -} - -const DEFAULT_APP_SETTINGS: DashboardAppSettings = { - isDrawerPinned: false -}; - const AppLayout: FC = ({ drawerlessPaths }) => { - const [ appSettings, setAppSettings ] = useLocalStorage('DashboardAppSettings', DEFAULT_APP_SETTINGS); - const [ isDrawerActive, setIsDrawerActive ] = useState(appSettings.isDrawerPinned); + const [ isDrawerActive, setIsDrawerActive ] = useState(false); const location = useLocation(); - const theme = useTheme(); const { user } = useApi(); - const isDrawerAvailable = !drawerlessPaths.some(path => location.pathname.startsWith(`/${path}`)); - const isDrawerOpen = isDrawerActive && isDrawerAvailable && Boolean(user); - - useEffect(() => { - if (isDrawerActive !== appSettings.isDrawerPinned) { - setAppSettings({ - ...appSettings, - isDrawerPinned: isDrawerActive - }); - } - }, [ appSettings, isDrawerActive, setAppSettings ]); + const isMediumScreen = useMediaQuery((t: Theme) => t.breakpoints.up('md')); + const isDrawerAvailable = Boolean(user) + && !drawerlessPaths.some(path => location.pathname.startsWith(`/${path}`)); + const isDrawerOpen = isDrawerActive && isDrawerAvailable; const onToggleDrawer = useCallback(() => { setIsDrawerActive(!isDrawerActive); @@ -54,47 +37,43 @@ const AppLayout: FC = ({ return ( - + muiTheme.zIndex.drawer + 1 }} + sx={{ + width: { + xs: '100%', + md: isDrawerAvailable ? `calc(100% - ${DRAWER_WIDTH}px)` : '100%' + }, + ml: { + xs: 0, + md: isDrawerAvailable ? DRAWER_WIDTH : 0 + } + }} > - + { + isDrawerAvailable && ( + + ) + } diff --git a/src/apps/dashboard/components/drawer/AppDrawer.tsx b/src/apps/dashboard/components/drawer/AppDrawer.tsx index 7b1e180123..fda5b5f2d4 100644 --- a/src/apps/dashboard/components/drawer/AppDrawer.tsx +++ b/src/apps/dashboard/components/drawer/AppDrawer.tsx @@ -1,5 +1,8 @@ +import ListItem from '@mui/material/ListItem'; +import List from '@mui/material/List'; import React, { FC } from 'react'; +import DrawerHeaderLink from 'apps/experimental/components/drawers/DrawerHeaderLink'; import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDrawer'; import ServerDrawerSection from './sections/ServerDrawerSection'; @@ -18,6 +21,11 @@ const AppDrawer: FC = ({ onClose={onClose} onOpen={onOpen} > + + + + + diff --git a/src/apps/experimental/AppLayout.tsx b/src/apps/experimental/AppLayout.tsx index 1f4860637c..fd02220a7f 100644 --- a/src/apps/experimental/AppLayout.tsx +++ b/src/apps/experimental/AppLayout.tsx @@ -1,46 +1,28 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; -import { useTheme } from '@mui/material/styles'; +import { type Theme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; import { Outlet, useLocation } from 'react-router-dom'; import AppBody from 'components/AppBody'; import ElevationScroll from 'components/ElevationScroll'; import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; import { useApi } from 'hooks/useApi'; -import { useLocalStorage } from 'hooks/useLocalStorage'; import AppToolbar from './components/AppToolbar'; import AppDrawer, { isDrawerPath } from './components/drawers/AppDrawer'; import './AppOverrides.scss'; -interface ExperimentalAppSettings { - isDrawerPinned: boolean -} - -const DEFAULT_EXPERIMENTAL_APP_SETTINGS: ExperimentalAppSettings = { - isDrawerPinned: false -}; - const AppLayout = () => { - const [ appSettings, setAppSettings ] = useLocalStorage('ExperimentalAppSettings', DEFAULT_EXPERIMENTAL_APP_SETTINGS); - const [ isDrawerActive, setIsDrawerActive ] = useState(appSettings.isDrawerPinned); + const [ isDrawerActive, setIsDrawerActive ] = useState(false); const { user } = useApi(); const location = useLocation(); - const theme = useTheme(); - const isDrawerAvailable = isDrawerPath(location.pathname); - const isDrawerOpen = isDrawerActive && isDrawerAvailable && Boolean(user); - - useEffect(() => { - if (isDrawerActive !== appSettings.isDrawerPinned) { - setAppSettings({ - ...appSettings, - isDrawerPinned: isDrawerActive - }); - } - }, [ appSettings, isDrawerActive, setAppSettings ]); + const isMediumScreen = useMediaQuery((t: Theme) => t.breakpoints.up('md')); + const isDrawerAvailable = isDrawerPath(location.pathname) && Boolean(user); + const isDrawerOpen = isDrawerActive && isDrawerAvailable; const onToggleDrawer = useCallback(() => { setIsDrawerActive(!isDrawerActive); @@ -48,46 +30,43 @@ const AppLayout = () => { return ( - + muiTheme.zIndex.drawer + 1 }} + sx={{ + width: { + xs: '100%', + md: isDrawerAvailable ? `calc(100% - ${DRAWER_WIDTH}px)` : '100%' + }, + ml: { + xs: 0, + md: isDrawerAvailable ? DRAWER_WIDTH : 0 + } + }} > - + { + isDrawerAvailable && ( + + ) + } diff --git a/src/apps/experimental/AppOverrides.scss b/src/apps/experimental/AppOverrides.scss index cece6608c1..f8f6d27405 100644 --- a/src/apps/experimental/AppOverrides.scss +++ b/src/apps/experimental/AppOverrides.scss @@ -20,11 +20,15 @@ $mui-bp-xl: 1536px; } // Fix the padding of some pages -.homePage.libraryPage, // Home page -.libraryPage:not(.withTabs) { // Tabless library pages +.homePage.libraryPage.withTabs, // Home page +// Library pages excluding the item details page and tabbed pages +.libraryPage:not( + .itemDetailPage, + .withTabs +) { padding-top: 3.25rem !important; } - +// Tabbed library pages .libraryPage.withTabs { padding-top: 6.5rem !important; diff --git a/src/apps/experimental/components/AppToolbar/index.tsx b/src/apps/experimental/components/AppToolbar/index.tsx index 9ff1e39202..950fb63841 100644 --- a/src/apps/experimental/components/AppToolbar/index.tsx +++ b/src/apps/experimental/components/AppToolbar/index.tsx @@ -8,22 +8,22 @@ import AppToolbar from 'components/toolbar/AppToolbar'; import globalize from 'scripts/globalize'; import AppTabs from '../tabs/AppTabs'; -import { isDrawerPath } from '../drawers/AppDrawer'; import RemotePlayButton from './RemotePlayButton'; import SyncPlayButton from './SyncPlayButton'; import { isTabPath } from '../tabs/tabRoutes'; interface AppToolbarProps { + isDrawerAvailable: boolean isDrawerOpen: boolean onDrawerButtonClick: (event: React.MouseEvent) => void } const ExperimentalAppToolbar: FC = ({ + isDrawerAvailable, isDrawerOpen, onDrawerButtonClick }) => { const location = useLocation(); - const isDrawerAvailable = isDrawerPath(location.pathname); const isTabsAvailable = isTabPath(location.pathname); return ( diff --git a/src/apps/experimental/components/drawers/AppDrawer.tsx b/src/apps/experimental/components/drawers/AppDrawer.tsx index 21926d6c59..ca87671889 100644 --- a/src/apps/experimental/components/drawers/AppDrawer.tsx +++ b/src/apps/experimental/components/drawers/AppDrawer.tsx @@ -1,11 +1,9 @@ import React, { FC } from 'react'; -import { useLocation } from 'react-router-dom'; import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDrawer'; import { ASYNC_USER_ROUTES } from '../../routes/asyncRoutes'; import { LEGACY_USER_ROUTES } from '../../routes/legacyRoutes'; -import { isTabPath } from '../tabs/tabRoutes'; import MainDrawerContent from './MainDrawerContent'; @@ -27,20 +25,14 @@ const AppDrawer: FC = ({ open = false, onClose, onOpen -}) => { - const location = useLocation(); - const hasSecondaryToolBar = isTabPath(location.pathname); - - return ( - - - - ); -}; +}) => ( + + + +); export default AppDrawer; diff --git a/src/apps/experimental/components/drawers/DrawerHeaderLink.tsx b/src/apps/experimental/components/drawers/DrawerHeaderLink.tsx new file mode 100644 index 0000000000..4515779763 --- /dev/null +++ b/src/apps/experimental/components/drawers/DrawerHeaderLink.tsx @@ -0,0 +1,33 @@ +import Box from '@mui/material/Box'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import React from 'react'; + +import { useApi } from 'hooks/useApi'; +import { useSystemInfo } from 'hooks/useSystemInfo'; +import ListItemLink from 'components/ListItemLink'; + +import appIcon from 'assets/img/icon-transparent.png'; + +const DrawerHeaderLink = () => { + const { api } = useApi(); + const { data: systemInfo } = useSystemInfo(api); + + return ( + + + + + + ); +}; + +export default DrawerHeaderLink; diff --git a/src/apps/experimental/components/drawers/MainDrawerContent.tsx b/src/apps/experimental/components/drawers/MainDrawerContent.tsx index 1ca4501bfc..4bdcbdd19d 100644 --- a/src/apps/experimental/components/drawers/MainDrawerContent.tsx +++ b/src/apps/experimental/components/drawers/MainDrawerContent.tsx @@ -1,7 +1,5 @@ import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'; -import type { SystemInfo } from '@jellyfin/sdk/lib/generated-client/models/system-info'; import { getUserViewsApi } from '@jellyfin/sdk/lib/utils/api/user-views-api'; -import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; import Dashboard from '@mui/icons-material/Dashboard'; import Edit from '@mui/icons-material/Edit'; import Favorite from '@mui/icons-material/Favorite'; @@ -24,11 +22,11 @@ import { useWebConfig } from 'hooks/useWebConfig'; import globalize from 'scripts/globalize'; import LibraryIcon from '../LibraryIcon'; +import DrawerHeaderLink from './DrawerHeaderLink'; const MainDrawerContent = () => { const { api, user } = useApi(); const location = useLocation(); - const [ systemInfo, setSystemInfo ] = useState(); const [ userViews, setUserViews ] = useState([]); const webConfig = useWebConfig(); @@ -45,15 +43,6 @@ const MainDrawerContent = () => { console.warn('[MainDrawer] failed to fetch user views', err); setUserViews([]); }); - - getSystemApi(api) - .getSystemInfo() - .then(({ data }) => { - setSystemInfo(data); - }) - .catch(err => { - console.warn('[MainDrawer] failed to fetch system info', err); - }); } else { setUserViews([]); } @@ -62,7 +51,10 @@ const MainDrawerContent = () => { return ( <> {/* MAIN LINKS */} - + + + + @@ -167,17 +159,6 @@ const MainDrawerContent = () => { )} - - {/* FOOTER */} - - - - - - ); }; diff --git a/src/components/Backdrop.tsx b/src/components/Backdrop.tsx index 1283be0aa9..180fab1947 100644 --- a/src/components/Backdrop.tsx +++ b/src/components/Backdrop.tsx @@ -1,4 +1,6 @@ +import Box from '@mui/material/Box'; import React, { useEffect } from 'react'; +import { DRAWER_WIDTH } from './ResponsiveDrawer'; const Backdrop = () => { useEffect(() => { @@ -8,7 +10,14 @@ const Backdrop = () => { return ( <> -
+
); diff --git a/src/components/ResponsiveDrawer.tsx b/src/components/ResponsiveDrawer.tsx index bc463cca2f..22d6f73c3b 100644 --- a/src/components/ResponsiveDrawer.tsx +++ b/src/components/ResponsiveDrawer.tsx @@ -1,17 +1,15 @@ -import { Theme } from '@mui/material/styles'; +import type { Theme } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Drawer from '@mui/material/Drawer'; import SwipeableDrawer from '@mui/material/SwipeableDrawer'; -import Toolbar from '@mui/material/Toolbar'; import useMediaQuery from '@mui/material/useMediaQuery'; -import React, { FC, useCallback } from 'react'; +import React, { FC } from 'react'; import browser from 'scripts/browser'; export const DRAWER_WIDTH = 240; export interface ResponsiveDrawerProps { - hasSecondaryToolBar?: boolean open: boolean onClose: () => void onOpen: () => void @@ -19,19 +17,13 @@ export interface ResponsiveDrawerProps { const ResponsiveDrawer: FC = ({ children, - hasSecondaryToolBar = false, open = false, onClose, onOpen }) => { - const isSmallScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm')); - const isLargeScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('lg')); + const isMediumScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('md')); - const getToolbarStyles = useCallback((theme: Theme) => ({ - marginBottom: (hasSecondaryToolBar && !isLargeScreen) ? theme.spacing(6) : 0 - }), [ hasSecondaryToolBar, isLargeScreen ]); - - return ( isSmallScreen ? ( + return ( isMediumScreen ? ( /* DESKTOP DRAWER */ = ({ boxSizing: 'border-box' } }} - variant='persistent' + variant='permanent' anchor='left' - open={open} > - {children} ) : ( @@ -65,10 +52,6 @@ const ResponsiveDrawer: FC = ({ keepMounted: true // Better open performance on mobile. }} > - = ({ )} - - - - Jellyfin - - - {children} {isUserLoggedIn && isUserMenuAvailable && ( diff --git a/src/hooks/useQuickConnect.ts b/src/hooks/useQuickConnect.ts index c99fbd3d9a..2ac8fa74a9 100644 --- a/src/hooks/useQuickConnect.ts +++ b/src/hooks/useQuickConnect.ts @@ -1,8 +1,8 @@ import { getQuickConnectApi } from '@jellyfin/sdk/lib/utils/api/quick-connect-api'; import { useQuery } from '@tanstack/react-query'; -import { AxiosRequestConfig } from 'axios'; +import type { AxiosRequestConfig } from 'axios'; -import { JellyfinApiContext, useApi } from './useApi'; +import { type JellyfinApiContext, useApi } from './useApi'; const fetchQuickConnectEnabled = async ( apiContext: JellyfinApiContext, diff --git a/src/hooks/useSystemInfo.ts b/src/hooks/useSystemInfo.ts new file mode 100644 index 0000000000..626f0b6d69 --- /dev/null +++ b/src/hooks/useSystemInfo.ts @@ -0,0 +1,23 @@ +import { useQuery } from '@tanstack/react-query'; +import type { Api } from '@jellyfin/sdk'; +import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; +import type { AxiosRequestConfig } from 'axios'; + +const fetchSystemInfo = async ( + api: Api | undefined, + options: AxiosRequestConfig +) => { + if (!api) throw new Error('No API instance available'); + + const response = await getSystemApi(api) + .getSystemInfo(options); + return response.data; +}; + +export const useSystemInfo = (api: Api | undefined) => { + return useQuery({ + queryKey: [ 'SystemInfo' ], + queryFn: ({ signal }) => fetchSystemInfo(api, { signal }), + enabled: !!api + }); +}; diff --git a/src/themes/theme.ts b/src/themes/theme.ts index e223e24c51..a5230ebafd 100644 --- a/src/themes/theme.ts +++ b/src/themes/theme.ts @@ -1,5 +1,7 @@ import { createTheme } from '@mui/material/styles'; +const LIST_ICON_WIDTH = 36; + /** The default Jellyfin app theme for mui */ const theme = createTheme({ palette: { @@ -49,11 +51,26 @@ const theme = createTheme({ variant: 'filled' } }, + MuiListItemIcon: { + styleOverrides: { + root: { + minWidth: LIST_ICON_WIDTH + } + } + }, MuiListSubheader: { styleOverrides: { root: { // NOTE: Added for drawer subheaders, but maybe it won't work in other cases? - backgroundColor: 'inherit' + backgroundColor: 'inherit', + position: 'initial' + } + } + }, + MuiListItemText: { + styleOverrides: { + inset: { + paddingLeft: LIST_ICON_WIDTH } } }