Skip to content

Commit

Permalink
Merge pull request #3624 from tloncorp/hm/more-eager-clearing
Browse files Browse the repository at this point in the history
unreads: clear lingering unread markers
  • Loading branch information
arthyn authored Jun 14, 2024
2 parents e81dd43 + 9daa132 commit 2ca2d84
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 231 deletions.
84 changes: 38 additions & 46 deletions apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function getUnreadDisplay(
id: string,
thread: Unread | undefined
): 'none' | 'top' | 'thread' | 'top-with-thread' {
const isTop = unread?.lastUnread?.id === id;
const isTop = unread?.lastUnread?.id === id && unread.status !== 'read';

// if this message is the oldest unread in the main chat,
// and has an unread thread, show the divider and thread indicator
Expand Down Expand Up @@ -207,55 +207,47 @@ const ChatMessage = React.memo<
() => isMessageHidden || isPostHidden,
[isMessageHidden, isPostHidden]
);
const { ref: viewRef } = useInView({

const { ref: viewRef, inView } = useInView({
threshold: 1,
onChange: useCallback(
(inView: boolean) => {
// if no tracked unread we don't need to take any action
if (!unread) {
return;
}
});

const unseen = unread.status === 'unread';
/* the first fire of this function
which we don't to do anything with. */
if (!inView && unseen) {
return;
}
useEffect(() => {
if (!inView || !unread) {
return;
}

const { seen: markSeen, delayedRead } = useUnreadsStore.getState();
/* once the unseen marker comes into view we need to mark it
as seen and start a timer to mark it read so it goes away.
we ensure that the brief matches and hasn't changed before
doing so. we don't want to accidentally clear unreads when
the state has changed
*/
if (
inView &&
(unreadDisplay === 'top' ||
unreadDisplay === 'top-with-thread') &&
unseen
) {
markSeen(unreadsKey);
delayedRead(unreadsKey, () => {
if (isDMOrMultiDM) {
markDmRead();
} else {
markReadChannel();
}
});
const unseen = unread.status === 'unread';
const { seen: markSeen, delayedRead } = useUnreadsStore.getState();
/* once the unseen marker comes into view we need to mark it
as seen and start a timer to mark it read so it goes away.
we ensure that the brief matches and hasn't changed before
doing so. we don't want to accidentally clear unreads when
the state has changed
*/
if (
inView &&
(unreadDisplay === 'top' || unreadDisplay === 'top-with-thread') &&
unseen
) {
markSeen(unreadsKey);
delayedRead(unreadsKey, () => {
if (isDMOrMultiDM) {
markDmRead();
} else {
markReadChannel();
}
},
[
unreadDisplay,
unread,
unreadsKey,
isDMOrMultiDM,
markReadChannel,
markDmRead,
]
),
});
});
}
}, [
inView,
unread,
unreadsKey,
unreadDisplay,
isDMOrMultiDM,
markReadChannel,
markDmRead,
]);

const cacheId = {
author: window.our,
Expand Down
34 changes: 11 additions & 23 deletions apps/tlon-web/src/chat/ChatThread/ChatThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
useRouteGroup,
useVessel,
} from '@/state/groups/groups';
import { useUnread, useUnreadsStore } from '@/state/unreads';
import { unreadStoreLogger, useUnread, useUnreadsStore } from '@/state/unreads';

import ChatScrollerPlaceholder from '../ChatScroller/ChatScrollerPlaceholder';
import { chatStoreLogger, useChatStore } from '../useChatStore';
Expand Down Expand Up @@ -133,11 +133,7 @@ export default function ChatThread() {
const { compatible, text } = useChannelCompatibility(`chat/${flag}`);
const { paddingBottom } = useBottomPadding();
const readTimeout = useUnread(chatUnreadsKey)?.readTimeout;
const clearOnNavRef = useRef({
readTimeout,
chatUnreadsKey,
markRead,
});
const path = location.pathname;
const activeTab = useActiveTab();

const returnURL = useCallback(
Expand Down Expand Up @@ -175,24 +171,16 @@ export default function ChatThread() {

// read the messages once navigated away
useEffect(() => {
clearOnNavRef.current = {
readTimeout,
chatUnreadsKey,
markRead,
};
}, [readTimeout, chatUnreadsKey, markRead]);

useEffect(
() => () => {
const curr = clearOnNavRef.current;
if (curr.readTimeout !== undefined && curr.readTimeout !== 0) {
chatStoreLogger.log('unmount read from thread');
useUnreadsStore.getState().read(curr.chatUnreadsKey);
curr.markRead();
return () => {
const winPath = window.location.pathname.replace('/apps/groups', '');
if (winPath !== path && readTimeout) {
unreadStoreLogger.log(winPath, path);
unreadStoreLogger.log('marking read from dismount', chatUnreadsKey);
useUnreadsStore.getState().read(chatUnreadsKey);
markRead();
}
},
[]
);
};
}, [path, readTimeout, chatUnreadsKey, markRead]);

useEffect(() => {
if (!idTimeIsNumber) {
Expand Down
33 changes: 18 additions & 15 deletions apps/tlon-web/src/chat/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import React, {
useRef,
useState,
} from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from 'react-router-dom';
import { VirtuosoHandle } from 'react-virtuoso';

import ChatScroller from '@/chat/ChatScroller/ChatScroller';
Expand All @@ -17,7 +22,7 @@ import ArrowS16Icon from '@/components/icons/ArrowS16Icon';
import { useChannelCompatibility, useMarkChannelRead } from '@/logic/channel';
import { log } from '@/logic/utils';
import { useInfinitePosts } from '@/state/channel/channel';
import { useUnread, useUnreadsStore } from '@/state/unreads';
import { unreadStoreLogger, useUnread, useUnreadsStore } from '@/state/unreads';

import ChatScrollerPlaceholder from './ChatScroller/ChatScrollerPlaceholder';
import UnreadAlerts from './UnreadAlerts';
Expand All @@ -38,6 +43,7 @@ const ChatWindow = React.memo(function ChatWindowRaw({
scrollElementRef,
isScrolling,
}: ChatWindowProps) {
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const { idTime } = useParams();
const scrollToId = useMemo(
Expand Down Expand Up @@ -65,7 +71,7 @@ const ChatWindow = React.memo(function ChatWindowRaw({
const [showUnreadBanner, setShowUnreadBanner] = useState(false);
const unreadsKey = getKey(whom);
const readTimeout = useUnread(unreadsKey)?.readTimeout;
const clearOnNavRef = useRef({ readTimeout, nest, unreadsKey, markRead });
const path = location.pathname;
const { compatible } = useChannelCompatibility(nest);
const navigate = useNavigate();
const latestMessageIndex = messages.length - 1;
Expand Down Expand Up @@ -165,19 +171,16 @@ const ChatWindow = React.memo(function ChatWindowRaw({

// read the messages once navigated away
useEffect(() => {
clearOnNavRef.current = { readTimeout, nest, unreadsKey, markRead };
}, [readTimeout, nest, unreadsKey, markRead]);

useEffect(
() => () => {
const curr = clearOnNavRef.current;
if (curr.readTimeout !== undefined && curr.readTimeout !== 0) {
useUnreadsStore.getState().read(curr.unreadsKey);
curr.markRead();
return () => {
const winPath = window.location.pathname.replace('/apps/groups', '');
if (winPath !== path && readTimeout) {
unreadStoreLogger.log(winPath, path);
unreadStoreLogger.log('marking read from dismount', unreadsKey);
useUnreadsStore.getState().read(unreadsKey);
markRead();
}
},
[]
);
};
}, [path, readTimeout, unreadsKey, markRead]);

useEffect(() => {
const doRefetch = async () => {
Expand Down
4 changes: 1 addition & 3 deletions apps/tlon-web/src/chat/UnreadAlerts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ActivitySummary, getKey } from '@tloncorp/shared/dist/urbit/activity';
import { getKey } from '@tloncorp/shared/dist/urbit/activity';
import { daToUnix } from '@urbit/api';
import bigInt from 'big-integer';
import { format, isToday } from 'date-fns';
Expand All @@ -11,8 +11,6 @@ import { pluralize, whomIsFlag } from '@/logic/utils';
import { useMarkDmReadMutation } from '@/state/chat';
import { useUnread, useUnreadsStore } from '@/state/unreads';

import { useChatStore } from './useChatStore';

interface UnreadAlertsProps {
whom: string;
root: string;
Expand Down
48 changes: 19 additions & 29 deletions apps/tlon-web/src/dms/DMThread.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { MessageKey, getKey } from '@tloncorp/shared/dist/urbit/activity';
import {
MessageKey,
getKey,
getThreadKey,
} from '@tloncorp/shared/dist/urbit/activity';
import { ReplyTuple } from '@tloncorp/shared/dist/urbit/channel';
import { formatUd } from '@urbit/aura';
import bigInt from 'big-integer';
Expand Down Expand Up @@ -32,14 +36,14 @@ import keyMap from '@/keyMap';
import { useDragAndDrop } from '@/logic/DragAndDropContext';
import { useBottomPadding } from '@/logic/position';
import { useIsScrolling } from '@/logic/scroll';
import useMedia, { useIsMobile } from '@/logic/useMedia';
import { useIsMobile } from '@/logic/useMedia';
import {
useMarkDmReadMutation,
useMultiDm,
useSendReplyMutation,
useWrit,
} from '@/state/chat';
import { useUnread, useUnreadsStore } from '@/state/unreads';
import { unreadStoreLogger, useUnread, useUnreadsStore } from '@/state/unreads';

export default function DMThread() {
const { ship, idTime, idShip } = useParams<{
Expand Down Expand Up @@ -68,23 +72,17 @@ export default function DMThread() {
const scrollElementRef = useRef<HTMLDivElement>(null);
const isScrolling = useIsScrolling(scrollElementRef);
const { paddingBottom } = useBottomPadding();
const unreadsKey = getKey(whom);
const unreadsKey = getThreadKey(whom, id);
const readTimeout = useUnread(unreadsKey)?.readTimeout;
const { markDmRead } = useMarkDmReadMutation(whom);
const isSmall = useMedia('(max-width: 1023px)');
const clearOnNavRef = useRef({
isSmall,
readTimeout,
unreadsKey,
markDmRead,
});
const msgKey: MessageKey = useMemo(
() => ({
id,
time: formatUd(bigInt(time)),
}),
[id, time]
);
const { markDmRead } = useMarkDmReadMutation(whom, msgKey);
const path = location.pathname;

const isClub = ship ? (ob.isValidPatp(ship) ? false : true) : false;
const club = useMultiDm(ship || '');
Expand Down Expand Up @@ -138,24 +136,16 @@ export default function DMThread() {

// read the messages once navigated away
useEffect(() => {
clearOnNavRef.current = { isSmall, readTimeout, unreadsKey, markDmRead };
}, [readTimeout, unreadsKey, isSmall, markDmRead]);

useEffect(
() => () => {
const curr = clearOnNavRef.current;
if (
curr.isSmall &&
curr.readTimeout !== undefined &&
curr.readTimeout !== 0
) {
chatStoreLogger.log('unmount read from thread');
useUnreadsStore.getState().read(curr.unreadsKey);
curr.markDmRead();
return () => {
const winPath = window.location.pathname.replace('/apps/groups', '');
if (winPath !== path && readTimeout) {
unreadStoreLogger.log(winPath, path);
unreadStoreLogger.log('marking read from dismount', unreadsKey);
useUnreadsStore.getState().read(unreadsKey);
markDmRead();
}
},
[]
);
};
}, [path, readTimeout, unreadsKey, markDmRead]);

if (!writ || isLoading) return null;

Expand Down
33 changes: 18 additions & 15 deletions apps/tlon-web/src/dms/DmWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { WritTuple } from '@tloncorp/shared/dist/urbit/dms';
import { udToDec } from '@urbit/api';
import bigInt from 'big-integer';
import { ReactElement, useCallback, useEffect, useMemo, useRef } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from 'react-router-dom';
import { VirtuosoHandle } from 'react-virtuoso';

import ChatScroller from '@/chat/ChatScroller/ChatScroller';
Expand All @@ -14,7 +19,7 @@ import ArrowS16Icon from '@/components/icons/ArrowS16Icon';
import { useIsScrolling } from '@/logic/scroll';
import { getPatdaParts, log } from '@/logic/utils';
import { useInfiniteDMs, useMarkDmReadMutation } from '@/state/chat';
import { useUnread, useUnreadsStore } from '@/state/unreads';
import { unreadStoreLogger, useUnread, useUnreadsStore } from '@/state/unreads';

interface DmWindowProps {
whom: string;
Expand All @@ -33,6 +38,7 @@ export default function DmWindow({
root,
prefixedElement,
}: DmWindowProps) {
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const { idTime } = useParams();
const scrollToId = useMemo(
Expand All @@ -45,7 +51,7 @@ export default function DmWindow({
const scrollElementRef = useRef<HTMLDivElement>(null);
const isScrolling = useIsScrolling(scrollElementRef);
const { markDmRead } = useMarkDmReadMutation(whom);
const clearOnNavRef = useRef({ readTimeout, unreadsKey, markDmRead });
const path = location.pathname;

const {
writs,
Expand Down Expand Up @@ -138,19 +144,16 @@ export default function DmWindow({

// read the messages once navigated away
useEffect(() => {
clearOnNavRef.current = { readTimeout, unreadsKey, markDmRead };
}, [readTimeout, unreadsKey, markDmRead]);

useEffect(
() => () => {
const curr = clearOnNavRef.current;
if (curr.readTimeout !== undefined && curr.readTimeout !== 0) {
useUnreadsStore.getState().read(curr.unreadsKey);
curr.markDmRead();
return () => {
const winPath = window.location.pathname.replace('/apps/groups', '');
if (winPath !== path && readTimeout) {
unreadStoreLogger.log(winPath, path);
unreadStoreLogger.log('marking read from dismount', unreadsKey);
useUnreadsStore.getState().read(unreadsKey);
markDmRead();
}
},
[]
);
};
}, [path, readTimeout, unreadsKey, markDmRead]);

useEffect(() => {
const doRefetch = async () => {
Expand Down
Loading

0 comments on commit 2ca2d84

Please sign in to comment.