@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,44 @@
1
+ import React, { useContext, useEffect, useState } from "react";
2
+ import { WeavyContext } from "../contexts/WeavyContext";
3
+ import useBadge from "../hooks/useBadge";
4
+ import { prefix as wy } from "../utils/styles";
5
+
6
+ const ConversationBadge = () => {
7
+ const { client } = useContext(WeavyContext);
8
+ const [badge, setBadge] = useState<number>(0);
9
+
10
+ if (!client) {
11
+ throw new Error('Weavy Badge component must be used within an WeavyProvider');
12
+ }
13
+
14
+ const { isLoading, data } = useBadge();
15
+
16
+ const handleBadge = (data: BadgeType) => {
17
+ setBadge(data.private + data.rooms);
18
+ }
19
+
20
+ useEffect(() => {
21
+ if(data){
22
+ setBadge(data.private + data.rooms);
23
+ }
24
+ }, [data])
25
+
26
+ useEffect(() => {
27
+ client.subscribe(null, "conversation-badge", handleBadge);
28
+
29
+ return () => {
30
+ client.unsubscribe(null, "conversation-badge", handleBadge);
31
+ }
32
+ }, [])
33
+
34
+ return (
35
+ <>
36
+ {!isLoading && badge > 0 &&
37
+ <span className={wy('badge badge-danger')}>{badge}</span>
38
+ }
39
+ </>
40
+
41
+ )
42
+ }
43
+
44
+ export default ConversationBadge;
@@ -0,0 +1,217 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { useQueryClient } from 'react-query';
3
+ import useFileUploader from '../hooks/useFileUploader';
4
+ import File from './File';
5
+ import Icon from '../ui/Icon';
6
+ import Button from '../ui/Button';
7
+ import useMutateTyping from '../hooks/useMutateTyping';
8
+ import useThrottle from '../hooks/useThrottle';
9
+ import { flushSync } from 'react-dom';
10
+ import Meetings from './Meetings';
11
+ import Meeting from './Meeting';
12
+ import FileBrowser from './FileBrowser';
13
+ import { getIcon } from '../utils/fileUtilities';
14
+ import { prefix as wy } from "../utils/styles";
15
+
16
+
17
+ type Props = {
18
+ conversationId: number,
19
+ handleInsert: Function
20
+ }
21
+
22
+ let uploadedFiles: any[] = [];
23
+
24
+ const ConversationForm = ({ conversationId, handleInsert }: Props) => {
25
+
26
+ const [text, setText] = useState<string>("");
27
+ const [files, setFiles] = useState<string>("");
28
+ const [fileCount, setFileCount] = useState<number>(0);
29
+ const [currentUploadCount, setCurrentUploadCount] = useState<number>(1);
30
+ const [working, setWorking] = useState<boolean>(false);
31
+ const queryClient = useQueryClient();
32
+ const [attachments, setAttachments] = useState<FileType[]>([]);
33
+ const [meetings, setMeetings] = useState<MeetingType[]>([]);
34
+ const [uploadError, setUploadError] = useState<string | boolean>(false);
35
+ const startTyping = useMutateTyping();
36
+ const textInput = useRef<HTMLTextAreaElement>(null);
37
+
38
+ let fileInput: HTMLInputElement | null;
39
+
40
+ const handleUploaded = (attachment: any) => {
41
+ // update attachment list
42
+ flushSync(() => {
43
+ if (attachment.status && attachment.status !== 200) {
44
+ setUploadError(attachment.detail);
45
+ } else {
46
+ setAttachments([...attachments, attachment[0]]);
47
+ }
48
+ })
49
+
50
+
51
+ }
52
+
53
+ const { mutateAsync: uploadFile, isSuccess: uploadSuccess } = useFileUploader(handleUploaded);
54
+
55
+
56
+ useEffect(() => {
57
+ // set stored text and attachments if available
58
+ let textValue: any = queryClient.getQueryData(["form-text", conversationId]) || "";
59
+ setText(textValue);
60
+
61
+ let attachmentValue: any = queryClient.getQueryData(["form-attachments", conversationId]) || [];
62
+ setAttachments(attachmentValue);
63
+ }, [conversationId]);
64
+
65
+ useEffect(() => {
66
+ // store attachments
67
+ queryClient.setQueryData(["form-attachments", conversationId], attachments);
68
+ }, [attachments]);
69
+
70
+ useEffect(() => {
71
+ handleAutoGrow();
72
+ }, [text])
73
+
74
+
75
+
76
+ const handleInsertMessage = (e: React.KeyboardEvent<EventTarget>) => {
77
+
78
+ if(e.type === 'keydown' && !(e.key === "Enter" && e.ctrlKey )) return;
79
+
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+
83
+ if (text === "" && attachments.length === 0 && meetings.length === 0) return;
84
+
85
+ handleInsert(text, attachments, meetings);
86
+ setText("");
87
+ setFiles("");
88
+ setAttachments([]);
89
+ setMeetings([]);
90
+
91
+ // clear stored text and attachments
92
+ queryClient.setQueryData(["form-text", conversationId], "");
93
+ queryClient.setQueryData(["form-attachments", conversationId], []);
94
+ }
95
+
96
+ const handleChange = (e: any) => {
97
+
98
+ // store text
99
+ queryClient.setQueryData(["form-text", conversationId], e.target.value);
100
+
101
+ // set text value
102
+ setText(e.target.value);
103
+
104
+ }
105
+
106
+ const handleFileUpload = async (e: any) => {
107
+ setUploadError(false);
108
+ setFiles(e.target.value);
109
+ setFileCount(e.target.files.length);
110
+ setWorking(true);
111
+
112
+ for (var i = 0; i < e.target.files.length; i++) {
113
+ setCurrentUploadCount(i + 1);
114
+ const file = e.target.files[i];
115
+ await uploadFile({ request: { file } });
116
+ }
117
+
118
+ setWorking(false)
119
+ }
120
+
121
+ const handleRemoveFile = (id: number, e: any) => {
122
+ setFiles("");
123
+ setAttachments(attachments.filter((a: FileType) => {
124
+ return a.id !== id
125
+ }));
126
+ }
127
+
128
+ const handleKeyPress = () => {
129
+ startTyping.mutate({ id: conversationId });
130
+ }
131
+
132
+ const openFileInput = (e: any) => {
133
+ fileInput?.click();
134
+ }
135
+
136
+ const handleAddMeeting = (data: any) => {
137
+ if (meetings.length > 0) return;
138
+
139
+ setMeetings([...meetings, data]);
140
+ }
141
+
142
+ const handleExternalFileAdded = (blobs: FileType[]) => {
143
+ setAttachments([...attachments, ...blobs]);
144
+ }
145
+
146
+ const handleRemoveMeeting = (id: number, e: any) => {
147
+ setMeetings(meetings.filter((a: MeetingType) => {
148
+ return a.id !== id
149
+ }));
150
+ }
151
+
152
+ const handleAutoGrow = () => {
153
+ if(textInput.current && textInput.current.parentNode){
154
+ const parent = textInput.current.parentNode as HTMLElement;
155
+ parent.dataset.replicatedValue = textInput.current.value;
156
+ }
157
+
158
+ }
159
+
160
+
161
+ return (
162
+ <form className={wy('message-form')}>
163
+ {uploadError &&
164
+ <div>{uploadError}</div>
165
+ }
166
+
167
+ {(working || attachments.length > 0 || meetings.length > 0) &&
168
+ <div>
169
+ {working &&
170
+ <div>Now uploading ({currentUploadCount} of {fileCount}) selected files</div>
171
+ }
172
+ <div className={wy('picker-list')}>
173
+ {attachments.map((a: FileType) => {
174
+ let { icon } = getIcon(a.name);
175
+ return (
176
+ <div key={a.id} className={wy('picker-list-item')}>
177
+ <File id={a.id} name={a.name} className={wy('picker-list-item-title')} icon={ icon } />
178
+ <Button.UI onClick={handleRemoveFile.bind(ConversationForm, a.id)}><Icon.UI name='close-circle' /></Button.UI>
179
+ </div>
180
+
181
+ )
182
+ })}
183
+
184
+ {meetings.map((m: MeetingType) => {
185
+ return (
186
+ <div key={m.id} className={wy('picker-list-item')}>
187
+ <Meeting id={m.id} title={m.provider} className={wy('picker-list-item-title')} />
188
+ <Button.UI onClick={handleRemoveMeeting.bind(ConversationForm, m.id)}><Icon.UI name='close-circle' /></Button.UI>
189
+ </div>
190
+
191
+ )
192
+ })}
193
+ </div>
194
+ </div>
195
+ }
196
+ <div className={wy('message-editor-inputs')}>
197
+
198
+ <div className={wy('message-editor-buttons')}>
199
+ <input type="file" ref={input => fileInput = input} value={files} onChange={handleFileUpload} multiple hidden tabIndex={-1} />
200
+ <Button.UI title="Upload attachment" onClick={openFileInput}><Icon.UI name="attachment" /></Button.UI>
201
+ <Meetings onMeetingAdded={handleAddMeeting} />
202
+ <FileBrowser onFileAdded={handleExternalFileAdded} />
203
+ </div>
204
+
205
+ <div className={wy('message-editor-text message-editor-grow')}>
206
+ <textarea rows={1} ref={textInput} className={wy('message-editor-textfield message-editor-textcontent')} value={text} onChange={handleChange} onKeyDown={handleInsertMessage} onKeyPress={useThrottle(handleKeyPress, 4000)}></textarea>
207
+ </div>
208
+ <div className={wy('message-editor-buttons')}>
209
+ <Button.UI type="button" onClick={handleInsertMessage} ><Icon.UI name="send"/></Button.UI>
210
+ </div>
211
+ </div>
212
+ </form>
213
+
214
+ )
215
+ }
216
+
217
+ export default ConversationForm;
@@ -0,0 +1,61 @@
1
+ import React, { useContext, useEffect } from 'react';
2
+ import { WeavyContext } from '../contexts/WeavyContext';
3
+ import useConversations from '../hooks/useConversations';
4
+ import ConversationListItem from './ConversationListItem';
5
+ import NewConversation from './NewConversation';
6
+ import Avatar from './Avatar';
7
+ import { UserContext } from '../contexts/UserContext';
8
+ import { prefix as wy } from "../utils/styles";
9
+
10
+ const ConversationList = () => {
11
+ const { client } = useContext(WeavyContext);
12
+ const { user } = useContext(UserContext);
13
+ const { data, isLoading, refetch } = useConversations();
14
+
15
+ if (!client) {
16
+ throw new Error('Weavy ConversationList component must be used within an WeavyProvider');
17
+ }
18
+
19
+ useEffect(() => {
20
+
21
+ client.subscribe(null, "app-inserted", handleAppInserted);
22
+ client.subscribe(null, "member-added", handleAppInserted);
23
+
24
+ return () => {
25
+ client.unsubscribe(null, "app-inserted", handleAppInserted);
26
+ client.unsubscribe(null, "member-added", handleAppInserted);
27
+ }
28
+ }, []);
29
+
30
+ const handleAppInserted = (data: ConversationType) => {
31
+ refetch();
32
+ }
33
+
34
+
35
+ if (isLoading) {
36
+ return (
37
+ <div>Loading Conversation list...</div>
38
+ )
39
+ }
40
+
41
+ return (
42
+ <>
43
+ <header className={wy('appbars')}>
44
+ <nav className={wy('appbar')}>
45
+ <Avatar src={user.avatar_url} name={user.title} presence={user.presence} id={user.id} size={24} />
46
+ <div>Messenger</div>
47
+ <NewConversation />
48
+ </nav>
49
+ </header>
50
+ <div className={wy('conversations')}>
51
+ {data && data.data?.map((item) =>
52
+ <ConversationListItem key={item.id} refetchConversations={refetch} item={item} />
53
+ )}
54
+
55
+ {/* <a className={wy('pager')}><svg className={wy('spinner spin')} width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle fill="none" cx="12" cy="12" r="11" stroke-linecap="butt" stroke-width="2"></circle></svg></a> */}
56
+ </div>
57
+ </>
58
+ )
59
+ }
60
+
61
+ export default ConversationList;
@@ -0,0 +1,155 @@
1
+ import React, { useCallback, useContext, useEffect } from 'react';
2
+ import joypixels from 'emoji-toolkit';
3
+ import dayjs from 'dayjs';
4
+ import { MessengerContext } from '../contexts/MessengerContext';
5
+ import { WeavyContext } from '../contexts/WeavyContext';
6
+ import useMutatePinned from '../hooks/useMutatePinned';
7
+ import useMutateRead from '../hooks/useMutateRead';
8
+ import { ConversationListItemProps } from '../types/ConversationListItem';
9
+ import Dropdown from '../ui/Dropdown';
10
+ import Icon from '../ui/Icon';
11
+ import Button from '../ui/Button';
12
+ import Typing from './Typing';
13
+ import useMutateRemoveMembers from '../hooks/useMutateRemoveMembers';
14
+ import Avatar from './Avatar';
15
+ import { UserContext } from '../contexts/UserContext';
16
+ import { prefix as wy } from "../utils/styles";
17
+
18
+
19
+ const ConversationListItem = ({ item, refetchConversations }: ConversationListItemProps) => {
20
+
21
+ const { client } = useContext(WeavyContext);
22
+ const { setSelectedConversationId, selectedConversationId } = useContext(MessengerContext);
23
+ const { user } = useContext(UserContext);
24
+ const readMutation = useMutateRead();
25
+ const pinMutation = useMutatePinned();
26
+ const removeMembers = useMutateRemoveMembers();
27
+ const date = dayjs.utc(item.last_message?.created_at).tz(dayjs.tz.guess());
28
+
29
+ const ChatRoom = "edb400ac-839b-45a7-b2a8-6a01820d1c44";
30
+
31
+ const handleClick = (e: React.MouseEvent<HTMLAnchorElement>, id: any) => {
32
+ e.preventDefault();
33
+ setSelectedConversationId(id);
34
+ }
35
+
36
+ const handleAppUpdated = useCallback((data: ConversationType) => {
37
+ if (data.id !== item.id) return;
38
+ refetchConversations();
39
+
40
+ }, [item.id])
41
+
42
+ useEffect(() => {
43
+ client.subscribe(`a${item.id}`, "app-updated", handleAppUpdated);
44
+
45
+ return () => {
46
+ client.unsubscribe(`a${item.id}`, "app-updated", handleAppUpdated);
47
+ }
48
+ }, [item.id])
49
+
50
+ const otherId = item.type !== ChatRoom ? item.member_ids.find((i) => { return i != user.id }) : null;
51
+
52
+ const handleUnread = (e: React.MouseEvent<HTMLAnchorElement>) => {
53
+ e.preventDefault();
54
+ readMutation.mutate({ id: item.id, read: false });
55
+ }
56
+
57
+ const handleRead = (e: React.MouseEvent<HTMLAnchorElement>) => {
58
+ e.preventDefault();
59
+ readMutation.mutate({ id: item.id, read: true });
60
+ }
61
+
62
+ const handlePin = (e: React.MouseEvent<HTMLAnchorElement>) => {
63
+ e.preventDefault();
64
+ pinMutation.mutate({ id: item.id, pin: true });
65
+ }
66
+
67
+ const handleUnpin = (e: React.MouseEvent<HTMLAnchorElement>) => {
68
+ e.preventDefault();
69
+ pinMutation.mutate({ id: item.id, pin: false });
70
+ }
71
+
72
+ const handleLeaveConversation = () => {
73
+ removeMembers.mutate({ id: item.id, members: [user.id] });
74
+ }
75
+
76
+ // const handleStar = (e: React.MouseEvent<HTMLAnchorElement>) => {
77
+ // e.preventDefault();
78
+ // console.log("Star: ", item.id)
79
+ // }
80
+
81
+ return (
82
+ <div className={wy('conversation' + (item.is_unread ? ' unread' : ''))} key={item.id}>
83
+ <a className={wy('conversation-link' + (selectedConversationId === item.id ? ' active': ''))} href="#" onClick={(e) => handleClick(e, item.id)}>
84
+ <Avatar src={item.avatar_url} id={otherId || -1} presence={item.type !== ChatRoom ? "away" : ""} name={item.display_name} />
85
+
86
+ <div className={wy('conversation-body')}>
87
+ <div className={wy('conversation-header')}>
88
+ <div className={wy('conversation-title')}>{item.display_name}</div>
89
+ {item.last_message &&
90
+ <time className={wy('conversation-time')} dateTime={item.last_message.created_at.toString()} title={date.format('LLLL')}>{date.fromNow()}</time>
91
+ }
92
+ </div>
93
+ <div className={wy('conversation-summary')}>
94
+ <Typing id={item.id} context="listitem">
95
+
96
+ {item.last_message?.html &&
97
+ <span className={wy('typing-hide')} dangerouslySetInnerHTML={{ __html: joypixels.shortnameToUnicode(item.last_message?.text) }}></span>
98
+ }
99
+ {!item.last_message?.html &&
100
+ <span className={wy('typing-hide')}>
101
+ {item.last_message?.attachment_ids?.length > 0 &&
102
+ <Icon.UI name="attachment" size={1} />
103
+ }
104
+ {item.last_message?.meeting_id &&
105
+ <Icon.UI name="zoom" size={1} />
106
+ }
107
+ </span>
108
+ }
109
+
110
+ </Typing>
111
+ </div>
112
+ </div>
113
+ </a>
114
+
115
+ <div className={wy('conversation-actions')}>
116
+ {item.is_pinned &&
117
+ <Button.UI onClick={handleUnpin}>
118
+ <Icon.UI name="pin" size={.75} />
119
+ </Button.UI>
120
+
121
+ }
122
+
123
+
124
+ <Dropdown.UI directionX='left'>
125
+ <>
126
+ {item.is_unread &&
127
+ <Dropdown.Item onClick={handleRead}>Mark as read</Dropdown.Item>
128
+ }
129
+ {!item.is_unread &&
130
+ <Dropdown.Item onClick={handleUnread}>Mark as unread</Dropdown.Item>
131
+ }
132
+ </>
133
+ <>
134
+ {item.is_pinned &&
135
+ <Dropdown.Item onClick={handleUnpin}>Unpin</Dropdown.Item>
136
+ }
137
+ {!item.is_pinned &&
138
+ <Dropdown.Item onClick={handlePin}>Pin</Dropdown.Item>
139
+ }
140
+ </>
141
+ {item.type === ChatRoom &&
142
+ <Dropdown.Item onClick={handleLeaveConversation}>Leave conversation</Dropdown.Item>
143
+ }
144
+
145
+ {/* <li><Button.UI onClick={handleStar}>Star</Button.UI></li> */}
146
+ </Dropdown.UI>
147
+
148
+
149
+
150
+ </div>
151
+ </div>
152
+ )
153
+ }
154
+
155
+ export default ConversationListItem;
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import Icon from "../ui/Icon";
3
+
4
+ type Props = {
5
+ id: number,
6
+ name: string
7
+ className?: string,
8
+ icon: string
9
+ }
10
+ const File = ({ id, name, className, icon }: Props) => {
11
+
12
+ return (
13
+ <>
14
+ <Icon.UI name={icon} size={1} />
15
+ <div className={className}>{name}</div>
16
+ <input type="hidden" value={id} name="blob"/>
17
+ </>
18
+ )
19
+ }
20
+
21
+ export default File;
@@ -0,0 +1,86 @@
1
+ import React, { useContext, useEffect } from "react";
2
+ import { WeavyContext } from "../contexts/WeavyContext";
3
+ import Button from '../ui/Button';
4
+ import Icon from '../ui/Icon';
5
+ import useMutateExternalBlobs from '../hooks/useMutateExternalBlobs';
6
+ import { UserContext } from "../contexts/UserContext";
7
+ import { prefix as wy } from "../utils/styles";
8
+
9
+ type Props = {
10
+ onFileAdded: Function
11
+ }
12
+
13
+ const FileBrowser = ({ onFileAdded }: Props) => {
14
+
15
+ const { options } = useContext(WeavyContext);
16
+ const { user } = useContext(UserContext);
17
+
18
+ const addExternalBlobs = useMutateExternalBlobs();
19
+
20
+ useEffect(() => {
21
+ window.addEventListener("message", handleFilebrowserMessage);
22
+
23
+
24
+ return () => {
25
+ window.removeEventListener("message", handleFilebrowserMessage);
26
+ }
27
+ }, []);
28
+
29
+ const handleFilebrowserMessage = async (e: any) => {
30
+ const messageData = e.data;
31
+
32
+ switch (messageData.name) {
33
+ case "addExternalBlobs":
34
+ var result = await addExternalBlobs.mutateAsync({ blobs: messageData.blobs });
35
+ onFileAdded(result);
36
+ closeFilebrowser();
37
+ break;
38
+ case "file-browser-close":
39
+ closeFilebrowser();
40
+ break;
41
+ }
42
+ }
43
+
44
+ const closeFilebrowser = () => {
45
+ let fb = document.getElementById("weavy-filebrowser");
46
+ if (fb) {
47
+ fb.style.display = "none";
48
+ }
49
+ }
50
+
51
+ const openFilebrowser = () => {
52
+ let fb = document.getElementById("weavy-filebrowser");
53
+
54
+ if (!fb) {
55
+ const origin = window.top?.document.location.origin;
56
+ const fileBrowserUrl = options?.filebrowserUrl;
57
+ const filebrowserSrc = fileBrowserUrl + "?origin=" + origin + "&v=X&t=" + Date.now().toString() + "&weavyId=-1";
58
+
59
+ let $filebrowserFrame = document.createElement("iframe");
60
+ $filebrowserFrame.id = "weavy-filebrowser";
61
+ $filebrowserFrame.name = "weavy-filebrowser";
62
+ $filebrowserFrame.src = filebrowserSrc;
63
+ $filebrowserFrame.className = wy('filebrowser-frame');
64
+ $filebrowserFrame.style.cssText = "position: absolute; top: 0; left: 0; height: 100%; width: 100%; background: rgba(1,1,1,.4); z-index: 10000; display:none"
65
+
66
+ window.top?.document.body.appendChild($filebrowserFrame);
67
+
68
+ $filebrowserFrame.addEventListener('load', () => {
69
+ $filebrowserFrame.style.display = "block";
70
+ });
71
+
72
+ } else {
73
+ fb.style.display = "block";
74
+ }
75
+ }
76
+
77
+ return (
78
+ <>
79
+ {options?.enableCloudFiles &&
80
+ <Button.UI onClick={openFilebrowser} title="Add file from cloud"><Icon.UI name="cloud" /></Button.UI>
81
+ }
82
+ </>
83
+ )
84
+ }
85
+
86
+ export default FileBrowser;
@@ -0,0 +1,66 @@
1
+ import React, { useState } from "react";
2
+ import { prefix as wy } from "../utils/styles";
3
+
4
+ type ImageProps = {
5
+ src: string,
6
+ previewSrc: string,
7
+ width: number
8
+ height: number,
9
+ more?: number,
10
+ onClick: (e: any) => void
11
+ }
12
+
13
+ export const Image = ({src, previewSrc, width, height, more, onClick}: ImageProps) => {
14
+ let ratio = width / height;
15
+ let baseSize = 64;
16
+ let maxScale = 2;
17
+ let flexRatio = ratio.toPrecision(5);
18
+ let flexBasis = (ratio * baseSize).toPrecision(5) + "px";
19
+ let padding = (100 / ratio).toPrecision(5) + "%"
20
+ let intrinsicWidth = width + "px"
21
+ let maxWidth = (maxScale * width) + "px";
22
+
23
+ return (
24
+ <a href={src} className={wy('image')} onClick={(e) => onClick(e)} style={{
25
+ flexBasis: flexBasis,
26
+ flexGrow: flexRatio,
27
+ flexShrink: flexRatio,
28
+ width: intrinsicWidth,
29
+ maxWidth: maxWidth
30
+ }}>
31
+ <div className={wy('image-area')} style={{ paddingBottom: padding}}>
32
+ <img src={previewSrc} alt="" loading="lazy" decoding="async" />
33
+ {!!more && <span className={wy('more')}>+{more}</span>}
34
+ </div>
35
+ </a>
36
+ );
37
+ }
38
+
39
+ type ImageGridProps = {
40
+ children: React.ReactNode,
41
+ limit?: number
42
+ }
43
+
44
+ export const ImageGrid = ({ children, limit = 3}: ImageGridProps) => {
45
+
46
+ let allImages = React.Children.toArray(children);
47
+
48
+ let more = allImages.length > limit ? allImages.length - limit : 0;
49
+
50
+ let firstImages = allImages.slice(0, limit);
51
+
52
+ let lastChild = firstImages[firstImages.length - 1];
53
+
54
+ if (React.isValidElement(lastChild)) {
55
+ // Set more property on last image
56
+ firstImages[firstImages.length - 1] = React.cloneElement(lastChild, { more });
57
+ }
58
+
59
+ return (
60
+ <div className={wy('image-grid')}>
61
+ {firstImages}
62
+ </div>
63
+ );
64
+ }
65
+
66
+ export default Image;
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import Icon from '../ui/Icon';
3
+
4
+ type Props = {
5
+ id: number,
6
+ title: string
7
+ className?: string
8
+ }
9
+ const Meeting = ({ id, title, className }: Props) => {
10
+
11
+ return (
12
+ <>
13
+ {/* <div className={className}>{title}</div> */}
14
+ <Icon.UI name="zoom" />
15
+ <div className={className}>Zoom meeting</div>
16
+ <input type="hidden" value={id} name="meeting"/>
17
+ </>
18
+ )
19
+ }
20
+
21
+ export default Meeting;