@weavy/uikit-react 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.github/workflows/publish.yml +16 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +110 -0
  4. package/changelog.md +50 -0
  5. package/dist/cjs/index.js +39 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/types/client/WeavyClient.d.ts +16 -0
  8. package/dist/cjs/types/components/Attachment.d.ts +13 -0
  9. package/dist/cjs/types/components/Avatar.d.ts +11 -0
  10. package/dist/cjs/types/components/Chat.d.ts +4 -0
  11. package/dist/cjs/types/components/Conversation.d.ts +4 -0
  12. package/dist/cjs/types/components/ConversationBadge.d.ts +3 -0
  13. package/dist/cjs/types/components/ConversationForm.d.ts +7 -0
  14. package/dist/cjs/types/components/ConversationList.d.ts +3 -0
  15. package/dist/cjs/types/components/ConversationListItem.d.ts +4 -0
  16. package/dist/cjs/types/components/File.d.ts +9 -0
  17. package/dist/cjs/types/components/FileBrowser.d.ts +6 -0
  18. package/dist/cjs/types/components/Image.d.ts +16 -0
  19. package/dist/cjs/types/components/Meeting.d.ts +8 -0
  20. package/dist/cjs/types/components/MeetingCard.d.ts +6 -0
  21. package/dist/cjs/types/components/Meetings.d.ts +6 -0
  22. package/dist/cjs/types/components/Message.d.ts +4 -0
  23. package/dist/cjs/types/components/Messages.d.ts +9 -0
  24. package/dist/cjs/types/components/Messenger.d.ts +4 -0
  25. package/dist/cjs/types/components/NewConversation.d.ts +3 -0
  26. package/dist/cjs/types/components/Presence.d.ts +7 -0
  27. package/dist/cjs/types/components/Reactions.d.ts +13 -0
  28. package/dist/cjs/types/components/SearchUsers.d.ts +7 -0
  29. package/dist/cjs/types/components/SeenBy.d.ts +9 -0
  30. package/dist/cjs/types/components/Typing.d.ts +8 -0
  31. package/dist/cjs/types/contexts/MessengerContext.d.ts +8 -0
  32. package/dist/cjs/types/contexts/PreviewContext.d.ts +7 -0
  33. package/dist/cjs/types/contexts/UserContext.d.ts +8 -0
  34. package/dist/cjs/types/contexts/WeavyContext.d.ts +10 -0
  35. package/dist/cjs/types/hooks/useBadge.d.ts +1 -0
  36. package/dist/cjs/types/hooks/useChat.d.ts +1 -0
  37. package/dist/cjs/types/hooks/useConversation.d.ts +1 -0
  38. package/dist/cjs/types/hooks/useConversations.d.ts +1 -0
  39. package/dist/cjs/types/hooks/useDebounce.d.ts +2 -0
  40. package/dist/cjs/types/hooks/useEvents.d.ts +6 -0
  41. package/dist/cjs/types/hooks/useFileUploader.d.ts +1 -0
  42. package/dist/cjs/types/hooks/useMembers.d.ts +1 -0
  43. package/dist/cjs/types/hooks/useMessages.d.ts +1 -0
  44. package/dist/cjs/types/hooks/useMutateChat.d.ts +4 -0
  45. package/dist/cjs/types/hooks/useMutateConversation.d.ts +3 -0
  46. package/dist/cjs/types/hooks/useMutateConversationName.d.ts +4 -0
  47. package/dist/cjs/types/hooks/useMutateDeleteReaction.d.ts +4 -0
  48. package/dist/cjs/types/hooks/useMutateExternalBlobs.d.ts +3 -0
  49. package/dist/cjs/types/hooks/useMutateMeeting.d.ts +3 -0
  50. package/dist/cjs/types/hooks/useMutateMembers.d.ts +4 -0
  51. package/dist/cjs/types/hooks/useMutateMessage.d.ts +9 -0
  52. package/dist/cjs/types/hooks/useMutatePinned.d.ts +4 -0
  53. package/dist/cjs/types/hooks/useMutateReaction.d.ts +4 -0
  54. package/dist/cjs/types/hooks/useMutateRead.d.ts +4 -0
  55. package/dist/cjs/types/hooks/useMutateRemoveMembers.d.ts +4 -0
  56. package/dist/cjs/types/hooks/useMutateTyping.d.ts +3 -0
  57. package/dist/cjs/types/hooks/usePresence.d.ts +1 -0
  58. package/dist/cjs/types/hooks/usePreview.d.ts +4 -0
  59. package/dist/cjs/types/hooks/useReactions.d.ts +3 -0
  60. package/dist/cjs/types/hooks/useSearchUsers.d.ts +1 -0
  61. package/dist/cjs/types/hooks/useThrottle.d.ts +2 -0
  62. package/dist/cjs/types/hooks/useUser.d.ts +1 -0
  63. package/dist/cjs/types/index.d.ts +15 -0
  64. package/dist/cjs/types/types/Chat.d.ts +3 -0
  65. package/dist/cjs/types/types/Conversation.d.ts +4 -0
  66. package/dist/cjs/types/types/ConversationListItem.d.ts +4 -0
  67. package/dist/cjs/types/types/Message.d.ts +15 -0
  68. package/dist/cjs/types/types/Messenger.d.ts +3 -0
  69. package/dist/cjs/types/types/types.d.ts +150 -0
  70. package/dist/cjs/types/ui/Button.d.ts +4 -0
  71. package/dist/cjs/types/ui/Dropdown.d.ts +19 -0
  72. package/dist/cjs/types/ui/Icon.d.ts +10 -0
  73. package/dist/cjs/types/ui/Overlay.d.ts +12 -0
  74. package/dist/cjs/types/utils/fileUtilities.d.ts +5 -0
  75. package/dist/cjs/types/utils/styles.d.ts +17 -0
  76. package/dist/esm/index.js +39 -0
  77. package/dist/esm/index.js.map +1 -0
  78. package/dist/esm/types/client/WeavyClient.d.ts +16 -0
  79. package/dist/esm/types/components/Attachment.d.ts +13 -0
  80. package/dist/esm/types/components/Avatar.d.ts +11 -0
  81. package/dist/esm/types/components/Chat.d.ts +4 -0
  82. package/dist/esm/types/components/Conversation.d.ts +4 -0
  83. package/dist/esm/types/components/ConversationBadge.d.ts +3 -0
  84. package/dist/esm/types/components/ConversationForm.d.ts +7 -0
  85. package/dist/esm/types/components/ConversationList.d.ts +3 -0
  86. package/dist/esm/types/components/ConversationListItem.d.ts +4 -0
  87. package/dist/esm/types/components/File.d.ts +9 -0
  88. package/dist/esm/types/components/FileBrowser.d.ts +6 -0
  89. package/dist/esm/types/components/Image.d.ts +16 -0
  90. package/dist/esm/types/components/Meeting.d.ts +8 -0
  91. package/dist/esm/types/components/MeetingCard.d.ts +6 -0
  92. package/dist/esm/types/components/Meetings.d.ts +6 -0
  93. package/dist/esm/types/components/Message.d.ts +4 -0
  94. package/dist/esm/types/components/Messages.d.ts +9 -0
  95. package/dist/esm/types/components/Messenger.d.ts +4 -0
  96. package/dist/esm/types/components/NewConversation.d.ts +3 -0
  97. package/dist/esm/types/components/Presence.d.ts +7 -0
  98. package/dist/esm/types/components/Reactions.d.ts +13 -0
  99. package/dist/esm/types/components/SearchUsers.d.ts +7 -0
  100. package/dist/esm/types/components/SeenBy.d.ts +9 -0
  101. package/dist/esm/types/components/Typing.d.ts +8 -0
  102. package/dist/esm/types/contexts/MessengerContext.d.ts +8 -0
  103. package/dist/esm/types/contexts/PreviewContext.d.ts +7 -0
  104. package/dist/esm/types/contexts/UserContext.d.ts +8 -0
  105. package/dist/esm/types/contexts/WeavyContext.d.ts +10 -0
  106. package/dist/esm/types/hooks/useBadge.d.ts +1 -0
  107. package/dist/esm/types/hooks/useChat.d.ts +1 -0
  108. package/dist/esm/types/hooks/useConversation.d.ts +1 -0
  109. package/dist/esm/types/hooks/useConversations.d.ts +1 -0
  110. package/dist/esm/types/hooks/useDebounce.d.ts +2 -0
  111. package/dist/esm/types/hooks/useEvents.d.ts +6 -0
  112. package/dist/esm/types/hooks/useFileUploader.d.ts +1 -0
  113. package/dist/esm/types/hooks/useMembers.d.ts +1 -0
  114. package/dist/esm/types/hooks/useMessages.d.ts +1 -0
  115. package/dist/esm/types/hooks/useMutateChat.d.ts +4 -0
  116. package/dist/esm/types/hooks/useMutateConversation.d.ts +3 -0
  117. package/dist/esm/types/hooks/useMutateConversationName.d.ts +4 -0
  118. package/dist/esm/types/hooks/useMutateDeleteReaction.d.ts +4 -0
  119. package/dist/esm/types/hooks/useMutateExternalBlobs.d.ts +3 -0
  120. package/dist/esm/types/hooks/useMutateMeeting.d.ts +3 -0
  121. package/dist/esm/types/hooks/useMutateMembers.d.ts +4 -0
  122. package/dist/esm/types/hooks/useMutateMessage.d.ts +9 -0
  123. package/dist/esm/types/hooks/useMutatePinned.d.ts +4 -0
  124. package/dist/esm/types/hooks/useMutateReaction.d.ts +4 -0
  125. package/dist/esm/types/hooks/useMutateRead.d.ts +4 -0
  126. package/dist/esm/types/hooks/useMutateRemoveMembers.d.ts +4 -0
  127. package/dist/esm/types/hooks/useMutateTyping.d.ts +3 -0
  128. package/dist/esm/types/hooks/usePresence.d.ts +1 -0
  129. package/dist/esm/types/hooks/usePreview.d.ts +4 -0
  130. package/dist/esm/types/hooks/useReactions.d.ts +3 -0
  131. package/dist/esm/types/hooks/useSearchUsers.d.ts +1 -0
  132. package/dist/esm/types/hooks/useThrottle.d.ts +2 -0
  133. package/dist/esm/types/hooks/useUser.d.ts +1 -0
  134. package/dist/esm/types/index.d.ts +15 -0
  135. package/dist/esm/types/types/Chat.d.ts +3 -0
  136. package/dist/esm/types/types/Conversation.d.ts +4 -0
  137. package/dist/esm/types/types/ConversationListItem.d.ts +4 -0
  138. package/dist/esm/types/types/Message.d.ts +15 -0
  139. package/dist/esm/types/types/Messenger.d.ts +3 -0
  140. package/dist/esm/types/types/types.d.ts +150 -0
  141. package/dist/esm/types/ui/Button.d.ts +4 -0
  142. package/dist/esm/types/ui/Dropdown.d.ts +19 -0
  143. package/dist/esm/types/ui/Icon.d.ts +10 -0
  144. package/dist/esm/types/ui/Overlay.d.ts +12 -0
  145. package/dist/esm/types/utils/fileUtilities.d.ts +5 -0
  146. package/dist/esm/types/utils/styles.d.ts +17 -0
  147. package/dist/index.d.ts +98 -0
  148. package/package.json +47 -0
  149. package/rollup.config.js +41 -0
  150. package/src/client/WeavyClient.ts +95 -0
  151. package/src/components/Attachment.tsx +33 -0
  152. package/src/components/Avatar.tsx +26 -0
  153. package/src/components/Chat.tsx +68 -0
  154. package/src/components/Conversation.tsx +220 -0
  155. package/src/components/ConversationBadge.tsx +44 -0
  156. package/src/components/ConversationForm.tsx +217 -0
  157. package/src/components/ConversationList.tsx +61 -0
  158. package/src/components/ConversationListItem.tsx +155 -0
  159. package/src/components/File.tsx +21 -0
  160. package/src/components/FileBrowser.tsx +86 -0
  161. package/src/components/Image.tsx +66 -0
  162. package/src/components/Meeting.tsx +21 -0
  163. package/src/components/MeetingCard.tsx +31 -0
  164. package/src/components/Meetings.tsx +58 -0
  165. package/src/components/Message.tsx +90 -0
  166. package/src/components/Messages.tsx +271 -0
  167. package/src/components/Messenger.tsx +34 -0
  168. package/src/components/NewConversation.tsx +50 -0
  169. package/src/components/Presence.tsx +15 -0
  170. package/src/components/Reactions.tsx +95 -0
  171. package/src/components/SearchUsers.tsx +90 -0
  172. package/src/components/SeenBy.tsx +26 -0
  173. package/src/components/Typing.tsx +131 -0
  174. package/src/contexts/MessengerContext.tsx +44 -0
  175. package/src/contexts/PreviewContext.tsx +105 -0
  176. package/src/contexts/UserContext.tsx +31 -0
  177. package/src/contexts/WeavyContext.tsx +66 -0
  178. package/src/hooks/useBadge.ts +32 -0
  179. package/src/hooks/useChat.ts +32 -0
  180. package/src/hooks/useConversation.ts +28 -0
  181. package/src/hooks/useConversations.ts +27 -0
  182. package/src/hooks/useDebounce.ts +22 -0
  183. package/src/hooks/useEvents.ts +43 -0
  184. package/src/hooks/useFileUploader.ts +35 -0
  185. package/src/hooks/useMembers.ts +27 -0
  186. package/src/hooks/useMessages.ts +42 -0
  187. package/src/hooks/useMessenger.ts +51 -0
  188. package/src/hooks/useMutateChat.ts +44 -0
  189. package/src/hooks/useMutateConversation.ts +40 -0
  190. package/src/hooks/useMutateConversationName.ts +41 -0
  191. package/src/hooks/useMutateDeleteReaction.ts +38 -0
  192. package/src/hooks/useMutateExternalBlobs.ts +39 -0
  193. package/src/hooks/useMutateMeeting.ts +39 -0
  194. package/src/hooks/useMutateMembers.ts +43 -0
  195. package/src/hooks/useMutateMessage.ts +116 -0
  196. package/src/hooks/useMutatePinned.ts +40 -0
  197. package/src/hooks/useMutateReaction.ts +38 -0
  198. package/src/hooks/useMutateRead.ts +40 -0
  199. package/src/hooks/useMutateRemoveMembers.ts +43 -0
  200. package/src/hooks/useMutateTyping.ts +34 -0
  201. package/src/hooks/usePresence.ts +32 -0
  202. package/src/hooks/usePreview.ts +21 -0
  203. package/src/hooks/useReactions.ts +53 -0
  204. package/src/hooks/useSearchUsers.ts +26 -0
  205. package/src/hooks/useThrottle.ts +13 -0
  206. package/src/hooks/useUser.ts +38 -0
  207. package/src/index.ts +33 -0
  208. package/src/types/Chat.ts +3 -0
  209. package/src/types/Conversation.ts +4 -0
  210. package/src/types/ConversationListItem.ts +4 -0
  211. package/src/types/Message.ts +16 -0
  212. package/src/types/Messenger.ts +3 -0
  213. package/src/types/emoji-toolkit.d.ts +1 -0
  214. package/src/types/types.ts +175 -0
  215. package/src/ui/Button.tsx +32 -0
  216. package/src/ui/Dropdown.tsx +58 -0
  217. package/src/ui/Icon.tsx +79 -0
  218. package/src/ui/Overlay.tsx +41 -0
  219. package/src/utils/fileUtilities.ts +230 -0
  220. package/src/utils/infiniteScroll.js +175 -0
  221. package/src/utils/scrollToBottom.js +75 -0
  222. package/src/utils/styles.ts +42 -0
  223. package/tsconfig.json +108 -0
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import Button from '../ui/Button';
3
+ import Icon from '../ui/Icon';
4
+ import { prefix as wy } from "../utils/styles";
5
+
6
+ type Props = {
7
+ meeting: MeetingCardType
8
+
9
+ }
10
+ const MeetingCard = ({ meeting }: Props) => {
11
+
12
+ const handleJoin = () => {
13
+
14
+ }
15
+
16
+ return (
17
+ <div className={wy('attachments')}>
18
+ <a className={wy('attachment')} href={meeting.join_url} target="_blank">
19
+ <div className={wy('attachment-icon')} title="Zoom meeting"><Icon.UI name="zoom" color="#4a8cff" size={4} /></div>
20
+ <div className={wy('attachment-content')}>
21
+ <div className={wy('attachment-title')}>Zoom meeting</div>
22
+ <div className={wy('attachment-meta')}>Meeting ID: {`${meeting.provider_id.substring(0,3)}-${meeting.provider_id.substring(3,6)}-${meeting.provider_id.substring(6)}`}</div>
23
+ <Button.UI className={wy('button-primary')}>Join meeting</Button.UI>
24
+ </div>
25
+ </a>
26
+ </div>
27
+
28
+ )
29
+ }
30
+
31
+ export default MeetingCard;
@@ -0,0 +1,58 @@
1
+ import React, { useContext, useEffect } from "react";
2
+ import { UserContext } from "../contexts/UserContext";
3
+ import { WeavyContext } from "../contexts/WeavyContext";
4
+ import useMutateMeeting from "../hooks/useMutateMeeting";
5
+ import Button from '../ui/Button';
6
+ import Icon from '../ui/Icon';
7
+
8
+ type Props = {
9
+ onMeetingAdded: Function
10
+ }
11
+
12
+ const Meetings = ({ onMeetingAdded }: Props) => {
13
+
14
+ const { options } = useContext(WeavyContext);
15
+ const { user } = useContext(UserContext);
16
+
17
+ const addMeeting = useMutateMeeting();
18
+ useEffect(() => {
19
+ window.addEventListener("message", createMeeting);
20
+
21
+
22
+ return () => {
23
+ window.removeEventListener("message", createMeeting);
24
+ }
25
+ }, [])
26
+
27
+ const createMeeting = async (e: any) => {
28
+
29
+ switch (e.data.name) {
30
+ case "zoom-signed-in":
31
+ var meeting = await addMeeting.mutateAsync({ provider: "zoom" });
32
+ onMeetingAdded(meeting)
33
+ break;
34
+
35
+ case "teams-signed-in":
36
+ //console.log("Add Teams meeting");
37
+ break;
38
+ }
39
+
40
+ }
41
+
42
+ const handleZoom = () => {
43
+ window.open(`${options?.zoomAuthenticationUrl}&state=${user.id}`,
44
+ "zoomAuthWin",
45
+ "height=640,width=480");
46
+ }
47
+
48
+ return (
49
+ <>
50
+ {options?.zoomAuthenticationUrl &&
51
+ <Button.UI onClick={handleZoom} title="Add Zoom meeting"><Icon.UI name="zoom" /></Button.UI>
52
+ }
53
+ </>
54
+
55
+ )
56
+ }
57
+
58
+ export default Meetings;
@@ -0,0 +1,90 @@
1
+ import React, { FC } from 'react';
2
+ import dayjs from 'dayjs';
3
+ import { MessageProps } from "../types/Message"
4
+ import Attachment from './Attachment';
5
+ import { ReactionsMenu, ReactionsList } from './Reactions';
6
+ import joypixels from 'emoji-toolkit';
7
+ import { Image, ImageGrid } from "./Image"
8
+ import SeenBy from './SeenBy';
9
+ import Avatar from "./Avatar";
10
+ import MeetingCard from './MeetingCard';
11
+ import usePreview from '../hooks/usePreview';
12
+ import { prefix as wy } from "../utils/styles";
13
+
14
+ const Message: FC<MessageProps> = ({ id, html, temp, me, avatar, name, created_at, attachments, meeting, parentId, reactions, seenBy }) => {
15
+
16
+ const { open, close } = usePreview(attachments);
17
+
18
+ var messageClassName = wy("message" + (me ? " message-me" : ""));
19
+ var images = attachments?.filter((a: AttachmentType) => a.kind === "image" && a.thumbnail_url);
20
+ var files = attachments?.filter((a: AttachmentType) => a.kind !== "image" || !a.thumbnail_url);
21
+
22
+ const date = dayjs.utc(created_at).tz(dayjs.tz.guess());
23
+
24
+ const handlePreviewClick = (e: React.MouseEvent<HTMLInputElement>, attachmentId: number) => {
25
+ e.preventDefault();
26
+ open(attachmentId);
27
+ }
28
+
29
+ return (
30
+ <>
31
+ <div className={messageClassName}>
32
+ {!me && (
33
+ <div className={wy('message-author')}>
34
+ {avatar && <Avatar src={avatar} size={32} name={name} />}
35
+ </div>
36
+ )}
37
+ <div className={wy('message-content')}>
38
+ <div className={wy('message-meta')}>
39
+ <time dateTime={created_at} title={date.format('LLLL')}>{date.fromNow()}</time>
40
+ </div>
41
+ <div className={wy('message-content-row')}>
42
+ <div className={wy('message-bubble')}>
43
+ {temp &&
44
+ <div className={wy('message-text')}>{html}</div>
45
+ }
46
+ {!temp &&
47
+ <>
48
+ {images && !!images.length && <ImageGrid>
49
+ {images.map((a: AttachmentType) =>
50
+ <Image onClick={(e) => handlePreviewClick(e, a.id)} key={a.id} src={a.download_url} previewSrc={a.preview_url} width={a.width} height={a.height} />
51
+ )}
52
+ </ImageGrid>}
53
+
54
+ {html && <div className={wy('message-text')} dangerouslySetInnerHTML={{ __html: joypixels.shortnameToUnicode(html || "") }}></div>}
55
+
56
+ {meeting &&
57
+ <MeetingCard meeting={meeting} />
58
+ }
59
+
60
+ {files && !!files.length && <div className={wy('attachments')}>
61
+ {files.map((a: AttachmentType) =>
62
+ <Attachment key={a.id} name={a.name} previewFormat={a.kind} provider={a.provider} url={a.download_url} previewUrl={a.provider ? a.external_url : a.preview_url} mediaType={a.media_type} kind={a.kind} size={a.size} />
63
+ )}
64
+ </div>}
65
+ </>
66
+
67
+ }
68
+ </div>
69
+ <div className={wy('message-buttons')}>
70
+ {!temp && <ReactionsMenu id={id} reactions={reactions} />}
71
+ </div>
72
+ </div>
73
+
74
+ {!temp && (
75
+ <div className={wy('reactions')}>
76
+ <ReactionsList id={id} parentId={parentId} reactions={reactions} />
77
+ </div>
78
+ )}
79
+
80
+ </div>
81
+
82
+
83
+ </div>
84
+ <SeenBy id={id} parentId={parentId} seenBy={seenBy} createdAt={created_at} />
85
+ </>
86
+ )
87
+
88
+ }
89
+
90
+ export default Message;
@@ -0,0 +1,271 @@
1
+ import React, { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import { UserContext } from '../contexts/UserContext';
3
+ import useMessages from '../hooks/useMessages';
4
+ import Button from '../ui/Button';
5
+ import Message from './Message';
6
+ import { createReverseScroller } from "../utils/infiniteScroll";
7
+ import { scrollParentToBottom, isParentAtBottom } from "../utils/scrollToBottom";
8
+ import ConversationForm from './ConversationForm';
9
+ import useEvents from '../hooks/useEvents';
10
+ import useMutateRead from '../hooks/useMutateRead';
11
+ import useMutateMessage from '../hooks/useMutateMessage';
12
+ import { useQueryClient } from 'react-query';
13
+ import { WeavyContext } from '../contexts/WeavyContext';
14
+ import { prefix as wy } from "../utils/styles";
15
+ import Avatar from './Avatar';
16
+
17
+ type Props = {
18
+ id: number,
19
+ members: MembersResult | undefined,
20
+ displayName?: string,
21
+ avatarUrl?: string
22
+ }
23
+
24
+ const Messages = ({ id, members, displayName, avatarUrl }: Props) => {
25
+ var reverseScroller:IntersectionObserver | null;
26
+
27
+ const { user } = useContext(UserContext);
28
+
29
+ const queryClient = useQueryClient();
30
+ const { client } = useContext(WeavyContext);
31
+
32
+ const readMoreRef = useRef<any>();
33
+ const messagesEndRef = useRef<any>();
34
+ const [resolveScrollerFetch, setResolveScrollerFetch] = useState<Function | null>()
35
+
36
+ const { dispatch, on, events } = useEvents();
37
+
38
+ const { isLoading, isError, data, error, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } = useMessages(id, {});
39
+
40
+
41
+ const readMessageMutation = useMutateRead();
42
+ const addMessageMutation = useMutateMessage();
43
+
44
+ // scroll to bottom when data changes
45
+ useLayoutEffect(() => {
46
+ //if (id && !isLoading && !isLoadingMembers && !isLoadingConversation) {
47
+ if (id && !isLoading) {
48
+ //console.log("useLayoutEffect scroller", id)
49
+ // scroll to bottom when selecting a conversation
50
+ scrollParentToBottom(messagesEndRef.current);
51
+
52
+ // Scroll to bottom again two frames later, because the height is changing
53
+ requestAnimationFrame(() => requestAnimationFrame(() => scrollParentToBottom(messagesEndRef.current)));
54
+
55
+ // register infinite scroll
56
+
57
+ reverseScroller?.disconnect();
58
+
59
+ reverseScroller = createReverseScroller(readMoreRef.current, () => {
60
+ if (hasNextPage) {
61
+ return fetchNextPage().then(() => {
62
+ // Wait for useLayoutEffect before resolving
63
+ return new Promise((resolve: Function) => setResolveScrollerFetch(resolve))
64
+ })
65
+ }
66
+ })
67
+ console.log("rs scroller", reverseScroller.root);
68
+ } else {
69
+ reverseScroller?.disconnect();
70
+ reverseScroller = null;
71
+ }
72
+
73
+ return () => {
74
+ reverseScroller?.disconnect();
75
+ reverseScroller = null;
76
+ }
77
+ }, [id, isLoading]);
78
+
79
+
80
+ useLayoutEffect(() => {
81
+ // Resolve fetchNextPage after layout has been painted
82
+ if (!isFetchingNextPage && resolveScrollerFetch) {
83
+ resolveScrollerFetch()
84
+ setResolveScrollerFetch(null);
85
+ }
86
+ }, [data]);
87
+
88
+ useEffect(() => {
89
+
90
+ if (id) {
91
+
92
+ window.addEventListener('focus', handleFocus, false)
93
+
94
+ // mark conversation as read
95
+ readMessageMutation.mutate({ id: id, read: true })
96
+
97
+ client.subscribe(`a${id}`, "message-inserted", handleRealtimeMessage);
98
+ client.subscribe(`a${id}`, "conversation-read", handleRealtimeSeenBy);
99
+ client.subscribe(`a${id}`, "reaction-inserted", handleRealtimeReactionInserted);
100
+ client.subscribe(`a${id}`, "reaction-deleted", handleRealtimeReactionDeleted);
101
+ }
102
+
103
+ return () => {
104
+ window.removeEventListener('focus', handleFocus, false)
105
+
106
+ if (id) {
107
+
108
+ // remove additional pages in cache. Only get first page
109
+ let qd = queryClient.getQueryData(["messages", id]);
110
+ if (qd) {
111
+ queryClient.setQueryData(["messages", id], (data: any) => ({
112
+ pages: data?.pages.splice(0, 1),
113
+ pageParams: data?.pageParams.splice(0, 1),
114
+ }));
115
+ }
116
+
117
+ client.unsubscribe(`a${id}`, "message-inserted", handleRealtimeMessage);
118
+ client.unsubscribe(`a${id}`, "conversation-read", handleRealtimeSeenBy);
119
+ client.unsubscribe(`a${id}`, "reaction-inserted", handleRealtimeReactionInserted);
120
+ client.unsubscribe(`a${id}`, "reaction-deleted", handleRealtimeReactionDeleted);
121
+ }
122
+ }
123
+ }, [id]);
124
+
125
+ const handleRealtimeReactionInserted = useCallback((data: ReactionType) => {
126
+ dispatch("reaction-inserted", data);
127
+ }, [id]);
128
+
129
+ const handleRealtimeReactionDeleted = useCallback((data: ReactionType) => {
130
+ dispatch("reaction-deleted", data);
131
+ }, [id]);
132
+
133
+ // handle new message from post form
134
+ const handleNewMessage = (text: string, attachments: [], meetings: []) => {
135
+ addMessageMutation.mutate({ id: id, text: text, userId: user.id, attachments: attachments, meetings: meetings }, {
136
+ onSuccess: () => { requestAnimationFrame(() => scrollParentToBottom(messagesEndRef.current, true))}
137
+ });
138
+
139
+ // mark conversation as read
140
+ setTimeout(() => { readMessageMutation.mutate({ id: id, read: true }) }, 1000);
141
+
142
+
143
+ requestAnimationFrame(() => scrollParentToBottom(readMoreRef.current, true));
144
+ }
145
+
146
+ const handleRealtimeSeenBy = async (data: any) => {
147
+ let isAtBottom = isParentAtBottom(readMoreRef.current);
148
+ // how to do this better?
149
+ queryClient.invalidateQueries(["members", id])
150
+
151
+ if (isAtBottom) {
152
+ requestAnimationFrame(() => scrollParentToBottom(readMoreRef.current, true));
153
+ }
154
+ }
155
+
156
+ // real time insert-message handler
157
+ const handleRealtimeMessage = useCallback((message: MessageType) => {
158
+ if (message.app_id !== id || message.created_by.id === user.id) return;
159
+
160
+ // mark conversation as read
161
+ readMessageMutation.mutate({ id: id, read: true })
162
+
163
+ const previousData = queryClient.getQueryData<any>(['messages', id]);
164
+
165
+ if (previousData && previousData.pages) {
166
+ let isAtBottom = isParentAtBottom(readMoreRef.current);
167
+
168
+ const newPagesArray = previousData.pages.map((page: any, i: number) => {
169
+ // remove temp message
170
+ if (i === 0) {
171
+ page.data = [
172
+ ...page.data,
173
+ message
174
+ ]
175
+
176
+ }
177
+ return page;
178
+
179
+ }) ?? [];
180
+
181
+ queryClient.setQueryData(["messages", id], (data: any) => ({
182
+ pages: newPagesArray,
183
+ pageParams: data.pageParams,
184
+ }));
185
+
186
+ if (isAtBottom) {
187
+ requestAnimationFrame(() => requestAnimationFrame(() => scrollParentToBottom(readMoreRef.current, true)));
188
+ }
189
+ }
190
+
191
+
192
+ }, [id]);
193
+
194
+ const handleFocus = useCallback(() => {
195
+ if (!id) return;
196
+
197
+ readMessageMutation.mutate({ id: id, read: true })
198
+ }, [id]);
199
+
200
+ let messageHeader = <div className={wy('avatar-header')}>
201
+ {avatarUrl && displayName && <Avatar src={avatarUrl} name={displayName} id={id} size={128} /> || ''}
202
+ {displayName && <div className={wy('avatar-display-name')}>{displayName}</div> || ''}
203
+ </div>;
204
+
205
+ let loadMoreButton = <Button.UI onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage} className={wy('message-readmore')}>Load more</Button.UI>;
206
+
207
+ let messages = (
208
+ <>
209
+ <div className={wy('message-readmore')} ref={readMoreRef}>
210
+ {isFetchingNextPage
211
+ ? 'Loading more...'
212
+ : hasNextPage
213
+ ? loadMoreButton
214
+ : messageHeader}
215
+
216
+ </div>
217
+ {data && members && data.pages && data.pages.map((group, i) => {
218
+ // Reverse key since page loading is reversed
219
+ return <React.Fragment key={data.pages.length - i}>
220
+ {
221
+ group.data?.map((item: MessageType) => {
222
+
223
+ return <Message
224
+ key={item.id}
225
+ id={item.id}
226
+ html={item.html}
227
+ temp={item.temp}
228
+ me={item.created_by.id === user.id}
229
+ avatar={item.created_by.avatar_url}
230
+ name={item.created_by.display_name}
231
+ created_at={item.created_at}
232
+ attachments={item.attachments}
233
+ meeting={item.meeting}
234
+ parentId={id}
235
+ reactions={item.reactions}
236
+ //reactions_count={item.reactions_count}
237
+ seenBy={members.data.length > 0 ? members.data.filter((member) => {
238
+ const hasRead = member.read_at >= item.created_at;
239
+ const nothingLaterRead = !data.pages.map((p) => p.data).flat().find((message: MessageType) => { return message.id > item.id && member.read_at >= message.created_at });
240
+ return hasRead && nothingLaterRead && member.id !== user.id;
241
+ }) : []}
242
+ />
243
+ })
244
+ }
245
+ </React.Fragment>
246
+ })}
247
+ <div ref={messagesEndRef} />
248
+ </>
249
+ );
250
+
251
+ if (isLoading) {
252
+ messages = (
253
+ <div>Loading messages...</div>
254
+ )
255
+ }
256
+
257
+
258
+ return (
259
+ <>
260
+ <div id="container" className={wy('messages')}>
261
+ {messages}
262
+ </div>
263
+ <div className={wy('message-editor')}>
264
+ <ConversationForm key={id} conversationId={id} handleInsert={handleNewMessage} />
265
+ </div>
266
+ </>
267
+
268
+ );
269
+ }
270
+
271
+ export default Messages;
@@ -0,0 +1,34 @@
1
+ import React, { FC, useContext } from 'react';
2
+ import MessengerProvider from '../contexts/MessengerContext';
3
+ import Conversation from './Conversation';
4
+ import ConversationList from './ConversationList';
5
+ import { Messenger } from '../types/Messenger';
6
+ import { WeavyContext } from '../contexts/WeavyContext';
7
+ import { prefix as wy } from "../utils/styles";
8
+
9
+ const Messenger: FC<Messenger> = ({ options }) => {
10
+
11
+ const { client } = useContext(WeavyContext);
12
+
13
+ if (!client) {
14
+ throw new Error('Weavy Messenger component must be used within an WeavyProvider');
15
+ }
16
+
17
+ return (
18
+ <MessengerProvider options={options}>
19
+ <div className={wy('messenger-provider')}>
20
+
21
+ <div className={wy('messenger-sidebar')}>
22
+ <ConversationList />
23
+ </div>
24
+
25
+ <div className={wy('messenger-conversation')}>
26
+ <Conversation id={null} />
27
+ </div>
28
+ </div>
29
+
30
+ </MessengerProvider>
31
+ )
32
+ }
33
+
34
+ export default Messenger;
@@ -0,0 +1,50 @@
1
+ import React, { useState } from 'react';
2
+ import useMutateConversation from '../hooks/useMutateConversation';
3
+ import Icon from '../ui/Icon';
4
+ import Overlay from '../ui/Overlay';
5
+ import Button from '../ui/Button';
6
+ import SearchUsers from './SearchUsers';
7
+ import { prefix as wy } from "../utils/styles";
8
+
9
+ const NewConversation = () => {
10
+ const [modalOpen, setModalOpen] = useState(false);
11
+ const addConversationMutation = useMutateConversation();
12
+
13
+ const handleCreate = async (selected: UserType[]) => {
14
+
15
+ const membersList = selected.map((m) => m.id);
16
+ await addConversationMutation.mutateAsync({ members: membersList });
17
+ setModalOpen(false);
18
+ }
19
+
20
+ const handleOpen = () => {
21
+ setModalOpen(true);
22
+ }
23
+
24
+ const handleClose = () => {
25
+ setModalOpen(false);
26
+ }
27
+
28
+ return (
29
+ <>
30
+
31
+ <Button.UI onClick={handleOpen}><Icon.UI name="plus" /></Button.UI>
32
+
33
+ <Overlay.UI isOpen={modalOpen} className={wy('modal')}>
34
+ <header className={wy('appbars')}>
35
+ <nav className={wy('appbar')}>
36
+ <Button.UI onClick={handleClose}><Icon.UI name='close' /></Button.UI>
37
+ <div className={wy('appbar-text')}>Create conversation</div>
38
+ </nav>
39
+ </header>
40
+
41
+ <SearchUsers handleSubmit={handleCreate} buttonTitle="Create conversation"/>
42
+ </Overlay.UI>
43
+ </>
44
+
45
+
46
+
47
+ )
48
+ }
49
+
50
+ export default React.memo(NewConversation);
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import { prefix as wy } from "../utils/styles";
3
+
4
+ type Props = {
5
+ id: number,
6
+ status: string
7
+ }
8
+
9
+ const Presence = ({ id, status }: Props) => {
10
+ return (
11
+ <span className={wy(`presence ${status === "active" ? "presence-active" : ""}`)} data-presence-id={id}></span>
12
+ )
13
+ }
14
+
15
+ export default Presence;
@@ -0,0 +1,95 @@
1
+ import React, { useContext, useEffect, useState } from "react";
2
+ import useMutateReaction from "../hooks/useMutateReaction";
3
+ import Icon from "../ui/Icon";
4
+ import Button from "../ui/Button";
5
+ import { MessengerContext } from "../contexts/MessengerContext";
6
+ import classNames from "classnames";
7
+
8
+ import { prefix as wy } from "../utils/styles";
9
+ import useReactions from "../hooks/useReactions";
10
+ import useMutateDeleteReaction from "../hooks/useMutateDeleteReaction";
11
+
12
+ type ReactionMenuProps = {
13
+ id: number,
14
+ reactions: ReactableType[]
15
+ }
16
+
17
+ type ReactionsProps = {
18
+ id: number,
19
+ parentId: number | null,
20
+ reactions: ReactableType[]
21
+ }
22
+
23
+ export const ReactionsMenu = ({ id, reactions }: ReactionMenuProps) => {
24
+ const { reactionsList } = useReactions(id, reactions);
25
+ const reactionMutation = useMutateReaction();
26
+ const reactionDeleteMutation = useMutateDeleteReaction();
27
+ const [visible, setVisible] = useState<boolean>(false);
28
+ const { options } = useContext(MessengerContext);
29
+ const [reactedEmoji, setReactedEmoji] = useState<string>('');
30
+
31
+ const emojis = options?.reactions;
32
+
33
+ useEffect(() => {
34
+ var filtered = reactionsList.find((e) => e.has_reacted);
35
+ setReactedEmoji(filtered ? filtered.content : '')
36
+ }, [reactionsList]);
37
+
38
+ useEffect(() => {
39
+ if (visible) {
40
+ document.addEventListener("click", () => { setVisible(false) });
41
+ } else {
42
+ document.removeEventListener("click", () => { setVisible(false) });
43
+ }
44
+
45
+ }, [visible]);
46
+
47
+ const toggleReactionMenu = (e: any) => {
48
+ e.stopPropagation();
49
+ setVisible(!visible);
50
+ }
51
+
52
+ const handleReaction = async (e: any) => {
53
+ // check if the reaction already exists for the user
54
+ const existing = reactionsList.find((r) => r.has_reacted)
55
+ const emoji = e.target.dataset.emoji;
56
+
57
+ if (existing) {
58
+ // delete existing reaction
59
+ await reactionDeleteMutation.mutateAsync({ id: id, reaction: emoji });
60
+ }
61
+
62
+ // add if not same reaction as before
63
+ if (!existing || existing.content !== emoji) {
64
+ await reactionMutation.mutateAsync({ id: id, reaction: emoji });
65
+ }
66
+
67
+ setVisible(false);
68
+ }
69
+
70
+ return (
71
+ <div className={wy(classNames("", { "active": visible }))} style={{ position: 'relative' }}>
72
+ <Button.UI onClick={toggleReactionMenu}><Icon.UI name="emoticon-outline" size={1} /></Button.UI>
73
+ <div className={wy('reaction-menu dropdown-menu')} style={{ display: visible ? 'block' : 'none', position: 'absolute', top: '-3.25rem' }}>
74
+ <div className={wy('reaction-picker')}>
75
+ {emojis?.map((r: string, i: number) => {
76
+ return <Button.UI key={i} onClick={handleReaction} className={wy(classNames("button-icon reaction-button", { "active": reactedEmoji === r }))} data-emoji={r}>{r}</Button.UI> //reactedEmoji
77
+ })}
78
+ </div>
79
+ </div>
80
+ </div>
81
+ )
82
+
83
+ }
84
+
85
+ export const ReactionsList = ({ id, reactions }: ReactionsProps) => {
86
+ const { reactionsList } = useReactions(id, reactions);
87
+
88
+ return (
89
+ <>
90
+ {reactionsList && reactionsList.map((r: ReactionGroup, i: number) => {
91
+ return <span key={i} className={wy('reaction')} title={r.count.toString()}>{r.content}</span> //r.has_reacted
92
+ })}
93
+ </>
94
+ )
95
+ }