Update mui based layout ui

This commit is contained in:
Bill Thornton 2023-11-28 10:26:14 -05:00
parent bfbdffdff5
commit 4e7f0136f7
14 changed files with 163 additions and 194 deletions

View file

@ -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,45 +19,37 @@ interface AppLayoutProps {
drawerlessPaths: string[]
}
interface DashboardAppSettings {
isDrawerPinned: boolean
}
const DEFAULT_APP_SETTINGS: DashboardAppSettings = {
isDrawerPinned: false
};
const AppLayout: FC<AppLayoutProps> = ({
drawerlessPaths
}) => {
const [ appSettings, setAppSettings ] = useLocalStorage<DashboardAppSettings>('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 isSmallScreen = useMediaQuery((t: Theme) => t.breakpoints.up('sm'));
const isDrawerAvailable = !isSmallScreen
&& !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 onToggleDrawer = useCallback(() => {
setIsDrawerActive(!isDrawerActive);
}, [ isDrawerActive, setIsDrawerActive ]);
return (
<Box sx={{ display: 'flex' }}>
<ElevationScroll elevate={isDrawerOpen}>
<ElevationScroll elevate={false}>
<AppBar
position='fixed'
sx={{ zIndex: (muiTheme) => muiTheme.zIndex.drawer + 1 }}
sx={{
width: {
xs: '100%',
sm: `calc(100% - ${DRAWER_WIDTH}px)`
},
ml: {
xs: 0,
sm: `${DRAWER_WIDTH}px`
}
}}
>
<AppToolbar
isDrawerAvailable={isDrawerAvailable}
@ -77,24 +69,7 @@ const AppLayout: FC<AppLayoutProps> = ({
component='main'
sx={{
width: '100%',
flexGrow: 1,
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
marginLeft: 0,
...(isDrawerAvailable && {
marginLeft: {
sm: `-${DRAWER_WIDTH}px`
}
}),
...(isDrawerActive && {
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
}),
marginLeft: 0
})
flexGrow: 1
}}
>
<AppBody>

View file

@ -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<ResponsiveDrawerProps> = ({
onClose={onClose}
onOpen={onOpen}
>
<List disablePadding>
<ListItem disablePadding>
<DrawerHeaderLink />
</ListItem>
</List>
<ServerDrawerSection />
<DevicesDrawerSection />
<LiveTvDrawerSection />

View file

@ -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>('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 isSmallScreen = useMediaQuery((t: Theme) => t.breakpoints.up('sm'));
const isDrawerAvailable = isDrawerPath(location.pathname) && Boolean(user);
const isDrawerOpen = isDrawerActive && isDrawerAvailable;
const onToggleDrawer = useCallback(() => {
setIsDrawerActive(!isDrawerActive);
@ -48,46 +30,43 @@ const AppLayout = () => {
return (
<Box sx={{ display: 'flex' }}>
<ElevationScroll elevate={isDrawerOpen}>
<ElevationScroll elevate={false}>
<AppBar
position='fixed'
sx={{ zIndex: (muiTheme) => muiTheme.zIndex.drawer + 1 }}
sx={{
width: {
xs: '100%',
sm: isDrawerAvailable ? `calc(100% - ${DRAWER_WIDTH}px)` : '100%'
},
ml: {
xs: 0,
sm: isDrawerAvailable ? DRAWER_WIDTH : 0
}
}}
>
<AppToolbar
isDrawerAvailable={!isSmallScreen && isDrawerAvailable}
isDrawerOpen={isDrawerOpen}
onDrawerButtonClick={onToggleDrawer}
/>
</AppBar>
</ElevationScroll>
<AppDrawer
open={isDrawerOpen}
onClose={onToggleDrawer}
onOpen={onToggleDrawer}
/>
{
user && (
<AppDrawer
open={isDrawerOpen}
onClose={onToggleDrawer}
onOpen={onToggleDrawer}
/>
)
}
<Box
component='main'
sx={{
width: '100%',
flexGrow: 1,
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
marginLeft: 0,
...(isDrawerAvailable && {
marginLeft: {
sm: `-${DRAWER_WIDTH}px`
}
}),
...(isDrawerActive && {
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
}),
marginLeft: 0
})
flexGrow: 1
}}
>
<AppBody>

View file

@ -20,7 +20,7 @@ $mui-bp-xl: 1536px;
}
// Fix the padding of some pages
.homePage.libraryPage, // Home page
.homePage.libraryPage.withTabs, // Home page
.libraryPage:not(.withTabs) { // Tabless library pages
padding-top: 3.25rem !important;
}

View file

@ -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<HTMLElement>) => void
}
const ExperimentalAppToolbar: FC<AppToolbarProps> = ({
isDrawerAvailable,
isDrawerOpen,
onDrawerButtonClick
}) => {
const location = useLocation();
const isDrawerAvailable = isDrawerPath(location.pathname);
const isTabsAvailable = isTabPath(location.pathname);
return (

View file

@ -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<ResponsiveDrawerProps> = ({
open = false,
onClose,
onOpen
}) => {
const location = useLocation();
const hasSecondaryToolBar = isTabPath(location.pathname);
return (
<ResponsiveDrawer
hasSecondaryToolBar={hasSecondaryToolBar}
open={open}
onClose={onClose}
onOpen={onOpen}
>
<MainDrawerContent />
</ResponsiveDrawer>
);
};
}) => (
<ResponsiveDrawer
open={open}
onClose={onClose}
onOpen={onOpen}
>
<MainDrawerContent />
</ResponsiveDrawer>
);
export default AppDrawer;

View file

@ -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 (
<ListItemLink to='/'>
<ListItemIcon sx={{ minWidth: 56 }}>
<Box
component='img'
src={appIcon}
sx={{ height: '2.5rem' }}
/>
</ListItemIcon>
<ListItemText
primary={systemInfo?.ServerName || 'Jellyfin'}
primaryTypographyProps={{ variant: 'h6' }}
secondary={systemInfo?.Version}
/>
</ListItemLink>);
};
export default DrawerHeaderLink;

View file

@ -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<SystemInfo>();
const [ userViews, setUserViews ] = useState<BaseItemDto[]>([]);
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 */}
<List>
<List sx={{ paddingTop: 0 }}>
<ListItem disablePadding>
<DrawerHeaderLink />
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/home.html' selected={isHomeSelected}>
<ListItemIcon>
@ -168,17 +160,6 @@ const MainDrawerContent = () => {
</List>
</>
)}
{/* FOOTER */}
<Divider style={{ marginTop: 'auto' }} />
<List>
<ListItem>
<ListItemText
primary={systemInfo?.ServerName ? systemInfo.ServerName : 'Jellyfin'}
secondary={systemInfo?.Version ? `v${systemInfo.Version}` : ''}
/>
</ListItem>
</List>
</>
);
};

