Commit 8230c266 authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Determine most recent highlighted annotation by its updated date

parent 962f69eb
...@@ -187,6 +187,7 @@ export default function ThreadList({ threads }: ThreadListProps) { ...@@ -187,6 +187,7 @@ export default function ThreadList({ threads }: ThreadListProps) {
const store = useSidebarStore(); const store = useSidebarStore();
const editing = store.countDrafts() > 0; const editing = store.countDrafts() > 0;
const highlightedAnnotations = store.highlightedAnnotations(); const highlightedAnnotations = store.highlightedAnnotations();
const allAnnotations = store.allAnnotations();
// Get the `$tag` of the most recently created unsaved annotation. // Get the `$tag` of the most recently created unsaved annotation.
const newAnnotationTag = (() => { const newAnnotationTag = (() => {
...@@ -211,20 +212,32 @@ export default function ThreadList({ threads }: ThreadListProps) { ...@@ -211,20 +212,32 @@ export default function ThreadList({ threads }: ThreadListProps) {
} }
}, [store, newAnnotationTag]); }, [store, newAnnotationTag]);
const mostRecentlyHighlightedAnnotationId = useMemo( const mostRecentHighlightedAnnotationId = useMemo(() => {
// If multiple highlighted annotations exist, assume that the last one in const highlightedAnnos = allAnnotations.filter(
// the list is the most recent. anno => anno.id && highlightedAnnotations.includes(anno.id),
() => highlightedAnnotations[highlightedAnnotations.length - 1], );
[highlightedAnnotations], // Get the annotation with most recent updated field, which contains a
); // date in ISO format. This means their alphabetical and chronological
// orders match.
const mostRecentHighlightedAnnotation =
highlightedAnnos.reduce<Annotation | null>(
(mostRecent, current) =>
!mostRecent || mostRecent.updated < current.updated
? current
: mostRecent,
null,
);
return mostRecentHighlightedAnnotation?.id;
}, [allAnnotations, highlightedAnnotations]);
// Scroll to the most recently highlighted annotation, unless creating/editing // Scroll to the most recently highlighted annotation, unless creating/editing
// another annotation // another annotation
useEffect(() => { useEffect(() => {
if (!editing && mostRecentlyHighlightedAnnotationId) { if (!editing && mostRecentHighlightedAnnotationId) {
setScrollToId(mostRecentlyHighlightedAnnotationId); setScrollToId(mostRecentHighlightedAnnotationId);
} }
}, [editing, mostRecentlyHighlightedAnnotationId]); }, [editing, mostRecentHighlightedAnnotationId]);
// Effect to scroll a particular thread into view. This is mainly used to // Effect to scroll a particular thread into view. This is mainly used to
// scroll a newly created annotation into view. // scroll a newly created annotation into view.
......
...@@ -57,6 +57,7 @@ describe('ThreadList', () => { ...@@ -57,6 +57,7 @@ describe('ThreadList', () => {
unsavedAnnotations: sinon.stub().returns([]), unsavedAnnotations: sinon.stub().returns([]),
countDrafts: sinon.stub().returns(0), countDrafts: sinon.stub().returns(0),
highlightedAnnotations: sinon.stub().returns([]), highlightedAnnotations: sinon.stub().returns([]),
allAnnotations: sinon.stub().returns([]),
}; };
fakeTopThread = { fakeTopThread = {
...@@ -193,13 +194,65 @@ describe('ThreadList', () => { ...@@ -193,13 +194,65 @@ describe('ThreadList', () => {
assert.notCalled(fakeScrollTop); assert.notCalled(fakeScrollTop);
}); });
it('should set the scroll container `scrollTop` to first highlighted annotation', () => { [
fakeStore.highlightedAnnotations.returns(['t2', 't3']); {
createComponent(); annotationUpdates: {
t2: '2024-01-01T10:40:00',
t3: '2024-01-01T10:41:00', // Most recent
t4: '2024-01-01T10:39:00',
},
// The most recent highlighted annotation is the third.
// At default height (200) should be at 400px.
expectedScrollTop: 400,
},
{
annotationUpdates: {
t1: '2024-01-01T10:42:00', // Most recent
t3: '2024-01-01T10:39:00',
t4: '2024-01-01T10:39:00',
},
// The most recent highlighted annotation is the first.
// At default height (200) should be at 0px.
expectedScrollTop: 0,
},
{
annotationUpdates: {
t1: '2024-01-01T10:42:00',
t3: '2024-01-01T10:39:00',
t4: '2024-01-01T10:51:00', // Most recent
},
// The most recent highlighted annotation is the fourth.
// At default height (200) should be at 600px.
expectedScrollTop: 600,
},
{
annotationUpdates: {
t1: '2024-01-01T10:42:00',
t3: '2024-01-01T10:39:00',
t2: '2024-01-01T10:51:00', // Most recent
},
// The most recent highlighted annotation is the second.
// At default height (200) should be at 200px.
expectedScrollTop: 200,
},
].forEach(({ annotationUpdates, expectedScrollTop }) => {
it('should set the scroll container `scrollTop` to most recent highlighted annotation', () => {
fakeStore.highlightedAnnotations.returns(
Object.keys(annotationUpdates),
);
fakeStore.allAnnotations.returns([
{}, // Discarded
{ id: 't1', updated: annotationUpdates.t1 },
{ id: 't2', updated: annotationUpdates.t2 },
{ id: 't3', updated: annotationUpdates.t3 },
{ id: 't4', updated: annotationUpdates.t4 },
{ id: 't5', updated: annotationUpdates.t5 },
]);
createComponent();
// The last highlighted annotation is the third in the collection of assert.calledWith(fakeScrollTop, expectedScrollTop);
// threads. At default height (200) should be at 400px. });
assert.calledWith(fakeScrollTop, 400);
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment