Merge branch 'fix-tl-jump' into 'main'

Fix home timeline jump when navigating back

See merge request soapbox-pub/soapbox!2772
environments/review-main-yi2y9f/deployments/4080
Alex Gleason 2023-10-02 18:35:56 +00:00
commit 702124fb79
3 zmienionych plików z 69 dodań i 54 usunięć

Wyświetl plik

@ -1,10 +1,8 @@
import clsx from 'clsx';
import throttle from 'lodash/throttle';
import React, { useState, useEffect, useCallback } from 'react';
import { useIntl, MessageDescriptor } from 'react-intl';
import Icon from 'soapbox/components/icon';
import { Text } from 'soapbox/components/ui';
import { Icon, Text } from 'soapbox/components/ui';
import { useSettings } from 'soapbox/hooks';
interface IScrollTopButton {
@ -31,67 +29,77 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
const intl = useIntl();
const settings = useSettings();
// Whether we are scrolled past the `threshold`.
const [scrolled, setScrolled] = useState<boolean>(false);
const autoload = settings.get('autoloadTimelines') === true;
// Whether we are scrolled above the `autoloadThreshold`.
const [scrolledTop, setScrolledTop] = useState<boolean>(false);
const autoload = settings.get('autoloadTimelines') === true;
const visible = count > 0 && scrolled;
const classes = clsx('fixed left-1/2 top-20 z-50 -translate-x-1/2', {
'hidden': !visible,
});
/** Number of pixels scrolled down from the top of the page. */
const getScrollTop = (): number => {
return (document.scrollingElement || document.documentElement).scrollTop;
};
const maybeUnload = () => {
if (autoload && getScrollTop() <= autoloadThreshold) {
/** Unload feed items if scrolled to the top. */
const maybeUnload = useCallback(() => {
if (autoload && scrolledTop) {
onClick();
}
};
}, [autoload, scrolledTop, onClick]);
/** Set state while scrolling. */
const handleScroll = useCallback(throttle(() => {
maybeUnload();
const scrollTop = getScrollTop();
if (getScrollTop() > threshold) {
setScrolled(true);
} else {
setScrolled(false);
}
}, 150, { trailing: true }), [autoload, threshold, autoloadThreshold, onClick]);
setScrolled(scrollTop > threshold);
setScrolledTop(scrollTop <= autoloadThreshold);
const scrollUp = () => {
}, 150, { trailing: true }), [threshold, autoloadThreshold]);
/** Scroll to top and trigger `onClick`. */
const handleClick: React.MouseEventHandler = useCallback(() => {
window.scrollTo({ top: 0 });
};
const handleClick: React.MouseEventHandler = () => {
setTimeout(scrollUp, 10);
onClick();
};
}, [onClick]);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
// Delay adding the scroll listener so navigating back doesn't
// unload feed items before the feed is rendered.
setTimeout(() => {
window.addEventListener('scroll', handleScroll);
handleScroll();
}, 250);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [onClick]);
}, [handleScroll]);
useEffect(() => {
maybeUnload();
}, [count]);
}, [maybeUnload]);
if (!visible) {
return null;
}
return (
<div className={classes}>
<a className='flex cursor-pointer items-center space-x-1.5 whitespace-nowrap rounded-full bg-primary-600 px-4 py-2 text-white transition-transform hover:scale-105 hover:bg-primary-700 active:scale-100' onClick={handleClick}>
<Icon src={require('@tabler/icons/arrow-bar-to-up.svg')} />
<div className='fixed left-1/2 top-20 z-50 -translate-x-1/2'>
<button
className='flex cursor-pointer items-center space-x-1.5 whitespace-nowrap rounded-full bg-primary-600 px-4 py-2 text-white transition-transform hover:scale-105 hover:bg-primary-700 active:scale-100'
onClick={handleClick}
>
<Icon
className='h-4 w-4'
src={require('@tabler/icons/arrow-bar-to-up.svg')}
/>
{(count > 0) && (
<Text theme='inherit' size='sm'>
{intl.formatMessage(message, { count })}
</Text>
)}
</a>
<Text theme='inherit' size='sm'>
{intl.formatMessage(message, { count })}
</Text>
</button>
</div>
);
};

Wyświetl plik

@ -14,7 +14,7 @@ import { getSettings } from 'soapbox/actions/settings';
import PullToRefresh from 'soapbox/components/pull-to-refresh';
import ScrollTopButton from 'soapbox/components/scroll-top-button';
import ScrollableList from 'soapbox/components/scrollable-list';
import { Column } from 'soapbox/components/ui';
import { Column, Portal } from 'soapbox/components/ui';
import PlaceholderNotification from 'soapbox/features/placeholder/components/placeholder-notification';
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
@ -104,13 +104,13 @@ const Notifications = () => {
});
};
const handleDequeueNotifications = () => {
const handleDequeueNotifications = useCallback(() => {
dispatch(dequeueNotifications());
};
}, []);
const handleRefresh = () => {
const handleRefresh = useCallback(() => {
return dispatch(expandNotifications());
};
}, []);
useEffect(() => {
handleDequeueNotifications();
@ -176,11 +176,15 @@ const Notifications = () => {
return (
<Column ref={column} label={intl.formatMessage(messages.title)} withHeader={false}>
{filterBarContainer}
<ScrollTopButton
onClick={handleDequeueNotifications}
count={totalQueuedNotificationsCount}
message={messages.queue}
/>
<Portal>
<ScrollTopButton
onClick={handleDequeueNotifications}
count={totalQueuedNotificationsCount}
message={messages.queue}
/>
</Portal>
<PullToRefresh onRefresh={handleRefresh}>
{scrollContainer}
</PullToRefresh>

Wyświetl plik

@ -6,6 +6,7 @@ import { defineMessages } from 'react-intl';
import { dequeueTimeline, scrollTopTimeline } from 'soapbox/actions/timelines';
import ScrollTopButton from 'soapbox/components/scroll-top-button';
import StatusList, { IStatusList } from 'soapbox/components/status-list';
import { Portal } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import { makeGetStatusIds } from 'soapbox/selectors';
@ -37,9 +38,9 @@ const Timeline: React.FC<ITimeline> = ({
const hasMore = useAppSelector(state => state.timelines.get(timelineId)?.hasMore === true);
const totalQueuedItemsCount = useAppSelector(state => state.timelines.get(timelineId)?.totalQueuedItemsCount || 0);
const handleDequeueTimeline = () => {
const handleDequeueTimeline = useCallback(() => {
dispatch(dequeueTimeline(timelineId, onLoadMore));
};
}, []);
const handleScrollToTop = useCallback(debounce(() => {
dispatch(scrollTopTimeline(timelineId, true));
@ -51,12 +52,14 @@ const Timeline: React.FC<ITimeline> = ({
return (
<>
<ScrollTopButton
key='timeline-queue-button-header'
onClick={handleDequeueTimeline}
count={totalQueuedItemsCount}
message={messages.queue}
/>
<Portal>
<ScrollTopButton
key='timeline-queue-button-header'
onClick={handleDequeueTimeline}
count={totalQueuedItemsCount}
message={messages.queue}
/>
</Portal>
<StatusList
timelineId={timelineId}