mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-10 04:39:19 +02:00
fix bugs with timeline scroll restoration
This commit is contained in:
parent
7632bcac63
commit
0396b8d2ad
@ -1,4 +1,4 @@
|
||||
import { ReactNode, memo, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { ReactNode, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Box, Button, Text } from "@chakra-ui/react";
|
||||
import { Kind } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
@ -22,6 +22,8 @@ import {
|
||||
} from "../../../providers/intersection-observer";
|
||||
import BadgeAwardCard from "../../../views/badges/components/badge-award-card";
|
||||
import ArticleNote from "./article-note";
|
||||
import SuperMap from "../../../classes/super-map";
|
||||
import { useDebounce, useThrottle } from "react-use";
|
||||
|
||||
function RenderEvent({ event, visible, minHeight }: { event: NostrEvent; visible: boolean; minHeight?: number }) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
@ -66,11 +68,13 @@ const NOTE_BUFFER = 5;
|
||||
const timelineNoteMinHeightCache = new WeakMap<TimelineLoader, Record<string, Record<string, number>>>();
|
||||
|
||||
function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
const notesArray = useSubject(timeline.timeline);
|
||||
const events = useThrottle(useSubject(timeline.timeline), 100);
|
||||
const [latest, setLatest] = useState(() => dayjs().unix());
|
||||
const { subject } = useIntersectionObserver();
|
||||
|
||||
const location = useLocation();
|
||||
// only update the location key when the timeline changes
|
||||
// this fixes an issue with the key changes when the drawer opens
|
||||
const cachedLocationKey = useMemo(() => location.key, [timeline]);
|
||||
const setCachedNumber = useCallback(
|
||||
(id: string, value: number) => {
|
||||
let timelineData = timelineNoteMinHeightCache.get(timeline);
|
||||
@ -78,96 +82,113 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
timelineData = {};
|
||||
timelineNoteMinHeightCache.set(timeline, timelineData);
|
||||
}
|
||||
if (!timelineData[location.key]) timelineData[location.key] = {};
|
||||
timelineData[location.key][id] = value;
|
||||
if (!timelineData[cachedLocationKey]) timelineData[cachedLocationKey] = {};
|
||||
timelineData[cachedLocationKey][id] = value;
|
||||
},
|
||||
[location.key, timeline],
|
||||
[cachedLocationKey, timeline],
|
||||
);
|
||||
const getCachedNumber = useCallback(
|
||||
(id: string) => {
|
||||
const timelineData = timelineNoteMinHeightCache.get(timeline);
|
||||
if (!timelineData) return undefined;
|
||||
return timelineData[location.key]?.[id] ?? undefined;
|
||||
return timelineData[cachedLocationKey]?.[id] ?? undefined;
|
||||
},
|
||||
[location.key, timeline],
|
||||
);
|
||||
const [maxDate, setMaxDate] = useState(getCachedNumber("max") ?? -Infinity);
|
||||
const [minDate, setMinDate] = useState(
|
||||
getCachedNumber("min") ?? timeline.timeline.value[NOTE_BUFFER]?.created_at ?? 0,
|
||||
[cachedLocationKey, timeline],
|
||||
);
|
||||
const [maxDate, setMaxDate] = useState(getCachedNumber("max") ?? Infinity);
|
||||
const [minDate, setMinDate] = useState(getCachedNumber("min") ?? events[NOTE_BUFFER]?.created_at ?? -Infinity);
|
||||
|
||||
// reset the latest and minDate when timeline changes
|
||||
useEffect(() => {
|
||||
setLatest(dayjs().unix());
|
||||
setMaxDate(getCachedNumber("max") ?? -Infinity);
|
||||
setMaxDate(getCachedNumber("max") ?? Infinity);
|
||||
setMinDate(getCachedNumber("min") ?? timeline.timeline.value[NOTE_BUFFER]?.created_at ?? 0);
|
||||
}, [timeline, setMinDate, setLatest, getCachedNumber]);
|
||||
|
||||
const newNotes: NostrEvent[] = [];
|
||||
const notes: NostrEvent[] = [];
|
||||
for (const note of notesArray) {
|
||||
if (note.created_at > latest) newNotes.push(note);
|
||||
else if (note.created_at > minDate) notes.push(note);
|
||||
}
|
||||
|
||||
const updateNoteMinHeight = useCallback(
|
||||
(id: string, element: Element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const current = getCachedNumber(id);
|
||||
setCachedNumber(id, Math.max(current ?? 0, rect.height));
|
||||
if (rect.height !== current) setCachedNumber(id, Math.max(current ?? 0, rect.height));
|
||||
},
|
||||
[setCachedNumber, getCachedNumber],
|
||||
);
|
||||
|
||||
// TODO: break this out into its own component or hook, this is pretty ugly
|
||||
const [intersectionEntryCache] = useState(() => new Map<string, IntersectionObserverEntry>());
|
||||
const { subject: intersectionSubject } = useIntersectionObserver();
|
||||
const [intersectionEntryCache] = useState(() => new Map<string, boolean>());
|
||||
useEffect(() => {
|
||||
const listener = (entities: ExtendedIntersectionObserverEntry[]) => {
|
||||
for (const entity of entities) {
|
||||
if (entity.id) {
|
||||
intersectionEntryCache.set(entity.id, entity.entry);
|
||||
intersectionEntryCache.set(entity.id, entity.entry.isIntersecting);
|
||||
updateNoteMinHeight(entity.id, entity.entry.target);
|
||||
}
|
||||
}
|
||||
|
||||
let min: number = Infinity;
|
||||
let max: number = -Infinity;
|
||||
let min: number = Infinity;
|
||||
let preload = NOTE_BUFFER;
|
||||
let foundVisible = false;
|
||||
for (const event of timeline.timeline.value) {
|
||||
if (event.created_at > latest) continue;
|
||||
const entry = intersectionEntryCache.get(getEventUID(event));
|
||||
if (!entry || !entry.isIntersecting) {
|
||||
const isIntersecting = intersectionEntryCache.get(getEventUID(event));
|
||||
|
||||
if (!isIntersecting) {
|
||||
if (foundVisible) {
|
||||
// found and event below the view
|
||||
// found an event below the view
|
||||
if (preload-- < 0) break;
|
||||
if (event.created_at < min) min = event.created_at;
|
||||
} else {
|
||||
// found and event above the view
|
||||
// found an event above the view
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// found visible event
|
||||
foundVisible = true;
|
||||
|
||||
const bufferEvent =
|
||||
timeline.timeline.value[Math.max(timeline.timeline.value.indexOf(event) - NOTE_BUFFER)] || event;
|
||||
if (bufferEvent.created_at > max) max = bufferEvent.created_at;
|
||||
const bufferEvent = timeline.timeline.value[timeline.timeline.value.indexOf(event) - NOTE_BUFFER];
|
||||
if (bufferEvent && bufferEvent.created_at > max) max = bufferEvent.created_at;
|
||||
}
|
||||
}
|
||||
|
||||
setMinDate((v) => Math.min(v, min));
|
||||
setMaxDate(max);
|
||||
|
||||
setCachedNumber("max", max);
|
||||
setCachedNumber("min", Math.min(getCachedNumber("min") ?? Infinity, min));
|
||||
if (min !== Infinity) {
|
||||
setMinDate((v) => {
|
||||
const value = Math.min(v, min);
|
||||
setCachedNumber("min", value);
|
||||
return value;
|
||||
});
|
||||
}
|
||||
if (max !== -Infinity) {
|
||||
setMaxDate(max);
|
||||
setCachedNumber("max", max);
|
||||
} else if (foundVisible) {
|
||||
setMaxDate(Infinity);
|
||||
setCachedNumber("max", Infinity);
|
||||
}
|
||||
};
|
||||
|
||||
subject.subscribe(listener);
|
||||
intersectionSubject.subscribe(listener);
|
||||
return () => {
|
||||
subject.unsubscribe(listener);
|
||||
intersectionSubject.unsubscribe(listener);
|
||||
};
|
||||
}, [setMinDate, intersectionEntryCache, updateNoteMinHeight, setCachedNumber, getCachedNumber, latest, timeline]);
|
||||
}, [
|
||||
setMinDate,
|
||||
setMaxDate,
|
||||
intersectionSubject,
|
||||
intersectionEntryCache,
|
||||
updateNoteMinHeight,
|
||||
setCachedNumber,
|
||||
getCachedNumber,
|
||||
latest,
|
||||
]);
|
||||
|
||||
const newNotes: NostrEvent[] = [];
|
||||
const notes: NostrEvent[] = [];
|
||||
for (const note of events) {
|
||||
if (note.created_at > latest) newNotes.push(note);
|
||||
else if (note.created_at >= minDate) notes.push(note);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -36,7 +36,10 @@ export function useRegisterIntersectionEntity(ref: MutableRefObject<Element | nu
|
||||
useEffect(() => {
|
||||
if (observer && ref.current) {
|
||||
observer.observe(ref.current);
|
||||
if (id) setElementId(ref.current, id);
|
||||
if (id) {
|
||||
setElementId(ref.current, id);
|
||||
if (import.meta.env.DEV) ref.current.setAttribute("data-event-id", id);
|
||||
}
|
||||
}
|
||||
}, [observer]);
|
||||
useUnmount(() => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user