Scroll to top when changing shown data in source browse

The current scroll position was preserved when changing the shown content type (browse, latest, filters) or when changing the filters.

Regression introduced with aab3e25031
This commit is contained in:
schroda
2024-04-16 02:49:08 +02:00
parent 80ad5e06be
commit 1f227ce886
2 changed files with 19 additions and 29 deletions

View File

@@ -126,6 +126,9 @@ const HorizontalGrid = forwardRef(
), ),
); );
export const getGridSnapshotKey = (location: ReturnType<typeof useLocation>) =>
`MangaGrid-snapshot-location-${location.key}`;
const VerticalGrid = forwardRef( const VerticalGrid = forwardRef(
( (
{ {
@@ -148,7 +151,7 @@ const VerticalGrid = forwardRef(
) => { ) => {
const location = useLocation<{ snapshot?: GridStateSnapshot }>(); const location = useLocation<{ snapshot?: GridStateSnapshot }>();
const snapshotSessionKey = `MangaGrid-snapshot-location-${location.key}`; const snapshotSessionKey = getGridSnapshotKey(location);
const [snapshot] = useSessionStorage<GridStateSnapshot | undefined>(snapshotSessionKey, undefined); const [snapshot] = useSessionStorage<GridStateSnapshot | undefined>(snapshotSessionKey, undefined);
const persistGridStateTimeout = useRef<NodeJS.Timeout | undefined>(); const persistGridStateTimeout = useRef<NodeJS.Timeout | undefined>();

View File

@@ -38,6 +38,8 @@ import {
import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx'; import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts'; import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
import { useSessionStorage } from '@/util/useStorage.tsx'; import { useSessionStorage } from '@/util/useStorage.tsx';
import { AppStorage } from '@/util/AppStorage.ts';
import { getGridSnapshotKey } from '@/components/MangaGrid.tsx';
const ContentTypeMenu = styled('div')(({ theme }) => ({ const ContentTypeMenu = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
@@ -218,7 +220,8 @@ export function SourceMangas() {
const { sourceId } = useParams<{ sourceId: string }>(); const { sourceId } = useParams<{ sourceId: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const { key: locationKey, state: locationState } = useLocation(); const location = useLocation();
const { key: locationKey, state: locationState } = location;
const { contentType: initialContentType = SourceContentType.POPULAR, clearCache = false } = const { contentType: initialContentType = SourceContentType.POPULAR, clearCache = false } =
useLocation<{ useLocation<{
contentType: SourceContentType; contentType: SourceContentType;
@@ -248,7 +251,6 @@ export function SourceMangas() {
currentFiltersToApply ?? [], currentFiltersToApply ?? [],
); );
const [dialogFiltersToApply, setDialogFiltersToApply] = useState<IPos[]>(filtersToApply); const [dialogFiltersToApply, setDialogFiltersToApply] = useState<IPos[]>(filtersToApply);
const [resetScrollPosition, setResetScrollPosition] = useState(false);
const [currentContentType, setCurrentContentType] = useSessionStorage<SourceContentType | undefined>( const [currentContentType, setCurrentContentType] = useSessionStorage<SourceContentType | undefined>(
`source-mangas-${sourceId}-content-type`, `source-mangas-${sourceId}-content-type`,
initialContentType, initialContentType,
@@ -266,9 +268,15 @@ export function SourceMangas() {
[sourceId], [sourceId],
); );
const scrollToTop = useCallback(() => {
AppStorage.session.setItem(getGridSnapshotKey(location), undefined);
window.scrollTo(0, 0);
}, [locationKey]);
const setFiltersToApply = (filters: IPos[]) => { const setFiltersToApply = (filters: IPos[]) => {
setCurrentFiltersToApply(filters); setCurrentFiltersToApply(filters);
setLocationFiltersToApply(filters); setLocationFiltersToApply(filters);
scrollToTop();
}; };
const setContentType = (newContentType: SourceContentType) => { const setContentType = (newContentType: SourceContentType) => {
@@ -300,7 +308,7 @@ export function SourceMangas() {
const updateContentType = useCallback( const updateContentType = useCallback(
(newContentType: SourceContentType, newSearch?: string | null) => { (newContentType: SourceContentType, newSearch?: string | null) => {
setContentType(newContentType); setContentType(newContentType);
setResetScrollPosition(true); scrollToTop();
if (query && !newSearch) { if (query && !newSearch) {
navigate( navigate(
@@ -313,16 +321,7 @@ export function SourceMangas() {
); );
} }
}, },
[setContentType, query], [setContentType, query, scrollToTop],
);
const updateLocationFilters = useCallback(
(updatedFilters: IPos[]) => {
if (contentType === SourceContentType.SEARCH) {
setFiltersToApply(updatedFilters);
}
},
[contentType, query],
); );
const setSearchContentType = !!query && contentType !== SourceContentType.SEARCH; const setSearchContentType = !!query && contentType !== SourceContentType.SEARCH;
@@ -338,12 +337,10 @@ export function SourceMangas() {
loadPage(lastPageNum + 1); loadPage(lastPageNum + 1);
}, [lastPageNum, hasNextPage, contentType]); }, [lastPageNum, hasNextPage, contentType]);
const resetFilters = useCallback(async () => { const resetFilters = useCallback(() => {
setDialogFiltersToApply([]); setDialogFiltersToApply([]);
setFiltersToApply([]); setFiltersToApply([]);
updateLocationFilters([]); }, [sourceId, contentType]);
setResetScrollPosition(true);
}, [sourceId, contentType, updateLocationFilters]);
useEffect(() => { useEffect(() => {
if (filteredOutAllItemsOfFetchedPage && hasNextPage && !loading) { if (filteredOutAllItemsOfFetchedPage && hasNextPage && !loading) {
@@ -373,7 +370,7 @@ export function SourceMangas() {
// with strict mode + dev mode the first request will be aborted. due to using SWR there won't be an // with strict mode + dev mode the first request will be aborted. due to using SWR there won't be an
// immediate second request since it's the same key. instead the "second" request will be the error handling of SWR // immediate second request since it's the same key. instead the "second" request will be the error handling of SWR
abortRequest(new Error(`SourceMangas(${sourceId}): search string changed`)); abortRequest(new Error(`SourceMangas(${sourceId}): search string changed`));
setResetScrollPosition(true); scrollToTop();
}, },
[query], [query],
); );
@@ -406,15 +403,6 @@ export function SourceMangas() {
}; };
}, [t, source]); }, [t, source]);
useEffect(() => {
if (!resetScrollPosition) {
return;
}
window.scrollTo(0, 0);
setResetScrollPosition(false);
}, [resetScrollPosition]);
return ( return (
<StyledGridWrapper hasContent={!!mangas.length}> <StyledGridWrapper hasContent={!!mangas.length}>
<ContentTypeMenu> <ContentTypeMenu>
@@ -459,7 +447,6 @@ export function SourceMangas() {
updateFilterValue={setDialogFiltersToApply} updateFilterValue={setDialogFiltersToApply}
setTriggerUpdate={() => { setTriggerUpdate={() => {
setFiltersToApply(dialogFiltersToApply); setFiltersToApply(dialogFiltersToApply);
updateLocationFilters(dialogFiltersToApply);
}} }}
resetFilterValue={resetFilters} resetFilterValue={resetFilters}
update={dialogFiltersToApply} update={dialogFiltersToApply}