-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Further evolved chat app for the owner-app (#25)
* Cleanup chat type of double items; * Simplified structure; * Properly display galleries of 3; * Chat context WIP * Added reply option; * Fixed getFileHeader to avoid altering when parsing; * Updated tryJsonParse to avoid returning empty objects, when the source is already parsed; * Improved handling of conversations; And read receipts * Fixed a bug gallery nav; * Added delete chat support; * Added delete ui state; Added delete of payload; * Vite upgrade; * Make gifs work, even when the tiny thumb isn't used; * Added new conversation and new group conversation sidebars; * Added a first version of group chat; * Added a first version of group chat; * Fix build provisioning; * WIP: keep logged in state, when back-end isn't reachable; * Added support for read reciepts on group chats; * Change command processor to run sequentially; * Add indication when not connected to group chat recipients; * Add indication when not connected to group chat recipients; * Better detection of emoji messages; * Integrated the chat-app into the owner-app; * Added message info; * Moved components; * Added deleted drive search result for queryModified; * Extended queryBatch with dynamice return type; * Increased contrast on the postTeaser; Added sr labels for the socials; * Build fixes; * Moved chat-app to run at identity.cloud/apps/chat * Fixed bad merge of conversatinos; * Rebuid package-lock; * Upgrade node v; * Explict set of rollup version; * Attribute editor fixes; * Updated chat link on sidenav;
- Loading branch information
1 parent
87412ee
commit eb852db
Showing
84 changed files
with
3,013 additions
and
2,077 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
export * from './src/templates/Conversations/Conversations'; | ||
export * from './src/templates/Chat/ChatHome'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { ErrorNotification } from '@youfoundation/common-app'; | ||
import { DriveSearchResult } from '@youfoundation/js-lib/core'; | ||
import { useChatMessages } from '../../hooks/chat/useChatMessages'; | ||
import { useMarkMessagesAsRead } from '../../hooks/chat/useMarkMessagesAsRead'; | ||
import { ChatMessage } from '../../providers/ChatProvider'; | ||
import { Conversation } from '../../providers/ConversationProvider'; | ||
import { ChatMessageItem } from './Detail/ChatMessageItem'; | ||
import { ChatActions } from './Detail/ContextMenu'; | ||
import { useMemo } from 'react'; | ||
|
||
export const ChatHistory = ({ | ||
conversation, | ||
setReplyMsg, | ||
}: { | ||
conversation: DriveSearchResult<Conversation> | undefined; | ||
setReplyMsg: (msg: DriveSearchResult<ChatMessage>) => void; | ||
}) => { | ||
const { | ||
all: { data: messages }, | ||
delete: { mutate: deleteMessages, error: deleteMessagesError }, | ||
} = useChatMessages({ conversationId: conversation?.fileMetadata?.appData?.uniqueId }); | ||
const flattenedMsgs = useMemo( | ||
() => | ||
(messages?.pages.flatMap((page) => page.searchResults).filter(Boolean) || | ||
[]) as DriveSearchResult<ChatMessage>[], | ||
[messages] | ||
); | ||
|
||
useMarkMessagesAsRead({ conversation, messages: flattenedMsgs }); | ||
|
||
const chatActions: ChatActions = { | ||
doReply: (msg: DriveSearchResult<ChatMessage>) => setReplyMsg(msg), | ||
doDelete: async (msg: DriveSearchResult<ChatMessage>) => { | ||
if (!conversation || !msg) return; | ||
await deleteMessages({ | ||
conversation: conversation, | ||
messages: [msg], | ||
deleteForEveryone: true, | ||
}); | ||
}, | ||
}; | ||
|
||
return ( | ||
<> | ||
<ErrorNotification error={deleteMessagesError} /> | ||
<div className="flex h-full flex-grow flex-col-reverse gap-2 overflow-auto p-5"> | ||
{flattenedMsgs?.map((msg) => | ||
msg ? ( | ||
<ChatMessageItem | ||
key={msg.fileId} | ||
msg={msg} | ||
conversation={conversation} | ||
chatActions={chatActions} | ||
/> | ||
) : null | ||
)} | ||
</div> | ||
</> | ||
); | ||
}; |
132 changes: 132 additions & 0 deletions
132
packages/chat-app/src/components/Chat/Composer/ChatComposer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { | ||
ErrorNotification, | ||
FileOverview, | ||
EmojiSelector, | ||
FileSelector, | ||
ImageIcon, | ||
VolatileInput, | ||
ActionButton, | ||
t, | ||
Times, | ||
} from '@youfoundation/common-app'; | ||
import { DriveSearchResult } from '@youfoundation/js-lib/core'; | ||
import { NewMediaFile } from '@youfoundation/js-lib/dist'; | ||
import { useChatMessage } from '../../../hooks/chat/useChatMessage'; | ||
import { ChatMessage } from '../../../providers/ChatProvider'; | ||
import { | ||
Conversation, | ||
GroupConversation, | ||
SingleConversation, | ||
} from '../../../providers/ConversationProvider'; | ||
import { useState, useEffect } from 'react'; | ||
import { EmbeddedMessage } from '../Detail/EmbeddedMessage'; | ||
|
||
export const ChatComposer = ({ | ||
conversation, | ||
replyMsg, | ||
clearReplyMsg, | ||
}: { | ||
conversation: DriveSearchResult<Conversation> | undefined; | ||
replyMsg: DriveSearchResult<ChatMessage> | undefined; | ||
clearReplyMsg: () => void; | ||
}) => { | ||
const [stateIndex, setStateIndex] = useState(0); // Used to force a re-render of the component, to reset the input | ||
const [message, setMessage] = useState<string | undefined>(); | ||
const [files, setFiles] = useState<NewMediaFile[]>(); | ||
|
||
const { | ||
mutate: sendMessage, | ||
status: sendMessageState, | ||
reset: resetState, | ||
error: sendMessageError, | ||
} = useChatMessage().send; | ||
|
||
const conversationContent = conversation?.fileMetadata.appData.content; | ||
const doSend = () => { | ||
if ((!message && !files) || !conversationContent || !conversation.fileMetadata.appData.uniqueId) | ||
return; | ||
sendMessage({ | ||
conversationId: conversation.fileMetadata.appData.uniqueId as string, | ||
message: message || '', | ||
replyId: replyMsg?.fileMetadata?.appData?.uniqueId, | ||
files, | ||
recipients: (conversationContent as GroupConversation).recipients || [ | ||
(conversationContent as SingleConversation).recipient, | ||
], | ||
}); | ||
}; | ||
|
||
// Reset state, when the message was sent successfully | ||
useEffect(() => { | ||
if (sendMessageState === 'success') { | ||
setMessage(''); | ||
setStateIndex((oldIndex) => oldIndex + 1); | ||
setFiles([]); | ||
clearReplyMsg(); | ||
resetState(); | ||
} | ||
}, [sendMessageState]); | ||
|
||
useEffect(() => { | ||
if (replyMsg) setFiles([]); | ||
}, [replyMsg]); | ||
|
||
useEffect(() => { | ||
if (files?.length) clearReplyMsg(); | ||
}, [files]); | ||
|
||
return ( | ||
<> | ||
<ErrorNotification error={sendMessageError} /> | ||
<div className="bg-page-background"> | ||
<FileOverview files={files} setFiles={setFiles} className="mt-2" /> | ||
{replyMsg ? <MessageForReply msg={replyMsg} onClear={clearReplyMsg} /> : null} | ||
<div className="flex flex-shrink-0 flex-row gap-2 px-5 py-3"> | ||
<div className="my-auto flex flex-row items-center gap-1"> | ||
<EmojiSelector | ||
size="none" | ||
className="px-1 py-1 text-foreground text-opacity-30 hover:text-opacity-100" | ||
onInput={(val) => setMessage((oldVal) => (oldVal ? `${oldVal} ${val}` : val))} | ||
/> | ||
<FileSelector | ||
onChange={(files) => setFiles(files.map((file) => ({ file })))} | ||
className="text-foreground text-opacity-30 hover:text-opacity-100" | ||
> | ||
<ImageIcon className="h-5 w-5" /> | ||
</FileSelector> | ||
</div> | ||
|
||
<VolatileInput | ||
key={stateIndex} | ||
placeholder="Your message" | ||
defaultValue={message} | ||
className="rounded-md border bg-background p-2 dark:border-slate-800" | ||
onChange={setMessage} | ||
onSubmit={(val) => { | ||
setMessage(val); | ||
doSend(); | ||
}} | ||
/> | ||
<ActionButton type="secondary" onClick={doSend} state={sendMessageState}> | ||
{t('Send')} | ||
</ActionButton> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
const MessageForReply = ({ | ||
msg, | ||
onClear, | ||
}: { | ||
msg: DriveSearchResult<ChatMessage>; | ||
onClear: () => void; | ||
}) => { | ||
return ( | ||
<div className="flex flex-row gap-2 px-5 py-3"> | ||
<EmbeddedMessage msg={msg} /> | ||
<ActionButton icon={Times} type="mute" onClick={onClear}></ActionButton> | ||
</div> | ||
); | ||
}; |
29 changes: 29 additions & 0 deletions
29
packages/chat-app/src/components/Chat/Detail/ChatDeliveryIndicator.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { useDotYouClient, SubtleCheck } from '@youfoundation/common-app'; | ||
import { DriveSearchResult } from '@youfoundation/js-lib/core'; | ||
import { ChatMessage, ChatDeliveryStatus } from '../../../providers/ChatProvider'; | ||
|
||
export const ChatDeliveryIndicator = ({ msg }: { msg: DriveSearchResult<ChatMessage> }) => { | ||
const identity = useDotYouClient().getIdentity(); | ||
const content = msg.fileMetadata.appData.content; | ||
const authorOdinId = msg.fileMetadata.senderOdinId; | ||
const messageFromMe = !authorOdinId || authorOdinId === identity; | ||
|
||
if (!messageFromMe) return null; | ||
return <InnerDeliveryIndicator state={content.deliveryStatus} />; | ||
}; | ||
|
||
export const InnerDeliveryIndicator = ({ state }: { state?: ChatDeliveryStatus }) => { | ||
const isDelivered = state && state >= ChatDeliveryStatus.Delivered; | ||
const isRead = state === ChatDeliveryStatus.Read; | ||
|
||
return ( | ||
<div | ||
className={`${isDelivered ? '-ml-2' : ''} flex flex-row drop-shadow-md ${ | ||
isRead ? 'text-blue-600 ' : 'text-foreground/60' | ||
}`} | ||
> | ||
{isDelivered ? <SubtleCheck className="relative -right-2 z-10 h-4 w-4" /> : null} | ||
<SubtleCheck className="h-4 w-4" /> | ||
</div> | ||
); | ||
}; |
60 changes: 60 additions & 0 deletions
60
packages/chat-app/src/components/Chat/Detail/ChatMessageInfo.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { createPortal } from 'react-dom'; | ||
import { DriveSearchResult } from '@youfoundation/js-lib/core'; | ||
import { ChatMessage } from '../../../providers/ChatProvider'; | ||
import { | ||
ConnectionImage, | ||
ConnectionName, | ||
DialogWrapper, | ||
t, | ||
usePortal, | ||
} from '@youfoundation/common-app'; | ||
import { | ||
Conversation, | ||
GroupConversation, | ||
SingleConversation, | ||
} from '../../../providers/ConversationProvider'; | ||
import { InnerDeliveryIndicator } from './ChatDeliveryIndicator'; | ||
|
||
export const ChatMessageInfo = ({ | ||
msg, | ||
conversation, | ||
onClose, | ||
}: { | ||
msg: DriveSearchResult<ChatMessage>; | ||
conversation: DriveSearchResult<Conversation>; | ||
onClose: () => void; | ||
}) => { | ||
const target = usePortal('modal-container'); | ||
const messageContent = msg.fileMetadata.appData.content; | ||
const conversationContent = conversation.fileMetadata.appData.content; | ||
const recipients = (conversationContent as GroupConversation).recipients || [ | ||
(conversationContent as SingleConversation).recipient, | ||
]; | ||
|
||
const dialog = ( | ||
<DialogWrapper onClose={onClose} title={t('Message info')}> | ||
<div> | ||
<p className="mb-2 text-lg">{t('Recipients')}</p> | ||
<div className="flex flex-col gap-4"> | ||
{recipients.map((recipient) => ( | ||
<div className="flex flex-row items-center justify-between" key={recipient}> | ||
<div className="flex flex-row items-center gap-2"> | ||
<ConnectionImage | ||
odinId={recipient} | ||
className="border border-neutral-200 dark:border-neutral-800" | ||
size="sm" | ||
/> | ||
<ConnectionName odinId={recipient} /> | ||
</div> | ||
<InnerDeliveryIndicator | ||
state={messageContent.deliveryDetails?.[recipient] || messageContent.deliveryStatus} | ||
/> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
</DialogWrapper> | ||
); | ||
|
||
return createPortal(dialog, target); | ||
}; |
Oops, something went wrong.