Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

omnibus: scroller perf, devex enhancements #1197

Merged
merged 12 commits into from
Nov 21, 2022
12 changes: 4 additions & 8 deletions ui/src/chat/ChatChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,19 @@ import {
useChannel,
} from '@/state/groups/groups';
import ChannelHeader from '@/channels/ChannelHeader';
import { createStorageKey } from '@/logic/utils';
import { useLocalStorage } from 'usehooks-ts';
import useRecentChannel from '@/logic/useRecentChannel';

function ChatChannel({ title }: ViewProps) {
const { chShip, chName } = useParams();
const chFlag = `${chShip}/${chName}`;
const nest = `chat/${chFlag}`;
const flag = useRouteGroup();
const [, setRecent] = useLocalStorage(
createStorageKey(`recent-chan:${flag}`),
''
);
const { setRecentChannel } = useRecentChannel(flag);

useEffect(() => {
useChatState.getState().initialize(chFlag);
setRecent(nest);
}, [chFlag, nest, setRecent]);
setRecentChannel(nest);
}, [chFlag, nest, setRecentChannel]);

const messages = useMessagesForChat(chFlag);
const perms = useChatPerms(chFlag);
Expand Down
11 changes: 7 additions & 4 deletions ui/src/chat/ChatScroller/ChatScroller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import {
useGetFirstUnreadID,
useLoadedWrits,
} from '@/state/chat/chat';
import { MESSAGE_FETCH_PAGE_SIZE } from '@/constants';
import {
INITIAL_MESSAGE_FETCH_PAGE_SIZE,
STANDARD_MESSAGE_FETCH_PAGE_SIZE,
} from '@/constants';
import { ChatBrief, ChatWrit } from '@/types/chat';
import { useIsMobile } from '@/logic/useMedia';
import { IChatScroller } from './IChatScroller';
Expand Down Expand Up @@ -182,7 +185,7 @@ export default function ChatScroller({
);

const fetchMessages = useCallback(
async (newer: boolean, pageSize = MESSAGE_FETCH_PAGE_SIZE) => {
async (newer: boolean, pageSize = STANDARD_MESSAGE_FETCH_PAGE_SIZE) => {
const newest = messages.peekLargest();
const seenNewest = newer && newest && loaded.newest.geq(newest[0]);
const oldest = messages.peekSmallest();
Expand Down Expand Up @@ -242,15 +245,15 @@ export default function ChatScroller({
}, [scrollTo, virtuoso]);

/**
* By default, 100 messages are fetched on initial load. If there are more
* By default, 50 messages are fetched on initial load. If there are more
* unreads per the brief, fetch those as well. That way, the user can click
* the unread banner and see the unread messages.
*/
useEffect(() => {
if (
fetching === 'initial' &&
brief &&
brief.count > MESSAGE_FETCH_PAGE_SIZE &&
brief.count > INITIAL_MESSAGE_FETCH_PAGE_SIZE &&
firstUnreadID &&
!keys.includes(firstUnreadID)
) {
Expand Down
30 changes: 9 additions & 21 deletions ui/src/chat/ChatUnreadAlerts.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React, { useCallback } from 'react';
import { format, isToday } from 'date-fns';
import { useLocation, useNavigate } from 'react-router';
import bigInt from 'big-integer';
import XIcon from '@/components/icons/XIcon';
import { ChatBrief } from '@/types/chat';
import { pluralize } from '../logic/utils';
import { useChatKeys, useChatState } from '../state/chat';
import { useChatState, useGetFirstUnreadID } from '../state/chat';
import { useChatInfo } from './useChatStore';

interface ChatUnreadAlertsProps {
Expand All @@ -21,23 +19,13 @@ export default function ChatUnreadAlerts({ whom }: ChatUnreadAlertsProps) {
const navigate = useNavigate();
const location = useLocation();
// TODO: how to handle replies?
const chatKeys = useChatKeys({ replying: false });
const goToFirstUnread = useCallback(
(b: ChatBrief) => {
const { 'read-id': lastRead } = b;
if (!lastRead) {
return;
}
// lastRead is formatted like: ~zod/123.456.789...
const lastReadBN = bigInt(lastRead.split('/')[1].replaceAll('.', ''));
const firstUnread = chatKeys.find((key) => key.gt(lastReadBN));
if (!firstUnread) {
return;
}
navigate(`${location.pathname}?msg=${firstUnread.toString()}`);
},
[chatKeys, location.pathname, navigate]
);
const firstUnreadID = useGetFirstUnreadID(whom);
const goToFirstUnread = useCallback(() => {
if (!firstUnreadID) {
return;
}
navigate(`${location.pathname}?msg=${firstUnreadID.toString()}`);
}, [firstUnreadID, location.pathname, navigate]);

if (!chatInfo.unread || chatInfo.unread.seen) {
return null;
Expand All @@ -62,7 +50,7 @@ export default function ChatUnreadAlerts({ whom }: ChatUnreadAlertsProps) {
<div className="absolute top-2 left-1/2 z-20 flex w-full -translate-x-1/2 flex-wrap items-center justify-center gap-2">
<button
className="button whitespace-nowrap bg-blue-soft text-sm text-blue dark:bg-blue-900 lg:text-base"
onClick={() => goToFirstUnread(brief)}
onClick={goToFirstUnread}
>
<span className="whitespace-nowrap font-normal">
{unreadMessage}&nbsp;&mdash;&nbsp;Click to View
Expand Down
6 changes: 5 additions & 1 deletion ui/src/components/ButterBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { useLocalStorage } from 'usehooks-ts';
import { createStorageKey } from '@/logic/utils';
import AsteriskIcon from './icons/Asterisk16Icon';

interface ButterBarProps {
Expand All @@ -8,7 +9,10 @@ interface ButterBarProps {
}

function ButterBar({ dismissKey, message }: ButterBarProps) {
const [isDismissed, setIsDismissed] = useLocalStorage(dismissKey, false);
const [isDismissed, setIsDismissed] = useLocalStorage(
createStorageKey(dismissKey),
false
);

function onClick() {
setIsDismissed(true);
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/PrereleaseNotice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function PrereleaseNotice() {

return (
<ButterBar
dismissKey={`${app}-prerelease-notice-dismissed`}
dismissKey={`prerelease-notice-dismissed`}
message={`Reminder: you are using a Beta version of ${app}. Everything you write or create, including groups and messages, will be deleted prior to official launch.`}
/>
);
Expand Down
5 changes: 4 additions & 1 deletion ui/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// eslint-disable-next-line import/prefer-default-export
export const MESSAGE_FETCH_PAGE_SIZE = 100;
export const INITIAL_MESSAGE_FETCH_PAGE_SIZE = 50;
export const STANDARD_MESSAGE_FETCH_PAGE_SIZE = 200;
export const LARGE_MESSAGE_FETCH_PAGE_SIZE = 400;
export const FETCH_BATCH_SIZE = 3;
export const MAX_DISPLAYED_OPTIONS = 40;

export const AUTHORS = [
Expand Down
12 changes: 4 additions & 8 deletions ui/src/diary/DiaryChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ import useDismissChannelNotifications from '@/logic/useDismissChannelNotificatio
import { DiaryDisplayMode } from '@/types/diary';
import DiaryGridView from '@/diary/DiaryList/DiaryGridView';
import { Link } from 'react-router-dom';
import { useLocalStorage } from 'usehooks-ts';
import * as Toast from '@radix-ui/react-toast';
import { createStorageKey } from '@/logic/utils';
import useRecentChannel from '@/logic/useRecentChannel';
import DiaryListItem from './DiaryList/DiaryListItem';
import useDiaryActions from './useDiaryActions';

Expand All @@ -36,10 +35,7 @@ function DiaryChannel() {
const letters = useNotesForDiary(chFlag);
const location = useLocation();
const navigate = useNavigate();
const [, setRecent] = useLocalStorage(
createStorageKey(`recent-chan:${flag}`),
''
);
const { setRecentChannel } = useRecentChannel(flag);
const newNote = new URLSearchParams(location.search).get('new');
const [showToast, setShowToast] = useState(false);
const { didCopy, onCopy } = useDiaryActions({
Expand Down Expand Up @@ -78,8 +74,8 @@ function DiaryChannel() {

useEffect(() => {
useDiaryState.getState().initialize(chFlag);
setRecent(nest);
}, [chFlag, nest, setRecent]);
setRecentChannel(nest);
}, [chFlag, nest, setRecentChannel]);

useEffect(() => {
let timeout: any;
Expand Down
11 changes: 4 additions & 7 deletions ui/src/groups/Groups.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react';
import { Outlet, useLocation, useMatch, useNavigate } from 'react-router';
import { Outlet, useMatch, useNavigate } from 'react-router';
import {
useGang,
useGroup,
Expand All @@ -11,9 +11,9 @@ import api from '@/api';
import { useChatState } from '@/state/chat';
import { useHeapState } from '@/state/heap/heap';
import { useDiaryState } from '@/state/diary';
import { createStorageKey, nestToFlag } from '@/logic/utils';
import { useLocalStorage } from 'usehooks-ts';
import { nestToFlag } from '@/logic/utils';
import { useIsMobile } from '@/logic/useMedia';
import useRecentChannel from '@/logic/useRecentChannel';

function Groups() {
const navigate = useNavigate();
Expand All @@ -26,10 +26,7 @@ function Groups() {
path: '/groups/:ship/:name',
end: true,
});
const [recentChannel] = useLocalStorage(
createStorageKey(`recent-chan:${flag}`),
''
);
const { recentChannel } = useRecentChannel(flag);

useEffect(() => {
if (initialized && !group && !gang) {
Expand Down
14 changes: 5 additions & 9 deletions ui/src/heap/HeapChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import _ from 'lodash';
import cn from 'classnames';
import { Outlet, useParams, useNavigate } from 'react-router';
import { Helmet } from 'react-helmet';
import { useLocalStorage } from 'usehooks-ts';
import bigInt from 'big-integer';
import { ViewProps } from '@/types/groups';
import Layout from '@/components/Layout/Layout';
import {
Expand All @@ -29,9 +29,8 @@ import {
import HeapBlock from '@/heap/HeapBlock';
import HeapRow from '@/heap/HeapRow';
import useDismissChannelNotifications from '@/logic/useDismissChannelNotifications';
import { createStorageKey } from '@/logic/utils';
import { GRID, HeapCurio, HeapDisplayMode, HeapSortMode } from '@/types/heap';
import bigInt from 'big-integer';
import useRecentChannel from '@/logic/useRecentChannel';
import NewCurioForm from './NewCurioForm';

function HeapChannel({ title }: ViewProps) {
Expand All @@ -41,10 +40,7 @@ function HeapChannel({ title }: ViewProps) {
const flag = useRouteGroup();
const channel = useChannel(flag, nest);
const group = useGroup(flag);
const [, setRecent] = useLocalStorage(
createStorageKey(`recent-chan:${flag}`),
''
);
const { setRecentChannel } = useRecentChannel(flag);

const navigate = useNavigate();
const displayMode = useHeapDisplayMode(chFlag);
Expand Down Expand Up @@ -90,8 +86,8 @@ function HeapChannel({ title }: ViewProps) {

useEffect(() => {
useHeapState.getState().initialize(chFlag);
setRecent(nest);
}, [chFlag, nest, setRecent]);
setRecentChannel(nest);
}, [chFlag, nest, setRecentChannel]);

useDismissChannelNotifications({
nest,
Expand Down
22 changes: 19 additions & 3 deletions ui/src/logic/usePrefetchChannels.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useCallback, useEffect } from 'react';
import { chunk } from 'lodash';
import { useGroup } from '@/state/groups';
import { useChatState } from '@/state/chat';
import { asyncForEach } from '@/lib';
import { useHeapState } from '@/state/heap/heap';
import { useDiaryState } from '@/state/diary';
import { FETCH_BATCH_SIZE } from '@/constants';
import { nestToFlag } from './utils';
import useRecentChannel from './useRecentChannel';

/**
* On load, prefetches the messages for the group that the user is currently in.
Expand All @@ -14,6 +17,7 @@ import { nestToFlag } from './utils';
*/
export default function usePrefetchChannels(flag: string) {
const group = useGroup(flag);
const { recentChannel } = useRecentChannel(flag);
const { initialize: initializeChat } = useChatState.getState();
const { initialize: initializeDiary } = useDiaryState.getState();
const { initialize: initializeHeap } = useHeapState.getState();
Expand All @@ -23,7 +27,7 @@ export default function usePrefetchChannels(flag: string) {
const [chType, chFlag] = nestToFlag(channel);
switch (chType) {
case 'chat':
initializeChat(chFlag);
await initializeChat(chFlag);
break;
case 'diary':
initializeDiary(chFlag);
Expand All @@ -39,8 +43,20 @@ export default function usePrefetchChannels(flag: string) {
);

const fetchAll = useCallback(async () => {
await asyncForEach(Object.keys(group?.channels ?? {}), fetchChannel);
}, [fetchChannel, group]);
// first, prioritize the recent channel
if (recentChannel && recentChannel !== '') {
await fetchChannel(recentChannel);
}

// defer the rest, in batches
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

const channels = Object.keys(group?.channels ?? {}).filter(
(c) => c !== recentChannel
);
const batched = chunk(channels, FETCH_BATCH_SIZE);
batched.forEach(async (batch) => {
await asyncForEach(batch, fetchChannel);
});
}, [fetchChannel, group, recentChannel]);

useEffect(() => {
if (!group) {
Expand Down
15 changes: 15 additions & 0 deletions ui/src/logic/useRecentChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useLocalStorage } from 'usehooks-ts';
import { createStorageKey } from './utils';

/**
* Tracks which channel was most recently visited
* @param flag The group flag
*/
export default function useRecentChannel(flag: string) {
const [recentChannel, setRecentChannel] = useLocalStorage(
createStorageKey(`recent-chan:${flag}`),
''
);

return { recentChannel, setRecentChannel };
}
2 changes: 1 addition & 1 deletion ui/src/state/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ export const useChatState = createState<ChatState>(
draft.chatSubs.push(whom);
});

await makeWritsStore(
makeWritsStore(
whom,
get,
`/chat/${whom}/writs`,
Expand Down
5 changes: 4 additions & 1 deletion ui/src/state/chat/writs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BigIntOrderedMap, decToUd, udToDec, unixToDa } from '@urbit/api';
import bigInt from 'big-integer';
import { INITIAL_MESSAGE_FETCH_PAGE_SIZE } from '@/constants';
import api from '../../api';
import {
ChatWrit,
Expand Down Expand Up @@ -112,7 +113,9 @@ export default function makeWritsStore(
});
return {
initialize: async () => {
const writs = await scry<ChatWrits>(`/newest/100`);
const writs = await scry<ChatWrits>(
`/newest/${INITIAL_MESSAGE_FETCH_PAGE_SIZE}`
);
const sta = get();
sta.batchSet((draft) => {
const pact: Pact = {
Expand Down
11 changes: 6 additions & 5 deletions ui/src/state/diary/notes.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import bigInt from 'big-integer';
import { BigIntOrderedMap, decToUd, udToDec } from '@urbit/api';
import { INITIAL_MESSAGE_FETCH_PAGE_SIZE } from '@/constants';
import {
NoteDiff,
NoteSeal,
DiaryNote,
DiaryNotes,
DiaryFlag,
DiaryDiff,
DiaryUpdate,
DiaryOutlines,
DiaryLetter,
DiaryQuip,
} from '@/types/diary';
import { BigIntOrderedMap, decToUd, udToDec } from '@urbit/api';
import bigInt from 'big-integer';
import api from '../../api';
import { DiaryState } from './type';

Expand Down Expand Up @@ -75,7 +74,9 @@ export default function makeNotesStore(

return {
initialize: async () => {
const notes = await scry<DiaryOutlines>(`/newest/100/outline`);
const notes = await scry<DiaryOutlines>(
`/newest/${INITIAL_MESSAGE_FETCH_PAGE_SIZE}/outline`
);
const sta = get();
sta.batchSet((draft) => {
let noteMap = new BigIntOrderedMap<DiaryLetter>();
Expand Down
Loading