New experiment: auto refresh

This commit is contained in:
Lim Chee Aun 2023-05-05 17:53:16 +08:00
parent 51c03fb5be
commit 711842916d
6 changed files with 105 additions and 29 deletions

View file

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { InView } from 'react-intersection-observer'; import { InView } from 'react-intersection-observer';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
@ -35,6 +35,7 @@ function Timeline({
allowFilters, allowFilters,
refresh, refresh,
}) { }) {
const snapStates = useSnapshot(states);
const [items, setItems] = useState([]); const [items, setItems] = useState([]);
const [uiState, setUIState] = useState('default'); const [uiState, setUIState] = useState('default');
const [showMore, setShowMore] = useState(false); const [showMore, setShowMore] = useState(false);
@ -203,41 +204,51 @@ function Timeline({
} }
}, [nearReachEnd, showMore]); }, [nearReachEnd, showMore]);
const isHovering = useRef(false);
const loadOrCheckUpdates = useCallback(
async ({ disableHoverCheck = false } = {}) => {
console.log('✨ Load or check updates', snapStates.settings.autoRefresh);
if (
snapStates.settings.autoRefresh &&
scrollableRef.current.scrollTop === 0 &&
(disableHoverCheck || !isHovering.current) &&
!inBackground()
) {
console.log('✨ Load updates', snapStates.settings.autoRefresh);
loadItems(true);
} else {
console.log('✨ Check updates', snapStates.settings.autoRefresh);
const hasUpdate = await checkForUpdates();
if (hasUpdate) {
console.log('✨ Has new updates', id);
setShowNew(true);
}
}
},
[id, loadItems, checkForUpdates, snapStates.settings.autoRefresh],
);
const lastHiddenTime = useRef(); const lastHiddenTime = useRef();
usePageVisibility( usePageVisibility(
(visible) => { (visible) => {
if (visible) { if (visible) {
const timeDiff = Date.now() - lastHiddenTime.current; const timeDiff = Date.now() - lastHiddenTime.current;
if (!lastHiddenTime.current || timeDiff > 1000 * 60) { if (!lastHiddenTime.current || timeDiff > 1000 * 60) {
(async () => { loadOrCheckUpdates({
console.log('✨ Check updates'); disableHoverCheck: true,
const hasUpdate = await checkForUpdates(); });
if (hasUpdate) {
console.log('✨ Has new updates', id);
setShowNew(true);
}
})();
} }
} else { } else {
lastHiddenTime.current = Date.now(); lastHiddenTime.current = Date.now();
} }
setVisible(visible); setVisible(visible);
}, },
[checkForUpdates], [checkForUpdates, loadOrCheckUpdates, snapStates.settings.autoRefresh],
); );
// checkForUpdates interval // checkForUpdates interval
useInterval( useInterval(
() => { loadOrCheckUpdates,
(async () => {
console.log('✨ Check updates');
const hasUpdate = await checkForUpdates();
if (hasUpdate) {
console.log('✨ Has new updates', id);
setShowNew(true);
}
})();
},
visible && !showNew ? checkForUpdatesInterval : null, visible && !showNew ? checkForUpdatesInterval : null,
); );
@ -254,6 +265,12 @@ function Timeline({
oRef.current = node; oRef.current = node;
}} }}
tabIndex="-1" tabIndex="-1"
onPointerEnter={(e) => {
isHovering.current = true;
}}
onPointerLeave={() => {
isHovering.current = false;
}}
> >
<div class="timeline-deck deck"> <div class="timeline-deck deck">
<header <header
@ -597,4 +614,8 @@ function TimelineStatusCompact({ status, instance }) {
); );
} }
function inBackground() {
return !!document.querySelector('.deck-backdrop, #modal-container > *');
}
export default Timeline; export default Timeline;

View file