View file

@ -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 (
<>
<div className='backdropContainer' />
<Box
className='backdropContainer'
sx={{
left: {
sm: DRAWER_WIDTH
}
}}
/>
<div className='backgroundContainer' />
</>
);

View file

@ -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,17 +17,11 @@ export interface ResponsiveDrawerProps {
const ResponsiveDrawer: FC<ResponsiveDrawerProps> = ({
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 getToolbarStyles = useCallback((theme: Theme) => ({
marginBottom: (hasSecondaryToolBar && !isLargeScreen) ? theme.spacing(6) : 0
}), [ hasSecondaryToolBar, isLargeScreen ]);
return ( isSmallScreen ? (
/* DESKTOP DRAWER */
@ -42,14 +34,9 @@ const ResponsiveDrawer: FC<ResponsiveDrawerProps> = ({
boxSizing: 'border-box'
}
}}
variant='persistent'
variant='permanent'
anchor='left'
open={open}
>
<Toolbar
variant='dense'
sx={getToolbarStyles}
/>
{children}
</Drawer>
) : (
@ -65,10 +52,6 @@ const ResponsiveDrawer: FC<ResponsiveDrawerProps> = ({
keepMounted: true // Better open performance on mobile.
}}
>
<Toolbar
variant='dense'
sx={getToolbarStyles}
/>
<Box
role='presentation'
// Close the drawer when the content is clicked

View file

@ -4,11 +4,9 @@ import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Toolbar from '@mui/material/Toolbar';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import React, { FC, ReactNode } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import appIcon from 'assets/img/icon-transparent.png';
import { appRouter } from 'components/router/appRouter';
import { useApi } from 'hooks/useApi';
import globalize from 'scripts/globalize';
@ -84,35 +82,6 @@ const AppToolbar: FC<AppToolbarProps> = ({
</Tooltip>
)}
<Box
component={Link}
to='/'
color='inherit'
aria-label={globalize.translate('Home')}
sx={{
ml: 2,
display: 'inline-flex',
textDecoration: 'none'
}}
>
<Box
component='img'
src={appIcon}
sx={{
height: '2rem',
marginInlineEnd: 1
}}
/>
<Typography
variant='h6'
noWrap
component='div'
sx={{ display: { xs: 'none', sm: 'inline-block' } }}
>
Jellyfin
</Typography>
</Box>
{children}
{isUserLoggedIn && isUserMenuAvailable && (

View file

@ -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,

View file

@ -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
});
};

View file

@ -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
}
}
}