Skip to content

Commit

Permalink
Merge pull request #3778 from tloncorp/hm/fix-thread-updates
Browse files Browse the repository at this point in the history
activity: re-enable auto marking active chats/threads
  • Loading branch information
arthyn authored Jul 22, 2024
2 parents a3fae3f + 1a45423 commit eedcb14
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 159 deletions.
41 changes: 17 additions & 24 deletions apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ export interface ChatScrollerProps {
parent?: MessageKey;
messages: PostTuple[] | WritTuple[] | ReplyTuple[];
isBroadcast?: boolean;
onAtTop?: () => void;
onAtBottom?: () => void;
onAtTopChange?: (atBottom: boolean) => void;
onAtBottomChange?: (atBottom: boolean) => void;
isLoadingOlder: boolean;
isLoadingNewer: boolean;
replying?: boolean;
Expand All @@ -208,8 +208,8 @@ export default function ChatScroller({
parent,
messages,
isBroadcast,
onAtTop,
onAtBottom,
onAtTopChange,
onAtBottomChange,
isLoadingOlder,
isLoadingNewer,
replying = false,
Expand Down Expand Up @@ -505,26 +505,19 @@ export default function ChatScroller({

// Load more items when list reaches the top or bottom.
useEffect(() => {
if (isLoadingOlder || isLoadingNewer || !userHasScrolled) return;

if (isAtTop && !hasLoadedOldest) {
logger.log('triggering onAtTop');
onAtTop?.();
} else if (isAtBottom) {
logger.log('triggering onAtBottom');
onAtBottom?.();
}
}, [
isLoadingOlder,
isLoadingNewer,
hasLoadedNewest,
hasLoadedOldest,
isAtTop,
isAtBottom,
onAtTop,
onAtBottom,
userHasScrolled,
]);
if (isLoadingNewer || !userHasScrolled) return;

logger.log('triggering onAtBottomChange');
onAtBottomChange?.(isAtBottom);
}, [isLoadingNewer, userHasScrolled, isAtBottom, onAtBottomChange]);

// Load more items when list reaches the top or bottom.
useEffect(() => {
if (isLoadingOlder || !userHasScrolled) return;

logger.log('triggering onAtTop');
onAtTopChange?.(isAtTop);
}, [isLoadingOlder, isAtTop, userHasScrolled, onAtTopChange]);

// When the list inverts, we need to flip the scroll position in order to appear to stay in the same place.
// We do this here as opposed to in an effect so that virtualItems is correct in time for this render.
Expand Down
25 changes: 21 additions & 4 deletions apps/tlon-web/src/chat/ChatThread/ChatThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ export default function ChatThread() {
[chName, chShip, name, ship, activeTab]
);

const onAtBottom = useCallback(() => {
const { bottom } = useChatStore.getState();
bottom(true);
const onAtBottom = useCallback((atBottom: boolean) => {
console.log('thread bottom called', atBottom);
const { threadBottom } = useChatStore.getState();
threadBottom(atBottom);
}, []);

const onEscape = useCallback(
Expand All @@ -157,6 +158,22 @@ export default function ChatThread() {
);
useEventListener('keydown', onEscape, threadRef);

useEffect(() => {
useChatStore.getState().threadBottom(true);

return () => {
useChatStore.getState().threadBottom(false);
};
}, []);

useEffect(() => {
useChatStore.getState().setCurrentThread(msgKey);

return () => {
useChatStore.getState().setCurrentThread(null);
};
}, [msgKey]);

useEffect(() => {
if (!idTimeIsNumber) {
navigate(returnURLWithoutMsg());
Expand Down Expand Up @@ -309,7 +326,7 @@ export default function ChatThread() {
isScrolling={isScrolling}
hasLoadedNewest={false}
hasLoadedOldest={false}
onAtBottom={onAtBottom}
onAtBottomChange={onAtBottom}
/>
)}
</div>
Expand Down
52 changes: 26 additions & 26 deletions apps/tlon-web/src/chat/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ import React, {
useRef,
useState,
} from 'react';
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from 'react-router-dom';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { VirtuosoHandle } from 'react-virtuoso';

import ChatScroller from '@/chat/ChatScroller/ChatScroller';
Expand Down Expand Up @@ -66,7 +61,6 @@ const ChatWindow = React.memo(function ChatWindowRaw({
isFetchingNextPage,
isFetchingPreviousPage,
} = useInfinitePosts(nest, scrollToId);
const { markRead } = useMarkChannelRead(nest);
const scrollerRef = useRef<VirtuosoHandle>(null);
const fetchingNewest =
isFetching && (!isFetchingNextPage || !isFetchingPreviousPage);
Expand Down Expand Up @@ -124,28 +118,34 @@ const ChatWindow = React.memo(function ChatWindowRaw({
]);

useEffect(() => {
useChatStore.getState().setCurrent(whom);
useChatStore.getState().setCurrent({ whom, group: flag });

return () => {
useChatStore.getState().setCurrent('');
useChatStore.getState().setCurrent(null);
};
}, [whom]);
}, [whom, flag]);

const onAtBottom = useCallback(() => {
const { bottom } = useChatStore.getState();
bottom(true);
if (hasPreviousPage && !isFetching) {
log('fetching previous page');
fetchPreviousPage();
}
}, [markRead, fetchPreviousPage, hasPreviousPage, isFetching]);
const onAtBottom = useCallback(
(atBottom: boolean) => {
const { bottom } = useChatStore.getState();
bottom(atBottom);
if (atBottom && hasPreviousPage && !isFetching) {
log('fetching previous page');
fetchPreviousPage();
}
},
[fetchPreviousPage, hasPreviousPage, isFetching]
);

const onAtTop = useCallback(() => {
if (hasNextPage && !isFetching) {
log('fetching next page');
fetchNextPage();
}
}, [fetchNextPage, hasNextPage, isFetching]);
const onAtTop = useCallback(
(atTop: boolean) => {
if (atTop && hasNextPage && !isFetching) {
log('fetching next page');
fetchNextPage();
}
},
[fetchNextPage, hasNextPage, isFetching]
);

/**
* we want to show unread banner after messages have had a chance to
Expand Down Expand Up @@ -232,8 +232,8 @@ const ChatWindow = React.memo(function ChatWindowRaw({
topLoadEndMarker={prefixedElement}
scrollTo={scrollToId ? bigInt(scrollToId) : undefined}
scrollerRef={scrollerRef}
onAtTop={onAtTop}
onAtBottom={onAtBottom}
onAtTopChange={onAtTop}
onAtBottomChange={onAtBottom}
scrollElementRef={scrollElementRef}
isScrolling={isScrolling}
hasLoadedOldest={!hasNextPage}
Expand Down
40 changes: 36 additions & 4 deletions apps/tlon-web/src/chat/useChatStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { ActivitySummary } from '@tloncorp/shared/dist/urbit/activity';
import {
ActivitySummary,
MessageKey,
} from '@tloncorp/shared/dist/urbit/activity';
import { Block } from '@tloncorp/shared/dist/urbit/channel';
import produce from 'immer';
import { useCallback } from 'react';
Expand All @@ -14,12 +17,21 @@ export interface ChatInfo {
failedToLoadContent: Record<string, Record<number, boolean>>;
}

interface CurrentChat {
whom: string;
group?: string;
}

type CurrentChatThread = MessageKey;

export interface ChatStore {
chats: {
[flag: string]: ChatInfo;
};
atBottom: boolean;
current: string;
atThreadBottom: boolean;
current: CurrentChat | null;
currentThread: CurrentChatThread | null;
reply: (flag: string, msgId: string | null) => void;
setBlocks: (whom: string, blocks: Block[]) => void;
setDialogs: (
Expand All @@ -35,7 +47,9 @@ export interface ChatStore {
) => void;
setHovering: (whom: string, writId: string, hovering: boolean) => void;
bottom: (atBottom: boolean) => void;
setCurrent: (whom: string) => void;
threadBottom: (atBottom: boolean) => void;
setCurrent: (current: CurrentChat | null) => void;
setCurrentThread: (current: CurrentChatThread | null) => void;
}

const emptyInfo: () => ChatInfo = () => ({
Expand All @@ -55,8 +69,10 @@ export function isUnread(unread: ActivitySummary): boolean {

export const useChatStore = create<ChatStore>((set, get) => ({
chats: {},
current: '',
current: null,
currentThread: null,
atBottom: false,
atThreadBottom: false,
setBlocks: (whom, blocks) => {
set(
produce((draft) => {
Expand Down Expand Up @@ -136,6 +152,22 @@ export const useChatStore = create<ChatStore>((set, get) => ({
})
);
},
setCurrentThread: (current) => {
set(
produce((draft) => {
chatStoreLogger.log('currentThread', current);
draft.currentThread = current;
})
);
},
threadBottom: (atBottom) => {
set(
produce((draft) => {
chatStoreLogger.log('atThreadBottom', atBottom);
draft.atThreadBottom = atBottom;
})
);
},
bottom: (atBottom) => {
set(
produce((draft) => {
Expand Down
47 changes: 47 additions & 0 deletions apps/tlon-web/src/components/Settings/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { useMutation } from '@tanstack/react-query';
import { ActivityAction, ActivityUpdate } from '@tloncorp/shared/dist/urbit';
import cn from 'classnames';
import { useCallback } from 'react';
import { Link, useLocation } from 'react-router-dom';

import api from '@/api';
import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner';
import { useIsMobile } from '@/logic/useMedia';
import { activityAction } from '@/state/activity';
import {
Theme,
useCalm,
Expand Down Expand Up @@ -44,9 +50,50 @@ export default function Settings() {
usePutEntryMutation({ bucket: window.desk, key: 'logActivity' });
const { mutate: resetAnalyticsId, status: resetAnalyticsIdStatus } =
useResetAnalyticsIdMutation();
const { mutate: markRead, isLoading } = useMutation({
mutationFn: async () => {
await api.trackedPoke<ActivityAction, ActivityUpdate>(
activityAction({
read: {
source: { base: null },
action: { all: { time: null, deep: true } },
},
}),
{ app: 'activity', path: '/v4' },
(event) => 'activity' in event
);

await new Promise((res) => setTimeout(res, 1000));
},
});
const isMarkReadPending = isLoading;
const markAllRead = useCallback(async () => {
markRead();
}, []);

return (
<>
<div className="card">
<div className="flex flex-col items-start space-y-2">
<h2 className="text-lg font-semibold">Activity</h2>
<p className="text-gray-600">
Mark all channels, DMs, and threads read (aka get rid of dots)
</p>
<button
disabled={isMarkReadPending}
className={cn('small-button whitespace-nowrap text-sm min-w-10', {
'bg-gray-400 text-gray-800': isMarkReadPending,
})}
onClick={markAllRead}
>
{isMarkReadPending ? (
<LoadingSpinner className="h-4 w-4" />
) : (
`Mark Everything as Read`
)}
</button>
</div>
</div>
<div className="card">
<div className="flex flex-col">
<h2 className="mb-2 text-lg font-semibold">Blocked Users</h2>
Expand Down
2 changes: 1 addition & 1 deletion apps/tlon-web/src/components/VolumeSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default function VolumeSetting({ source }: { source: Source }) {
);

return (
<div className="space-y-4 min-w-[400px]">
<div className="space-y-4 flex-1 sm:flex-none sm:min-w-[400px]">
<Setting
on={currentUnreads}
name="Show Unread Indicator"
Expand Down
6 changes: 0 additions & 6 deletions apps/tlon-web/src/dms/BroadcastWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@ export default function BroadcastWindow({
scrollElementRef={scrollElementRef}
isScrolling={isScrolling}
topLoadEndMarker={prefixedElement}
onAtTop={() => {
return;
}}
onAtBottom={() => {
return;
}}
hasLoadedOldest={true}
hasLoadedNewest={true}
/>
Expand Down
Loading

0 comments on commit eedcb14

Please sign in to comment.