diff --git a/pkg/interface/src/logic/lib/useLazyScroll.ts b/pkg/interface/src/logic/lib/useLazyScroll.ts index f9e8c10eb..bd1c31a32 100644 --- a/pkg/interface/src/logic/lib/useLazyScroll.ts +++ b/pkg/interface/src/logic/lib/useLazyScroll.ts @@ -1,5 +1,6 @@ import { useEffect, RefObject, useRef, useState } from "react"; import _ from "lodash"; +import usePreviousValue from "./usePreviousValue"; export function distanceToBottom(el: HTMLElement) { const { scrollTop, scrollHeight, clientHeight } = el; @@ -11,28 +12,41 @@ export function distanceToBottom(el: HTMLElement) { export function useLazyScroll( ref: RefObject, margin: number, + count: number, loadMore: () => Promise ) { const [isDone, setIsDone] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const oldCount = usePreviousValue(count); + const loadUntil = (el: HTMLElement) => { + if (!isDone && distanceToBottom(el) < margin) { + setIsLoading(true); + return loadMore().then((done) => { + setIsLoading(false); + if (done) { + setIsDone(true); + return Promise.resolve(); + } + return loadUntil(el); + }); + } + setIsLoading(false); + return Promise.resolve(); + }; + + useEffect(() => { + if((oldCount > count) && ref.current) { + loadUntil(ref.current); + } + }, [count]); + + useEffect(() => { if (!ref.current) { return; } setIsDone(false); const scroll = ref.current; - const loadUntil = (el: HTMLElement) => { - if (!isDone && distanceToBottom(el) < margin) { - return loadMore().then((done) => { - if (done) { - setIsDone(true); - return Promise.resolve(); - } - return loadUntil(el); - }); - } - return Promise.resolve(); - }; - loadUntil(scroll); const onScroll = (e: Event) => { @@ -40,12 +54,13 @@ export function useLazyScroll( loadUntil(el); }; - ref.current.addEventListener("scroll", onScroll); + ref.current.addEventListener("scroll", onScroll, { passive: true }); return () => { ref.current?.removeEventListener("scroll", onScroll); }; - }, [ref?.current]); + }, [ref?.current, count]); + - return isDone; + return { isDone, isLoading }; } diff --git a/pkg/interface/src/logic/lib/usePreviousValue.ts b/pkg/interface/src/logic/lib/usePreviousValue.ts new file mode 100644 index 000000000..e24097810 --- /dev/null +++ b/pkg/interface/src/logic/lib/usePreviousValue.ts @@ -0,0 +1,20 @@ +import { useRef } from "react"; +import { Primitive } from "~/types"; + + + +export default function usePreviousValue(value: T): T { + const prev = useRef(null); + const curr = useRef(null); + + if (prev?.current !== curr?.current) { + prev.current = curr?.current; + } + + if (curr.current !== value) { + curr.current = value; + } + + return prev.current!; +} + diff --git a/pkg/interface/src/logic/reducers/hark-update.ts b/pkg/interface/src/logic/reducers/hark-update.ts index e35f62f29..6928e4508 100644 --- a/pkg/interface/src/logic/reducers/hark-update.ts +++ b/pkg/interface/src/logic/reducers/hark-update.ts @@ -350,7 +350,12 @@ function archive(json: any, state: HarkState) { const [archived, unarchived] = _.partition(timebox, (idxNotif) => notifIdxEqual(index, idxNotif.index) ); - state.notifications.set(time, unarchived); + if(unarchived.length === 0) { + console.log('deleting entire timebox'); + state.notifications.delete(time); + } else { + state.notifications.set(time, unarchived); + } const newlyRead = archived.filter(x => !x.notification.read).length; updateNotificationStats(state, index, 'notifications', (x) => x - newlyRead); } diff --git a/pkg/interface/src/types/index.ts b/pkg/interface/src/types/index.ts index 549159261..5c1d81b0b 100644 --- a/pkg/interface/src/types/index.ts +++ b/pkg/interface/src/types/index.ts @@ -13,3 +13,4 @@ export * from './metadata-update'; export * from './noun'; export * from './s3-update'; export * from './workspace'; +export * from './util'; diff --git a/pkg/interface/src/types/util.ts b/pkg/interface/src/types/util.ts index 435410fec..2f3d390e5 100644 --- a/pkg/interface/src/types/util.ts +++ b/pkg/interface/src/types/util.ts @@ -1,4 +1,5 @@ import { Icon } from "@tlon/indigo-react"; export type PropFunc any> = Parameters[0]; +export type Primitive = string | number | undefined | symbol | null | boolean; export type IconRef = PropFunc['icon']; diff --git a/pkg/interface/src/views/apps/notifications/inbox.tsx b/pkg/interface/src/views/apps/notifications/inbox.tsx index 21e6ede2a..a5d5cf382 100644 --- a/pkg/interface/src/views/apps/notifications/inbox.tsx +++ b/pkg/interface/src/views/apps/notifications/inbox.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useCallback, useRef, useState } from "react"; import f from "lodash/fp"; import _ from "lodash"; -import { Icon, Col, Center, Row, Box, Text, Anchor, Rule } from "@tlon/indigo-react"; +import { Icon, Col, Center, Row, Box, Text, Anchor, Rule, LoadingSpinner } from "@tlon/indigo-react"; import moment from "moment"; import { Notifications, Rolodex, Timebox, IndexedNotification, Groups, joinProgress, JoinRequests, GroupNotificationsConfig, NotificationGraphConfig } from "~/types"; import { MOMENT_CALENDAR_DATE, daToUnix, resourceAsPath } from "~/logic/lib/util"; @@ -38,6 +38,7 @@ function filterNotification(associations: Associations, groups: string[]) { export default function Inbox(props: { notifications: Notifications; + notificationsSize: number; archive: Notifications; groups: Groups; showArchive?: boolean; @@ -103,7 +104,12 @@ export default function Inbox(props: { return api.hark.getMore(); }, [api]); - const loadedAll = useLazyScroll(scrollRef, 0.2, loadMore); + const { isDone, isLoading } = useLazyScroll( + scrollRef, + 0.2, + _.flatten(notifications).length, + loadMore + ); return ( @@ -126,11 +132,17 @@ export default function Inbox(props: { /> ); })} - {loadedAll && ( + {isDone && (
No more notifications
+ )} + {isLoading && ( +
+ +
)} + ); }