Skip to content

Commit

Permalink
Community perf (#513)
Browse files Browse the repository at this point in the history
* Marking as read perf improvements;

* Lighten up the MessageComposer;

* Less intense markAsRead operations; Improved draft saver/fetcher to be faster and cause less re-renders;

* Lint fix
  • Loading branch information
stef-coenen authored Jan 8, 2025
1 parent d8897ab commit be4c183
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import {
LinkOverview,
t,
trimRichText,
useDebounce,
useErrors,
useLinkPreviewBuilder,
VolatileInputRef,
} from '@homebase-id/common-app';
import { useState, useEffect, FC, useRef, lazy } from 'react';
import { useState, FC, useRef, lazy, useMemo, useCallback } from 'react';

import { getNewId, isTouchDevice } from '@homebase-id/js-lib/helpers';
import { ChatComposerProps } from '@homebase-id/chat-app/src/components/Chat/Composer/ChatComposer';
import { HomebaseFile, NewHomebaseFile, NewMediaFile, RichText } from '@homebase-id/js-lib/core';
import { HomebaseFile, NewMediaFile, RichText } from '@homebase-id/js-lib/core';
import { useChatMessage } from '@homebase-id/chat-app/src/hooks/chat/useChatMessage';
import { Plus, PaperPlane, Times } from '@homebase-id/common-app/icons';
import { LinkPreview } from '@homebase-id/js-lib/media';
Expand All @@ -29,8 +28,9 @@ const RichTextEditor = lazy(() =>
import { EmbeddedMessage } from '@homebase-id/chat-app/src/components/Chat/Detail/EmbeddedMessage';
import { ChatMessage } from '@homebase-id/chat-app/src/providers/ChatProvider';
import { useParams } from 'react-router-dom';
import { useCommunityMetadata } from '../../../../hooks/community/useCommunityMetadata';
import { CommunityMetadata, Draft } from '../../../../providers/CommunityMetadataProvider';
import { DraftSaver } from './DraftSaver';
import { useCommunity } from '../../../../hooks/community/useCommunity';
import { useMessageDraft } from './useMessageDraft';

const HUNDRED_MEGA_BYTES = 100 * 1024 * 1024;

Expand All @@ -43,71 +43,38 @@ export const CommunityDirectComposer: FC<ChatComposerProps> = ({
const { odinKey, communityKey } = useParams();
const volatileRef = useRef<VolatileInputRef>(null);

const {
single: { data: metadata },
update: { mutate: updateMetadata },
} = useCommunityMetadata({
const { data: community } = useCommunity({
odinId: odinKey,
communityId: communityKey,
});

const [toSaveMeta, setToSaveMeta] = useState<
HomebaseFile<CommunityMetadata> | NewHomebaseFile<CommunityMetadata> | undefined
>();
const drafts = (toSaveMeta || metadata)?.fileMetadata.appData.content.drafts || {};
const [message, setMessage] = useState<RichText | undefined>(
conversation?.fileMetadata.appData.uniqueId
? drafts[conversation.fileMetadata.appData.uniqueId]?.message
: undefined
);
const [files, setFiles] = useState<NewMediaFile[]>();

const instantSave = (
toSaveMeta: NewHomebaseFile<CommunityMetadata> | HomebaseFile<CommunityMetadata>
) => updateMetadata({ metadata: toSaveMeta });
const debouncedSave = useDebounce(() => toSaveMeta && updateMetadata({ metadata: toSaveMeta }), {
timeoutMillis: 2000,
});
useEffect(() => {
if (metadata && conversation && conversation?.fileMetadata.appData.uniqueId) {
if (drafts[conversation.fileMetadata.appData.uniqueId]?.message === message) return;

const newDrafts: Record<string, Draft | undefined> = {
...drafts,
[conversation.fileMetadata.appData.uniqueId]: {
message,
updatedAt: new Date().getTime(),
},
};
}).fetch;

const newMeta = {
...metadata,
fileMetadata: {
...metadata?.fileMetadata,
appData: {
...metadata?.fileMetadata.appData,
content: { ...metadata?.fileMetadata.appData.content, drafts: newDrafts },
},
},
};
const [message, setMessage] = useState<RichText | undefined>(undefined);
const [files, setFiles] = useState<NewMediaFile[]>();

if (message === undefined) {
instantSave(newMeta);
return;
}
setToSaveMeta(newMeta);
debouncedSave();
}
}, [conversation, message, debouncedSave]);
const draft = useMessageDraft(
!message
? {
community,
draftKey: conversation?.fileMetadata.appData.uniqueId,
}
: undefined
);

const plainMessage = message && getTextRootsRecursive(message).join(' ');
const plainMessage = useMemo(
() =>
((message || draft?.message) && getTextRootsRecursive(message || draft?.message).join(' ')) ||
'',
[message, draft]
);
const { linkPreviews, setLinkPreviews } = useLinkPreviewBuilder(plainMessage || '');

const addError = useErrors().add;
const { mutateAsync: sendMessage } = useChatMessage().send;

const conversationContent = conversation?.fileMetadata.appData.content;
const doSend = async () => {
const toSendMessage = message || draft?.message;

const trimmedVal = plainMessage?.trim();
const replyId = replyMsg?.fileMetadata.appData.uniqueId;
const newFiles = [...(files || [])];
Expand All @@ -120,7 +87,7 @@ export const CommunityDirectComposer: FC<ChatComposerProps> = ({
return;

// Clear internal state and allow excessive senders
setMessage(undefined);
setMessage([]);
setFiles([]);
volatileRef.current?.clear();
volatileRef.current?.focus();
Expand All @@ -129,7 +96,7 @@ export const CommunityDirectComposer: FC<ChatComposerProps> = ({
try {
await sendMessage({
conversation,
message: trimRichText(message) || '',
message: trimRichText(toSendMessage) || '',
replyId: replyId,
files: newFiles,
chatId: getNewId(),
Expand All @@ -147,8 +114,26 @@ export const CommunityDirectComposer: FC<ChatComposerProps> = ({
}
};

const changeHandler = useCallback(
(newVal: {
target: {
name: string;
value: RichText;
};
}) => setMessage(newVal.target.value),
[]
);

const onSubmit = useMemo(() => (isTouchDevice() ? undefined : doSend), [doSend]);

return (
<>
<DraftSaver
community={community}
draftKey={conversation?.fileMetadata.appData.uniqueId}
message={message || draft?.message}
/>

<div className={`bg-background pb-[env(safe-area-inset-bottom)]`}>
<div
className="flex flex-shrink-0 flex-row gap-2 px-0 md:px-3 md:pb-2 lg:pb-5"
Expand All @@ -165,10 +150,10 @@ export const CommunityDirectComposer: FC<ChatComposerProps> = ({
<RichTextEditor
className="relative w-8 flex-grow border-t bg-background px-2 pb-1 dark:border-slate-800 md:rounded-md md:border"
contentClassName="max-h-[50vh] overflow-auto"
onChange={(newVal) => setMessage(newVal.target.value)}
defaultValue={message}
onChange={changeHandler}
defaultValue={message || draft?.message}
autoFocus={!isTouchDevice()}
onSubmit={isTouchDevice() ? undefined : doSend}
onSubmit={onSubmit}
placeholder={t('Your message')}
ref={volatileRef}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useDebounce } from '@homebase-id/common-app';
import { HomebaseFile, RichText, NewHomebaseFile } from '@homebase-id/js-lib/core';
import { useState, useEffect } from 'react';
import { useCommunityMetadata } from '../../../../hooks/community/useCommunityMetadata';
import { CommunityDefinition } from '../../../../providers/CommunityDefinitionProvider';
import { CommunityMetadata, Draft } from '../../../../providers/CommunityMetadataProvider';

export const DraftSaver = ({
community,
draftKey,
message,
}: {
community: HomebaseFile<CommunityDefinition> | undefined;
draftKey: string | undefined;
message: RichText | undefined;
}) => {
const {
single: { data: metadata },
update: { mutate: updateMetadata },
} = useCommunityMetadata({
odinId: community?.fileMetadata.senderOdinId,
communityId: community?.fileMetadata.appData.uniqueId,
});

const drafts = metadata?.fileMetadata.appData.content.drafts || {};

const [toSaveMeta, setToSaveMeta] = useState<
HomebaseFile<CommunityMetadata> | NewHomebaseFile<CommunityMetadata> | undefined
>();

const debouncedSave = useDebounce(
() => {
toSaveMeta && updateMetadata({ metadata: toSaveMeta });
},
{
timeoutMillis: 2000,
}
);

useEffect(() => {
if (metadata && draftKey) {
if (drafts[draftKey]?.message === message) return;

const newDrafts: Record<string, Draft | undefined> = {
...drafts,
[draftKey]: {
message,
updatedAt: new Date().getTime(),
},
};

const newMeta: NewHomebaseFile<CommunityMetadata> | HomebaseFile<CommunityMetadata> = {
...metadata,
fileMetadata: {
...metadata?.fileMetadata,
appData: {
...metadata?.fileMetadata.appData,
content: { ...metadata?.fileMetadata.appData.content, drafts: newDrafts },
},
},
};

if (message === undefined || message.length === 0) {
updateMetadata({ metadata: newMeta });
return;
} else {
setToSaveMeta(newMeta);
debouncedSave();
}
}
}, [draftKey, message, debouncedSave]);

return null;
};
Loading

0 comments on commit be4c183

Please sign in to comment.