@ -1,7 +1,7 @@
import './notifications.css'; import './notifications.css';
import { memo } from 'preact/compat'; import { memo } from 'preact/compat';
import { useEffect, useRef, useState } from 'preact/hooks'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
import Icon from '../components/icon'; import Icon from '../components/icon';
@ -95,6 +95,33 @@ function Notifications() {
} }
}, [nearReachEnd, showMore]); }, [nearReachEnd, showMore]);
const isHovering = useRef(false);
const loadUpdates = useCallback(() => {
console.log('✨ Load updates', {
autoRefresh: snapStates.settings.autoRefresh,
scrollTop: scrollableRef.current?.scrollTop === 0,
isHovering: isHovering.current,
inBackground: inBackground(),
notificationsShowNew: snapStates.notificationsShowNew,
uiState,
});
if (
snapStates.settings.autoRefresh &&
scrollableRef.current?.scrollTop === 0 &&
!isHovering.current &&
!inBackground() &&
snapStates.notificationsShowNew &&
uiState !== 'loading'
) {
loadNotifications(true);
}
}, [
snapStates.notificationsShowNew,
snapStates.settings.autoRefresh,
uiState,
]);
useEffect(loadUpdates, [snapStates.notificationsShowNew]);
const todayDate = new Date(); const todayDate = new Date();
const yesterdayDate = new Date(todayDate - 24 * 60 * 60 * 1000); const yesterdayDate = new Date(todayDate - 24 * 60 * 60 * 1000);
let currentDay = new Date(); let currentDay = new Date();
@ -110,6 +137,14 @@ function Notifications() {
class="deck-container" class="deck-container"
ref={scrollableRef} ref={scrollableRef}
tabIndex="-1" tabIndex="-1"
onPointerEnter={() => {
console.log('👆 Pointer enter');
isHovering.current = true;
}}
onPointerLeave={() => {
console.log('👇 Pointer leave');
isHovering.current = false;
}}
> >
<div class={`timeline-deck deck ${onlyMentions ? 'only-mentions' : ''}`}> <div class={`timeline-deck deck ${onlyMentions ? 'only-mentions' : ''}`}>
<header <header
@ -245,4 +280,8 @@ function Notifications() {
); );
} }
function inBackground() {
return !!document.querySelector('.deck-backdrop, #modal-container > *');
}
export default memo(Notifications); export default memo(Notifications);

View file

@ -147,6 +147,18 @@ function Settings({ onClose }) {
<h3>Experiments</h3> <h3>Experiments</h3>
<section> <section>
<ul> <ul>
<li>
<label>
<input
type="checkbox"
checked={snapStates.settings.autoRefresh}
onChange={(e) => {
states.settings.autoRefresh = e.target.checked;
}}
/>{' '}
Auto refresh timeline posts
</label>
</li>
<li> <li>
<label> <label>
<input <input

View file

@ -41,6 +41,7 @@ const states = proxy({
shortcuts: store.account.get('shortcuts') ?? [], shortcuts: store.account.get('shortcuts') ?? [],
// Settings // Settings
settings: { settings: {
autoRefresh: store.account.get('settings-autoRefresh') ?? false,
shortcutsViewMode: store.account.get('settings-shortcutsViewMode') ?? null, shortcutsViewMode: store.account.get('settings-shortcutsViewMode') ?? null,
shortcutsColumnsMode: shortcutsColumnsMode:
store.account.get('settings-shortcutsColumnsMode') ?? false, store.account.get('settings-shortcutsColumnsMode') ?? false,
@ -64,6 +65,9 @@ subscribeKey(states, 'notificationsLast', (v) => {
subscribe(states, (changes) => { subscribe(states, (changes) => {
console.debug('STATES change', changes); console.debug('STATES change', changes);
for (const [action, path, value, prevValue] of changes) { for (const [action, path, value, prevValue] of changes) {
if (path.join('.') === 'settings.autoRefresh') {
store.account.set('settings-autoRefresh', !!value);
}
if (path.join('.') === 'settings.boostsCarousel') { if (path.join('.') === 'settings.boostsCarousel') {
store.account.set('settings-boostsCarousel', !!value); store.account.set('settings-boostsCarousel', !!value);
} }

View file

@ -1,13 +1,10 @@
import { useEffect, useRef } from 'preact/hooks'; import { useEffect, useRef } from 'preact/hooks';
const noop = () => {}; function useInterval(fn, delay, deps, immediate) {
const savedCallback = useRef(fn);
function useInterval(callback, delay, immediate) {
const savedCallback = useRef(noop);
useEffect(() => { useEffect(() => {
savedCallback.current = callback; savedCallback.current = fn;
}, []); }, [deps]);
useEffect(() => { useEffect(() => {
if (!immediate || delay === null || delay === false) return; if (!immediate || delay === null || delay === false) return;

View file

@ -1,7 +1,10 @@
import { useEffect, useRef } from 'preact/hooks'; import { useEffect, useRef } from 'preact/hooks';
export default function usePageVisibility(fn = () => {}, deps = []) { export default function usePageVisibility(fn = () => {}, deps = []) {
const savedCallback = useRef(fn, deps); const savedCallback = useRef(fn);
useEffect(() => {
savedCallback.current = fn;
}, [deps]);
useEffect(() => { useEffect(() => {
const handleVisibilityChange = () => { const handleVisibilityChange = () => {