Restore virtuoso states on browser back navigation
- manga chapter list - library duplicates - updates - history - download queue
This commit is contained in:
38
src/lib/virtuoso/Component/GroupedVirtuosoPersisted.tsx
Normal file
38
src/lib/virtuoso/Component/GroupedVirtuosoPersisted.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { ComponentProps, useRef } from 'react';
|
||||
import { GroupedVirtuoso, GroupedVirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||
import { useMergedRef } from '@mantine/hooks';
|
||||
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
|
||||
|
||||
export const GroupedVirtuosoPersisted = ({
|
||||
ref: passedRef,
|
||||
persistKey,
|
||||
...props
|
||||
}: ComponentProps<typeof GroupedVirtuoso> & { persistKey: string }) => {
|
||||
const { state, persistState } = VirtuosoUtil.usePersistState<StateSnapshot>(persistKey);
|
||||
|
||||
const localRef = useRef<GroupedVirtuosoHandle>(undefined);
|
||||
const ref = useMergedRef(localRef, passedRef);
|
||||
|
||||
return (
|
||||
<GroupedVirtuoso
|
||||
{...props}
|
||||
ref={ref}
|
||||
isScrolling={(isScrolling) => {
|
||||
if (!isScrolling) {
|
||||
localRef.current?.getState(persistState);
|
||||
}
|
||||
|
||||
props.isScrolling?.(isScrolling);
|
||||
}}
|
||||
restoreStateFrom={state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
25
src/lib/virtuoso/Component/VirtuosoGridPersisted.tsx
Normal file
25
src/lib/virtuoso/Component/VirtuosoGridPersisted.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { ComponentProps, useRef } from 'react';
|
||||
import { GridStateSnapshot, VirtuosoGrid, VirtuosoGridHandle } from 'react-virtuoso';
|
||||
import { useMergedRef } from '@mantine/hooks';
|
||||
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
|
||||
|
||||
export const VirtuosoGridPersisted = ({
|
||||
ref: passedRef,
|
||||
persistKey,
|
||||
...props
|
||||
}: ComponentProps<typeof VirtuosoGrid> & { persistKey: string }) => {
|
||||
const { state, persistState } = VirtuosoUtil.usePersistState<GridStateSnapshot>(persistKey);
|
||||
|
||||
const localRef = useRef<VirtuosoGridHandle>(undefined);
|
||||
const ref = useMergedRef(localRef, passedRef);
|
||||
|
||||
return <VirtuosoGrid {...props} ref={ref} stateChanged={persistState} restoreStateFrom={state} />;
|
||||
};
|
||||
38
src/lib/virtuoso/Component/VirtuosoPersisted.tsx
Normal file
38
src/lib/virtuoso/Component/VirtuosoPersisted.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { ComponentProps, useRef } from 'react';
|
||||
import { StateSnapshot, Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
||||
import { useMergedRef } from '@mantine/hooks';
|
||||
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
|
||||
|
||||
export const VirtuosoPersisted = ({
|
||||
ref: passedRef,
|
||||
persistKey,
|
||||
...props
|
||||
}: ComponentProps<typeof Virtuoso> & { persistKey: string }) => {
|
||||
const { state, persistState } = VirtuosoUtil.usePersistState<StateSnapshot>(persistKey);
|
||||
|
||||
const localRef = useRef<VirtuosoHandle>(undefined);
|
||||
const ref = useMergedRef(localRef, passedRef);
|
||||
|
||||
return (
|
||||
<Virtuoso
|
||||
{...props}
|
||||
ref={ref}
|
||||
isScrolling={(isScrolling) => {
|
||||
if (!isScrolling) {
|
||||
localRef.current?.getState(persistState);
|
||||
}
|
||||
|
||||
props.isScrolling?.(isScrolling);
|
||||
}}
|
||||
restoreStateFrom={state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,10 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useSessionStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
|
||||
export class VirtuosoUtil {
|
||||
static readonly GROUP = 0;
|
||||
@@ -112,4 +115,46 @@ export class VirtuosoUtil {
|
||||
[convertIndex, getGroupKey, getNormalKey],
|
||||
);
|
||||
}
|
||||
|
||||
static usePersistState<Snapshot>(key: string): {
|
||||
key: string;
|
||||
state: Snapshot | undefined;
|
||||
persistState: (state: Snapshot) => void;
|
||||
deleteState: () => void;
|
||||
} {
|
||||
const location = useLocation<{ snapshot?: Snapshot }>();
|
||||
|
||||
const snapshotSessionKey = `virtuoso-snapshot-${key}-${location.key}`;
|
||||
const [snapshot] = useSessionStorage<Snapshot | undefined>(snapshotSessionKey, undefined);
|
||||
|
||||
const persistGridStateTimeout = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const persistState = useCallback(
|
||||
(state: Snapshot) => {
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
clearTimeout(persistGridStateTimeout.current);
|
||||
persistGridStateTimeout.current = setTimeout(() => {
|
||||
const didLocationChange = currentUrl !== window.location.href;
|
||||
if (didLocationChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppStorage.session.setItem(snapshotSessionKey, state, false);
|
||||
}, 250);
|
||||
},
|
||||
[snapshotSessionKey],
|
||||
);
|
||||
const deleteState = useCallback(() => {
|
||||
AppStorage.session.setItem(snapshotSessionKey, undefined, false);
|
||||
}, [snapshotSessionKey]);
|
||||
|
||||
useEffect(() => clearTimeout(persistGridStateTimeout.current), [location.key, persistGridStateTimeout.current]);
|
||||
|
||||
return {
|
||||
key: snapshotSessionKey,
|
||||
state: snapshot,
|
||||
persistState,
|
||||
deleteState,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import Stack from '@mui/material/Stack';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ComponentProps, useCallback, useMemo, useState } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { ResumeFab } from '@/modules/manga/components/ResumeFAB.tsx';
|
||||
@@ -45,6 +44,7 @@ import { shouldForwardProp } from '@/modules/core/utils/ShouldForwardProp.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||
import { ChapterListCard } from '@/modules/chapter/components/cards/ChapterListCard.tsx';
|
||||
import { VirtuosoPersisted } from '@/lib/virtuoso/Component/VirtuosoPersisted.tsx';
|
||||
|
||||
type ChapterListHeaderProps = {
|
||||
scrollbarWidth: number;
|
||||
@@ -61,7 +61,7 @@ const ChapterListHeader = styled(Stack, {
|
||||
}));
|
||||
|
||||
type StyledVirtuosoProps = { topOffset: number };
|
||||
const StyledVirtuoso = styled(Virtuoso, {
|
||||
const StyledVirtuoso = styled(VirtuosoPersisted, {
|
||||
shouldForwardProp: shouldForwardProp<StyledVirtuosoProps>(['topOffset']),
|
||||
})<StyledVirtuosoProps>(({ theme, topOffset }) => ({
|
||||
listStyle: 'none',
|
||||
@@ -234,6 +234,7 @@ export const ChapterList = ({
|
||||
)}
|
||||
|
||||
<StyledVirtuoso
|
||||
persistKey={`manga-${manga.id}-chapter-list`}
|
||||
topOffset={appBarHeight + chapterListHeaderHeight}
|
||||
style={{
|
||||
// override Virtuoso default values and set them with class
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { ContextProp, GroupedVirtuoso, TopItemListProps } from 'react-virtuoso';
|
||||
import { ContextProp, TopItemListProps } from 'react-virtuoso';
|
||||
import { ComponentProps, useMemo } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||
import { GroupedVirtuosoPersisted } from '@/lib/virtuoso/Component/GroupedVirtuosoPersisted.tsx';
|
||||
|
||||
const StickyVirtuosoHeaderWithOffset =
|
||||
(topOffset: number) =>
|
||||
@@ -23,7 +24,7 @@ export const StyledGroupedVirtuoso = ({
|
||||
heightToSubtract = 0,
|
||||
style,
|
||||
...props
|
||||
}: ComponentProps<typeof GroupedVirtuoso> & { heightToSubtract?: number }) => {
|
||||
}: ComponentProps<typeof GroupedVirtuosoPersisted> & { heightToSubtract?: number }) => {
|
||||
const { appBarHeight, bottomBarHeight } = useNavBarContext();
|
||||
|
||||
const TopItemList = useMemo(
|
||||
@@ -32,7 +33,7 @@ export const StyledGroupedVirtuoso = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<GroupedVirtuoso
|
||||
<GroupedVirtuosoPersisted
|
||||
useWindowScroll
|
||||
{...props}
|
||||
components={{
|
||||
|
||||
@@ -13,7 +13,6 @@ import IconButton from '@mui/material/IconButton';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DeleteSweepIcon from '@mui/icons-material/DeleteSweep';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { closestCenter, DndContext, DragEndEvent } from '@dnd-kit/core';
|
||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||
import { useWindowEvent } from '@mantine/hooks';
|
||||
@@ -32,6 +31,7 @@ import { DownloadQueueChapterCard } from '@/modules/downloads/components/Downloa
|
||||
import { useAppTitle } from '@/modules/navigation-bar/hooks/useAppTitle.ts';
|
||||
import { useAppAction } from '@/modules/navigation-bar/hooks/useAppAction.ts';
|
||||
import { ChapterDownloadStatus } from '@/modules/chapter/Chapter.types.ts';
|
||||
import { VirtuosoPersisted } from '@/lib/virtuoso/Component/VirtuosoPersisted.tsx';
|
||||
|
||||
export const DownloadQueue: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -159,7 +159,8 @@ export const DownloadQueue: React.FC = () => {
|
||||
onDragAbort={() => setDndActiveDownload(null)}
|
||||
>
|
||||
<SortableContext items={dndItems} strategy={verticalListSortingStrategy}>
|
||||
<Virtuoso
|
||||
<VirtuosoPersisted
|
||||
persistKey="download-queue"
|
||||
useWindowScroll
|
||||
overscan={window.innerHeight * 0.5}
|
||||
totalCount={queue.length}
|
||||
|
||||
@@ -290,6 +290,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
||||
return (
|
||||
<>
|
||||
<StyledGroupedVirtuoso
|
||||
persistKey="extensions"
|
||||
heightToSubtract={tabsMenuHeight}
|
||||
overscan={window.innerHeight * 0.5}
|
||||
groupCounts={groupCounts}
|
||||
|
||||
@@ -79,6 +79,7 @@ export const History: React.FC = () => {
|
||||
|
||||
return (
|
||||
<StyledGroupedVirtuoso
|
||||
persistKey="history"
|
||||
components={{
|
||||
Footer: () => (isLoading ? <LoadingPlaceholder usePadding /> : null),
|
||||
}}
|
||||
|
||||
@@ -140,6 +140,7 @@ export const LibraryDuplicates = () => {
|
||||
if (gridLayout === GridLayout.List) {
|
||||
return (
|
||||
<StyledGroupedVirtuoso
|
||||
persistKey="library-duplicates"
|
||||
groupCounts={mangasCountByTitle}
|
||||
groupContent={(index) => (
|
||||
<StyledGroupHeader isFirstItem={index === 0}>
|
||||
|
||||
@@ -10,7 +10,6 @@ import React, {
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
@@ -19,22 +18,20 @@ import React, {
|
||||
} from 'react';
|
||||
import Grid, { GridTypeMap } from '@mui/material/Grid';
|
||||
import Box, { BoxProps } from '@mui/material/Box';
|
||||
import { GridItemProps, GridStateSnapshot, VirtuosoGrid } from 'react-virtuoso';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { GridItemProps } from 'react-virtuoso';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||
import { LoadingPlaceholder } from '@/modules/core/components/feedback/LoadingPlaceholder.tsx';
|
||||
import { MangaCard } from '@/modules/manga/components/cards/MangaCard.tsx';
|
||||
import { useSessionStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/modules/core/components/buttons/StyledFab.tsx';
|
||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
import { MangaCardProps } from '@/modules/manga/Manga.types.ts';
|
||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
|
||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
||||
import { VirtuosoGridPersisted } from '@/lib/virtuoso/Component/VirtuosoGridPersisted.tsx';
|
||||
|
||||
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
|
||||
<Grid {...props} ref={ref} container spacing={1}>
|
||||
@@ -138,8 +135,7 @@ const HorizontalGrid = forwardRef(
|
||||
),
|
||||
);
|
||||
|
||||
export const getGridSnapshotKey = (location: ReturnType<typeof useLocation>) =>
|
||||
`MangaGrid-snapshot-location-${location.key}`;
|
||||
export const MANGA_GRID_SNAPSHOT_KEY = 'MangaGrid-snapshot-location';
|
||||
|
||||
const VerticalGrid = forwardRef(
|
||||
(
|
||||
@@ -160,69 +156,45 @@ const VerticalGrid = forwardRef(
|
||||
loadMore: () => void;
|
||||
},
|
||||
ref: ForwardedRef<HTMLDivElement | null>,
|
||||
) => {
|
||||
const location = useLocation<{ snapshot?: GridStateSnapshot }>();
|
||||
|
||||
const snapshotSessionKey = getGridSnapshotKey(location);
|
||||
const [snapshot] = useSessionStorage<GridStateSnapshot | undefined>(snapshotSessionKey, undefined);
|
||||
|
||||
const persistGridStateTimeout = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const persistGridState = (gridState: GridStateSnapshot) => {
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
clearTimeout(persistGridStateTimeout.current);
|
||||
persistGridStateTimeout.current = setTimeout(() => {
|
||||
const didLocationChange = currentUrl !== window.location.href;
|
||||
if (didLocationChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppStorage.session.setItem(snapshotSessionKey, gridState, false);
|
||||
}, 250);
|
||||
};
|
||||
useEffect(() => clearTimeout(persistGridStateTimeout.current), [location.key, persistGridStateTimeout.current]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box ref={ref}>
|
||||
<VirtuosoGrid
|
||||
useWindowScroll
|
||||
increaseViewportBy={window.innerHeight * 0.5}
|
||||
totalCount={mangas.length}
|
||||
components={{
|
||||
List: GridContainer,
|
||||
Item: GridItemContainer,
|
||||
}}
|
||||
restoreStateFrom={snapshot}
|
||||
stateChanged={persistGridState}
|
||||
endReached={() => loadMore()}
|
||||
computeItemKey={(index) => mangas[index].id}
|
||||
itemContent={(index) =>
|
||||
createMangaCard(
|
||||
mangas[index],
|
||||
gridLayout,
|
||||
inLibraryIndicator,
|
||||
isSelectModeActive,
|
||||
selectedMangaIds,
|
||||
handleSelection,
|
||||
mode,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
{/* render div to prevent UI jumping around when showing/hiding loading placeholder */
|
||||
/* eslint-disable-next-line no-nested-ternary */}
|
||||
{isSelectModeActive && gridLayout === GridLayout.List ? (
|
||||
<Box sx={{ paddingBottom: DEFAULT_FULL_FAB_HEIGHT }} />
|
||||
) : // eslint-disable-next-line no-nested-ternary
|
||||
isLoading ? (
|
||||
<LoadingPlaceholder />
|
||||
) : hasNextPage ? (
|
||||
<div style={{ height: '75px' }} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
},
|
||||
) => (
|
||||
<>
|
||||
<Box ref={ref}>
|
||||
<VirtuosoGridPersisted
|
||||
persistKey={MANGA_GRID_SNAPSHOT_KEY}
|
||||
useWindowScroll
|
||||
increaseViewportBy={window.innerHeight * 0.5}
|
||||
totalCount={mangas.length}
|
||||
components={{
|
||||
List: GridContainer,
|
||||
Item: GridItemContainer,
|
||||
}}
|
||||
endReached={() => loadMore()}
|
||||
computeItemKey={(index) => mangas[index].id}
|
||||
itemContent={(index) =>
|
||||
createMangaCard(
|
||||
mangas[index],
|
||||
gridLayout,
|
||||
inLibraryIndicator,
|
||||
isSelectModeActive,
|
||||
selectedMangaIds,
|
||||
handleSelection,
|
||||
mode,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
{/* render div to prevent UI jumping around when showing/hiding loading placeholder */
|
||||
/* eslint-disable-next-line no-nested-ternary */}
|
||||
{isSelectModeActive && gridLayout === GridLayout.List ? (
|
||||
<Box sx={{ paddingBottom: DEFAULT_FULL_FAB_HEIGHT }} />
|
||||
) : // eslint-disable-next-line no-nested-ternary
|
||||
isLoading ? (
|
||||
<LoadingPlaceholder />
|
||||
) : hasNextPage ? (
|
||||
<div style={{ height: '75px' }} />
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
);
|
||||
|
||||
export interface IMangaGridProps
|
||||
|
||||
@@ -40,8 +40,7 @@ import {
|
||||
useMetadataServerSettings,
|
||||
} from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
||||
import { useLocalStorage, useSessionStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
import { getGridSnapshotKey } from '@/modules/manga/components/MangaGrid.tsx';
|
||||
import { MANGA_GRID_SNAPSHOT_KEY } from '@/modules/manga/components/MangaGrid.tsx';
|
||||
import { createUpdateSourceMetadata, useGetSourceMetadata } from '@/modules/source/services/SourceMetadata.ts';
|
||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||
import { GET_SOURCE_BROWSE } from '@/lib/graphql/queries/SourceQuery.ts';
|
||||
@@ -57,6 +56,7 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { Sources } from '@/modules/source/services/Sources.ts';
|
||||
import { useAppTitleAndAction } from '@/modules/navigation-bar/hooks/useAppTitleAndAction.ts';
|
||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
|
||||
|
||||
const DEFAULT_SOURCE: SourceIdInfo = { id: '-1' };
|
||||
|
||||
@@ -246,10 +246,12 @@ export function SourceMangas() {
|
||||
query ? SourceContentType.SEARCH : currentContentType!,
|
||||
);
|
||||
|
||||
const { key: persistedGridStateKey, deleteState: deletePersistedGridState } =
|
||||
VirtuosoUtil.usePersistState(MANGA_GRID_SNAPSHOT_KEY);
|
||||
const scrollToTop = useCallback(() => {
|
||||
AppStorage.session.setItem(getGridSnapshotKey(location), undefined, false);
|
||||
deletePersistedGridState();
|
||||
window.scrollTo(0, 0);
|
||||
}, [locationKey]);
|
||||
}, [persistedGridStateKey]);
|
||||
|
||||
const currentQuery = useRef(query);
|
||||
const currentAbortRequest = useRef<(reason: any) => void>(() => {});
|
||||
|
||||
@@ -114,6 +114,7 @@ export const Updates: React.FC = () => {
|
||||
})}
|
||||
</Typography>
|
||||
<StyledGroupedVirtuoso
|
||||
persistKey="updates"
|
||||
heightToSubtract={lastUpdateTimestampCompHeight}
|
||||
components={{
|
||||
Footer: () => (isLoading ? <LoadingPlaceholder usePadding /> : null),
|
||||
|
||||
Reference in New Issue
Block a user