Prevent grid items from jumping after closing reader
Due to the reader and the "default" nav bar updating the same navbar width in the context, the grid item width calculation resulted in different grid item widths, which caused the items to "jump" after closing the reader
This commit is contained in:
@@ -86,8 +86,9 @@ const MainApp = () => {
|
|||||||
id="appMainContainer"
|
id="appMainContainer"
|
||||||
component="main"
|
component="main"
|
||||||
sx={{
|
sx={{
|
||||||
flexGrow: 1,
|
|
||||||
minHeight: `calc(100vh - ${bottomBarHeight}px)`,
|
minHeight: `calc(100vh - ${bottomBarHeight}px)`,
|
||||||
|
width: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
||||||
|
minWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
||||||
maxWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
maxWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
mb: `${bottomBarHeight}px`,
|
mb: `${bottomBarHeight}px`,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { AppStorage } from '@/util/AppStorage.ts';
|
|||||||
import { MangaCardProps } from '@/components/manga/MangaCard.types.tsx';
|
import { MangaCardProps } from '@/components/manga/MangaCard.types.tsx';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { useResizeObserver } from '@/util/useResizeObserver.tsx';
|
import { useResizeObserver } from '@/util/useResizeObserver.tsx';
|
||||||
import { useDebounce } from '@/util/useDebounce.ts';
|
import { useNavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||||
|
|
||||||
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
|
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
|
||||||
<Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}>
|
<Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}>
|
||||||
@@ -253,12 +253,15 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const gridRef = useRef<HTMLDivElement>(null);
|
const { navBarWidth } = useNavBarContext();
|
||||||
|
|
||||||
const [actualDimensions, setDimensions] = useState(document.documentElement.offsetWidth);
|
const gridRef = useRef<HTMLDivElement>(null);
|
||||||
const dimensions = useDebounce(actualDimensions, 500);
|
|
||||||
const [gridItemWidth] = useLocalStorage<number>('ItemWidth', 300);
|
|
||||||
const gridWrapperRef = useRef<HTMLDivElement>(null);
|
const gridWrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [dimensions, setDimensions] = useState(
|
||||||
|
gridWrapperRef.current?.offsetWidth ?? Math.max(0, document.documentElement.offsetWidth - navBarWidth),
|
||||||
|
);
|
||||||
|
const [gridItemWidth] = useLocalStorage<number>('ItemWidth', 300);
|
||||||
const GridItemContainer = useMemo(
|
const GridItemContainer = useMemo(
|
||||||
() => GridItemContainerWithDimension(dimensions, gridItemWidth, gridLayout),
|
() => GridItemContainerWithDimension(dimensions, gridItemWidth, gridLayout),
|
||||||
[dimensions, gridItemWidth, gridLayout],
|
[dimensions, gridItemWidth, gridLayout],
|
||||||
@@ -300,14 +303,14 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
|
|||||||
const gridWidth = gridWrapperRef.current?.offsetWidth;
|
const gridWidth = gridWrapperRef.current?.offsetWidth;
|
||||||
|
|
||||||
if (!gridWidth) {
|
if (!gridWidth) {
|
||||||
return document.documentElement.offsetWidth;
|
return document.documentElement.offsetWidth - navBarWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
return gridWidth;
|
return gridWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
setDimensions(getDimensions());
|
setDimensions(getDimensions());
|
||||||
}, []),
|
}, [navBarWidth]),
|
||||||
);
|
);
|
||||||
|
|
||||||
useResizeObserver(
|
useResizeObserver(
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ type ContextType = {
|
|||||||
navBarWidth: number;
|
navBarWidth: number;
|
||||||
setNavBarWidth: React.Dispatch<React.SetStateAction<number>>;
|
setNavBarWidth: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
|
||||||
|
readerNavBarWidth: number;
|
||||||
|
setReaderNavBarWidth: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
|
||||||
bottomBarHeight: number;
|
bottomBarHeight: number;
|
||||||
setBottomBarHeight: React.Dispatch<React.SetStateAction<number>>;
|
setBottomBarHeight: React.Dispatch<React.SetStateAction<number>>;
|
||||||
};
|
};
|
||||||
@@ -52,6 +55,8 @@ export const NavBarContext = React.createContext<ContextType>({
|
|||||||
setIsCollapsed: (): void => {},
|
setIsCollapsed: (): void => {},
|
||||||
navBarWidth: 0,
|
navBarWidth: 0,
|
||||||
setNavBarWidth: (): void => {},
|
setNavBarWidth: (): void => {},
|
||||||
|
readerNavBarWidth: 0,
|
||||||
|
setReaderNavBarWidth: (): void => {},
|
||||||
bottomBarHeight: 0,
|
bottomBarHeight: 0,
|
||||||
setBottomBarHeight: (): void => {},
|
setBottomBarHeight: (): void => {},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
||||||
import AppBar from '@mui/material/AppBar';
|
import AppBar from '@mui/material/AppBar';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
@@ -73,7 +73,7 @@ const navbarItems: Array<NavbarItem> = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function DefaultNavBar() {
|
export function DefaultNavBar() {
|
||||||
const { title, action, override, isCollapsed, setIsCollapsed, setAppBarHeight, navBarWidth } =
|
const { title, action, override, isCollapsed, setIsCollapsed, setAppBarHeight, navBarWidth, setNavBarWidth } =
|
||||||
useContext(NavBarContext);
|
useContext(NavBarContext);
|
||||||
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
@@ -98,6 +98,15 @@ export function DefaultNavBar() {
|
|||||||
);
|
);
|
||||||
const NavBarComponent = useMemo(() => (isMobileWidth ? MobileBottomBar : DesktopSideBar), [isMobileWidth]);
|
const NavBarComponent = useMemo(() => (isMobileWidth ? MobileBottomBar : DesktopSideBar), [isMobileWidth]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!isMobileWidth) {
|
||||||
|
// do not reset navbar width to prevent grid from jumping due to the changing grid item width
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNavBarWidth(0);
|
||||||
|
}, [isMobileWidth]);
|
||||||
|
|
||||||
const navBar = useMemo(
|
const navBar = useMemo(
|
||||||
() => <NavBarComponent navBarItems={visibleNavBarItems} />,
|
() => <NavBarComponent navBarItems={visibleNavBarItems} />,
|
||||||
[NavBarComponent, visibleNavBarItems],
|
[NavBarComponent, visibleNavBarItems],
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export function NavBarContextProvider({ children }: IProps) {
|
|||||||
});
|
});
|
||||||
const [isCollapsed, setIsCollapsed] = useLocalStorage('NavBar::isCollapsed', false);
|
const [isCollapsed, setIsCollapsed] = useLocalStorage('NavBar::isCollapsed', false);
|
||||||
const [navBarWidth, setNavBarWidth] = useState(0);
|
const [navBarWidth, setNavBarWidth] = useState(0);
|
||||||
|
const [readerNavBarWidth, setReaderNavBarWidth] = useState(0);
|
||||||
const [bottomBarHeight, setBottomBarHeight] = useState(0);
|
const [bottomBarHeight, setBottomBarHeight] = useState(0);
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -53,6 +54,8 @@ export function NavBarContextProvider({ children }: IProps) {
|
|||||||
setIsCollapsed,
|
setIsCollapsed,
|
||||||
navBarWidth,
|
navBarWidth,
|
||||||
setNavBarWidth,
|
setNavBarWidth,
|
||||||
|
readerNavBarWidth,
|
||||||
|
setReaderNavBarWidth,
|
||||||
bottomBarHeight,
|
bottomBarHeight,
|
||||||
setBottomBarHeight,
|
setBottomBarHeight,
|
||||||
}),
|
}),
|
||||||
@@ -70,6 +73,8 @@ export function NavBarContextProvider({ children }: IProps) {
|
|||||||
setIsCollapsed,
|
setIsCollapsed,
|
||||||
navBarWidth,
|
navBarWidth,
|
||||||
setNavBarWidth,
|
setNavBarWidth,
|
||||||
|
readerNavBarWidth,
|
||||||
|
setReaderNavBarWidth,
|
||||||
bottomBarHeight,
|
bottomBarHeight,
|
||||||
setBottomBarHeight,
|
setBottomBarHeight,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
|
|||||||
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
|
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import Slide from '@mui/material/Slide';
|
import Slide from '@mui/material/Slide';
|
||||||
@@ -138,7 +138,7 @@ interface IProps {
|
|||||||
|
|
||||||
export function ReaderNavBar(props: IProps) {
|
export function ReaderNavBar(props: IProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setNavBarWidth } = useNavBarContext();
|
const { setReaderNavBarWidth } = useNavBarContext();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation<{
|
const location = useLocation<{
|
||||||
@@ -155,10 +155,10 @@ export function ReaderNavBar(props: IProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setNavBarWidth(navBarRef.current.offsetWidth);
|
setReaderNavBarWidth(navBarRef.current.offsetWidth);
|
||||||
}, [navBarRef.current]),
|
}, [navBarRef.current]),
|
||||||
);
|
);
|
||||||
useEffect(() => () => setNavBarWidth(0), [navBarRef]);
|
useLayoutEffect(() => () => setReaderNavBarWidth(0), [navBarRef]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
settings,
|
settings,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
import { styled, useTheme } from '@mui/material/styles';
|
import { styled, useTheme } from '@mui/material/styles';
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import ListItem from '@mui/material/ListItem';
|
import ListItem from '@mui/material/ListItem';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
@@ -92,7 +92,6 @@ export const DesktopSideBar = ({ navBarItems }: { navBarItems: NavbarItem[] }) =
|
|||||||
ref,
|
ref,
|
||||||
useCallback(() => setNavBarWidth(ref.current?.clientWidth ?? 0), [ref.current]),
|
useCallback(() => setNavBarWidth(ref.current?.clientWidth ?? 0), [ref.current]),
|
||||||
);
|
);
|
||||||
useEffect(() => () => setNavBarWidth(0), []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer variant="permanent" sx={{ width: navBarWidth }}>
|
<Drawer variant="permanent" sx={{ width: navBarWidth }}>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import BottomNavigation from '@mui/material/BottomNavigation';
|
|||||||
import BottomNavigationAction from '@mui/material/BottomNavigationAction';
|
import BottomNavigationAction from '@mui/material/BottomNavigationAction';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { NavbarItem } from '@/typings.ts';
|
import { NavbarItem } from '@/typings.ts';
|
||||||
import { useResizeObserver } from '@/util/useResizeObserver.tsx';
|
import { useResizeObserver } from '@/util/useResizeObserver.tsx';
|
||||||
@@ -27,7 +27,7 @@ export const MobileBottomBar = ({ navBarItems }: { navBarItems: NavbarItem[] })
|
|||||||
ref,
|
ref,
|
||||||
useCallback(() => setBottomBarHeight(ref.current?.clientHeight ?? 0), [ref.current]),
|
useCallback(() => setBottomBarHeight(ref.current?.clientHeight ?? 0), [ref.current]),
|
||||||
);
|
);
|
||||||
useEffect(() => () => setBottomBarHeight(0), []);
|
useLayoutEffect(() => () => setBottomBarHeight(0), []);
|
||||||
|
|
||||||
const [selectedNavBarItem, setSelectedNavBarItem] = useState(
|
const [selectedNavBarItem, setSelectedNavBarItem] = useState(
|
||||||
navBarItems.find((navBarItem) => navBarItem.path === location.pathname)?.path,
|
navBarItems.find((navBarItem) => navBarItem.path === location.pathname)?.path,
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
import Tabs, { TabsProps } from '@mui/material/Tabs';
|
import Tabs, { TabsProps } from '@mui/material/Tabs';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { ForwardedRef, forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
import { ForwardedRef, forwardRef } from 'react';
|
||||||
import { useResizeObserver } from '@/util/useResizeObserver.tsx';
|
|
||||||
import { useNavBarContext } from '@/components/context/NavbarContext.tsx';
|
import { useNavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||||
|
|
||||||
const StyledTabsMenu = styled(Tabs)(({ theme }) => ({
|
const StyledTabsMenu = styled(Tabs)(({ theme }) => ({
|
||||||
@@ -27,33 +26,21 @@ const StyledTabsMenu = styled(Tabs)(({ theme }) => ({
|
|||||||
|
|
||||||
export const TabsMenu = forwardRef(
|
export const TabsMenu = forwardRef(
|
||||||
(
|
(
|
||||||
{ children, tabsCount, ...props }: TabsProps & { tabsCount: number },
|
{ children, tabsCount, sx, ...props }: TabsProps & { tabsCount: number },
|
||||||
ref: ForwardedRef<HTMLDivElement | null>,
|
ref: ForwardedRef<HTMLDivElement | null>,
|
||||||
) => {
|
) => {
|
||||||
const { appBarHeight } = useNavBarContext();
|
const { appBarHeight } = useNavBarContext();
|
||||||
|
|
||||||
const tabsMenuRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
useImperativeHandle(ref, () => tabsMenuRef.current!);
|
|
||||||
const [width, setWidth] = useState<number>();
|
|
||||||
useResizeObserver(
|
|
||||||
tabsMenuRef,
|
|
||||||
useCallback(() => setWidth(tabsMenuRef.current?.clientWidth), [tabsMenuRef.current]),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Visual Hack: 160px is min-width for viewport width of >600
|
|
||||||
const scrollableTabs = !width ? false : width < tabsCount * 160;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTabsMenu
|
<StyledTabsMenu
|
||||||
{...props}
|
sx={{ ...sx, top: appBarHeight }}
|
||||||
sx={{ ...props.sx, top: appBarHeight }}
|
ref={ref}
|
||||||
ref={tabsMenuRef}
|
|
||||||
indicatorColor="primary"
|
indicatorColor="primary"
|
||||||
textColor="primary"
|
textColor="primary"
|
||||||
centered={!scrollableTabs}
|
variant="scrollable"
|
||||||
variant={scrollableTabs ? 'scrollable' : 'fullWidth'}
|
|
||||||
scrollButtons
|
scrollButtons
|
||||||
allowScrollButtonsMobile
|
allowScrollButtonsMobile
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledTabsMenu>
|
</StyledTabsMenu>
|
||||||
|
|||||||
@@ -37,7 +37,13 @@ export function Browse() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsWrapper>
|
<TabsWrapper>
|
||||||
<TabsMenu ref={tabsMenuRef} value={tabNum} tabsCount={2} onChange={(e, newTab) => setTabNum(newTab)}>
|
<TabsMenu
|
||||||
|
ref={tabsMenuRef}
|
||||||
|
variant="fullWidth"
|
||||||
|
value={tabNum}
|
||||||
|
tabsCount={2}
|
||||||
|
onChange={(e, newTab) => setTabNum(newTab)}
|
||||||
|
>
|
||||||
<Tab sx={{ textTransform: 'none' }} label={t('source.title_one')} />
|
<Tab sx={{ textTransform: 'none' }} label={t('source.title_one')} />
|
||||||
<Tab sx={{ textTransform: 'none' }} label={t('extension.title_other')} />
|
<Tab sx={{ textTransform: 'none' }} label={t('extension.title_other')} />
|
||||||
<Tab sx={{ textTransform: 'none' }} label={t('migrate.title')} />
|
<Tab sx={{ textTransform: 'none' }} label={t('migrate.title')} />
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export function Reader() {
|
|||||||
const isLastPage = curPage === chapter.pageCount - 1;
|
const isLastPage = curPage === chapter.pageCount - 1;
|
||||||
const curPageDebounced = useDebounce(curPage, isLastPage ? 0 : 1000);
|
const curPageDebounced = useDebounce(curPage, isLastPage ? 0 : 1000);
|
||||||
const [pageToScrollTo, setPageToScrollTo] = useState<number | undefined>(undefined);
|
const [pageToScrollTo, setPageToScrollTo] = useState<number | undefined>(undefined);
|
||||||
const { setOverride, setTitle, navBarWidth: currentNavBarWidth } = useContext(NavBarContext);
|
const { setOverride, setTitle, readerNavBarWidth: currentNavBarWidth } = useContext(NavBarContext);
|
||||||
const [retrievingNextChapter, setRetrievingNextChapter] = useState(false);
|
const [retrievingNextChapter, setRetrievingNextChapter] = useState(false);
|
||||||
const {
|
const {
|
||||||
data: mangaChaptersData,
|
data: mangaChaptersData,
|
||||||
|
|||||||
Reference in New Issue
Block a user