@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,90 @@
1
+ import React, { useEffect, useState } from "react"
2
+ import useDebounce from "../hooks/useDebounce";
3
+ import useSearchUsers from "../hooks/useSearchUsers";
4
+ import Avatar from './Avatar';
5
+ import Button from '../ui/Button';
6
+ import Icon from '../ui/Icon';
7
+ import { prefix as wy } from "../utils/styles";
8
+
9
+ type SearchUsersProps = {
10
+ handleSubmit: any,
11
+ buttonTitle: string
12
+ }
13
+
14
+ const SearchUsers = ({handleSubmit, buttonTitle}: SearchUsersProps) => {
15
+
16
+ const [text, setText] = useState("");
17
+ const [selected, setSelected] = useState<MemberType[]>([]);
18
+
19
+ const { isLoading, isError, data, error, isFetching, refetch } = useSearchUsers(text, {
20
+ enabled: false
21
+ });
22
+
23
+ const throttledCb = useDebounce(() => refetch(), 250);
24
+ useEffect(throttledCb, [text])
25
+
26
+ const isChecked = (id: number): boolean => {
27
+ return selected.find((u) => { return u.id === id }) != null;
28
+ }
29
+
30
+ const handleSelected = (e: any, member: MemberType) => {
31
+ if (e.target.checked) {
32
+ setSelected([...selected, member]);
33
+ } else {
34
+ setSelected(selected.filter((u) => { return u.id !== member.id }));
35
+ }
36
+
37
+ }
38
+
39
+ const clear = () => {
40
+ setSelected([]);
41
+ setText("");
42
+ }
43
+
44
+ return (
45
+ <div className={wy('search scroll-y')}>
46
+ <div className={wy('search-form pane-group')}>
47
+ <Button.UI><Icon.UI name="magnify" /></Button.UI>
48
+ <input className={wy('search-input')} value={text} onChange={(e) => setText(e.target.value)} name="text" placeholder='Search...' />
49
+ </div>
50
+
51
+ <div className={wy('pane-group')}>
52
+ {data && data.data.length === 0 &&
53
+ <div className={wy('search-no-result')}>Your search did not match any people.</div>
54
+ }
55
+ <table className={wy('search-result-table')}>
56
+ <tbody>
57
+ {data && data.data.length > 0 && data.data.map((user: MemberType) => {
58
+ return (
59
+ <tr key={user.id} className={wy('search-result-table-checkbox')}>
60
+ <td className={wy('search-result-table-icon')}>
61
+ <Avatar src={user.avatar_url} size={24} id={user.id} presence={user.presence} name={user.display_name} />
62
+ </td>
63
+ <td>{user.display_name}</td>
64
+ <td className={wy('search-result-table-icon')}><input type="checkbox" checked={isChecked(user.id)} onChange={(e) => handleSelected(e, user)} /></td>
65
+ </tr>
66
+ )
67
+ })}
68
+ </tbody>
69
+ </table>
70
+ </div>
71
+ {/*<div className={wy('search-group')}>
72
+ <h2>Selected people</h2>
73
+ <ul>
74
+ {selected && selected.length > 0 && selected.map((user: UserType) => {
75
+ return <li key={user.id}> {user.title} {user.email && ` - ${user.email}`}</li>
76
+ })}
77
+ </ul>
78
+ </div>*/}
79
+ <div className={wy('footerbars')}>
80
+ <div className={wy('footerbar')}>
81
+ <div className={wy('pane-group')}>
82
+ <button className={wy('button-primary')} type="button" onClick={() => {handleSubmit(selected); clear();}} disabled={selected.length === 0}>{buttonTitle}</button>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ )
88
+ }
89
+
90
+ export default SearchUsers;
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import Avatar from "./Avatar";
3
+ import dayjs from 'dayjs';
4
+ import { prefix as wy } from "../utils/styles";
5
+
6
+ type Props = {
7
+ id: number,
8
+ parentId: number | null,
9
+ seenBy: MemberType[],
10
+ createdAt: string
11
+ }
12
+
13
+ const SeenBy = ({ seenBy }: Props) => {
14
+
15
+ return (
16
+ <div className={wy('readby-status')}>
17
+ {seenBy && seenBy.length > 0 && seenBy.map((member: MemberType) => {
18
+ const date = dayjs.utc(member.read_at).tz(dayjs.tz.guess());
19
+ return (<Avatar name={`Seen by ${member.name} at ${date.format('LLLL')}`} src={member.avatar_url} size={16} key={member.id}/>)})
20
+ }
21
+ </div>
22
+ )
23
+ }
24
+
25
+ export default SeenBy;
26
+
@@ -0,0 +1,131 @@
1
+ import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
2
+ import { UserContext } from '../contexts/UserContext';
3
+ import { WeavyContext } from '../contexts/WeavyContext';
4
+
5
+ type Props = {
6
+ id: number,
7
+ context: string // use context to create handlers to make them unique. This makes the useRealTime hook NOT to unsubscribe to the eventhandlers.
8
+ children: any
9
+ }
10
+
11
+ const Typing = ({ children, id, context }: Props) => {
12
+ const [activeTypers, setActiveTypers] = useState<any>([]);
13
+ const [text, setText] = useState<string>("");
14
+ const typingTimeout = useRef<any>(null);
15
+ const { client } = useContext(WeavyContext);
16
+ const { user } = useContext(UserContext);
17
+
18
+ useEffect(() => {
19
+ updateTyping();
20
+
21
+ return () => {
22
+ clearTimeout(typingTimeout.current);
23
+ };
24
+ }, [activeTypers, id, context]);
25
+
26
+ useEffect(() => {
27
+ setActiveTypers([]);
28
+
29
+ client.subscribe(`a${id}`, "typing", handleTyping);
30
+ client.subscribe(`a${id}`, "message-inserted", handleStopTyping);
31
+
32
+ return () => {
33
+ client.unsubscribe(`a${id}`, "typing", handleTyping);
34
+ client.unsubscribe(`a${id}`, "message-inserted", handleStopTyping);
35
+ }
36
+ }, [id])
37
+
38
+ const updateTyping = () => {
39
+
40
+ let typers = activeTypers;
41
+ if (typingTimeout.current) {
42
+ clearTimeout(typingTimeout.current);
43
+ typingTimeout.current = null;
44
+ }
45
+
46
+ // discard typing events older than 5 seconds
47
+ let now = Date.now();
48
+ typers.forEach(function (item: any, index: number) {
49
+ if (now - item.time > 5 * 1000) {
50
+ typers.splice(index, 1);
51
+ }
52
+ });
53
+
54
+ if (typers.length) {
55
+ // use age of typing event to animate ellipsis...
56
+ let dots = (Math.round((now - Math.max.apply(null, typers.map(function (x: any) { return x.time; }))) / 1000) % 3) + 1;
57
+
58
+ let ellipsis = (".").repeat(dots); //+ (".").repeat(3 - dots);
59
+
60
+ // merge names of people typing
61
+ let names = typers.map((item: any) => item.display_name).sort();
62
+
63
+ let typingText = "";
64
+ for (let i = 0; i < names.length; i++) {
65
+ if (i > 0) {
66
+ if (i === (names.length - 1)) {
67
+ typingText += " " + "and" + " ";
68
+ } else {
69
+ typingText += ", ";
70
+ }
71
+ }
72
+ typingText += names[i];
73
+ }
74
+ if (names.length === 1) {
75
+ typingText += " " + "is typing";
76
+ } else {
77
+ typingText += " " + "are typing";
78
+ }
79
+
80
+ // update gui
81
+ setText(typingText + ellipsis);
82
+
83
+ // schedule another call to updateTyping in 1 second
84
+ typingTimeout.current = setTimeout(updateTyping, 1000);
85
+ } else {
86
+ setText("");
87
+ }
88
+ }
89
+
90
+ const setTypers = (data: any) => {
91
+
92
+ // remove existing typing events by this user (can only type in one conversation at a time)
93
+ activeTypers.forEach(function (item: any, index: number) {
94
+ if (item.member.id === data.member.id) {
95
+ setActiveTypers(activeTypers.splice(index, 1));
96
+ }
97
+ });
98
+
99
+ // track time when we received this event
100
+ data.member.time = Date.now();
101
+ setActiveTypers([...activeTypers, data.member]);
102
+ }
103
+
104
+ const handleTyping = useCallback((data: any) => {
105
+ if (data.conversation.id === id && data.member.id !== user.id) {
106
+ setTypers(data);
107
+ }
108
+
109
+ }, [id, context, activeTypers]);
110
+
111
+ const handleStopTyping = useCallback((data: any) => {
112
+ if (data.app_id === id) {
113
+ setActiveTypers([]);
114
+ }
115
+ }, [id, context, activeTypers]);
116
+
117
+ return (
118
+ <>
119
+ {text !== "" &&
120
+ <>{text}</>
121
+ }
122
+ {text === "" &&
123
+ <>{children}</>
124
+ }
125
+ </>
126
+
127
+
128
+ )
129
+ }
130
+
131
+ export default React.memo(Typing);
@@ -0,0 +1,44 @@
1
+ import React, { createContext, useState } from "react";
2
+ import usePresence from "../hooks/usePresence";
3
+
4
+ export const MessengerContext = createContext<MessengerContextProps>({
5
+ selectedConversationId: null,
6
+ setSelectedConversationId: (id: any) => { },
7
+ options: {}
8
+ });
9
+
10
+ type Props = {
11
+ children: React.ReactNode,
12
+ options?: MessengerContextOptions
13
+ }
14
+
15
+ const MessengerProvider = ({ children, options }: Props) => {
16
+
17
+ let defaultOptions: MessengerContextOptions = {
18
+ reactions: ['😍', '😎', '😉', '😜', '👍']
19
+ };
20
+
21
+ let opts = { ...defaultOptions, ...options };
22
+
23
+ const [selectedConversationId, setSelectedConversation] = useState(null);
24
+
25
+ // hook up to presence updates
26
+ usePresence();
27
+
28
+ const setSelectedConversationId = (id: any) => {
29
+ setSelectedConversation(id);
30
+ }
31
+
32
+ return (
33
+ <>
34
+ <MessengerContext.Provider value={{ options: opts, selectedConversationId, setSelectedConversationId }}>
35
+ {children}
36
+ </MessengerContext.Provider>
37
+ </>
38
+
39
+
40
+ )
41
+ };
42
+
43
+ export default MessengerProvider;
44
+
@@ -0,0 +1,105 @@
1
+ import React, { createContext, useEffect, useState } from "react";
2
+ import Overlay from '../ui/Overlay';
3
+ import Button from '../ui/Button';
4
+ import Icon from '../ui/Icon';
5
+ import { prefix as wy } from "../utils/styles";
6
+
7
+ export const PreviewContext = createContext<PreviewContextProps>({
8
+ openPreview: Function,
9
+ closePreview: Function
10
+ });
11
+
12
+ type Props = {
13
+ children: React.ReactNode
14
+ }
15
+
16
+ const PreviewProvider = ({ children }: Props) => {
17
+ const [modalPreviewOpen, setModalPreviewOpen] = useState<boolean>(false);
18
+ const [attachments, setAttachments] = useState<AttachmentType[]>([]);
19
+ const [activeAttachment, setActiveAttchment] = useState<AttachmentType>();
20
+
21
+ const [currentAttachmentId, setCurrentAttachmentId] = useState<number | null>();
22
+ const [nextAttachmentId, setNextAttachmentId] = useState<number | null>();
23
+ const [previousAttachmentId, setPreviousAttachmentId] = useState<number | null>();
24
+
25
+ useEffect(() => {
26
+
27
+ if (currentAttachmentId) {
28
+ var itemIndex = attachments.map(function (a: AttachmentType) { return a.id; }).indexOf(currentAttachmentId);
29
+
30
+ setPreviousAttachmentId(itemIndex > 0 ? attachments[itemIndex - 1].id : null);
31
+ setNextAttachmentId(itemIndex + 1 < attachments.length ? attachments[itemIndex + 1].id : null);
32
+ setActiveAttchment(attachments.find((a: AttachmentType) => a.id === currentAttachmentId));
33
+ }
34
+
35
+ }, [attachments, currentAttachmentId])
36
+
37
+ const openPreview = (attachments: AttachmentType[], id: number) => {
38
+ setAttachments(attachments);
39
+ setCurrentAttachmentId(id);
40
+
41
+ setModalPreviewOpen(true);
42
+ }
43
+
44
+ const closePreview = () => {
45
+ setModalPreviewOpen(false);
46
+ }
47
+
48
+ const handlePrevious = () => {
49
+ setCurrentAttachmentId(previousAttachmentId);
50
+ }
51
+
52
+ const handleNext = () => {
53
+ setCurrentAttachmentId(nextAttachmentId);
54
+ }
55
+
56
+ const handleDownload = () => {
57
+ window.open(`${activeAttachment?.download_url}&d=1`, "_blank");
58
+ }
59
+
60
+ return (
61
+ <>
62
+
63
+ <PreviewContext.Provider value={{ openPreview: openPreview, closePreview: closePreview }}>
64
+ {children}
65
+ </PreviewContext.Provider>
66
+
67
+ <Overlay.UI isOpen={modalPreviewOpen} className={wy('dark')}>
68
+ <header className={wy('appbars')}>
69
+ <nav className={wy('appbar')}>
70
+ <Button.UI onClick={closePreview}><Icon.UI name='close' /></Button.UI>
71
+ <div className={wy('appbar-text')}>
72
+ {activeAttachment &&
73
+ <span>{activeAttachment.name}</span>
74
+ }
75
+
76
+ </div>
77
+ <Button.UI onClick={handleDownload}><Icon.UI name='download' /></Button.UI>
78
+ </nav>
79
+ </header>
80
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }}>
81
+
82
+ {activeAttachment &&
83
+ <>
84
+ {previousAttachmentId &&
85
+ <Button.UI onClick={handlePrevious} style={{ position: "absolute", top: "50%", left: "0" }}><Icon.UI name="previous" /></Button.UI>
86
+ }
87
+ {nextAttachmentId &&
88
+ <Button.UI onClick={handleNext} style={{ position: "absolute", top: "50%", right: "0", }}><Icon.UI name="next" /></Button.UI>
89
+ }
90
+
91
+ <img style={{ maxWidth: "100%" }} src={activeAttachment.preview_url} />
92
+ </>
93
+ }
94
+
95
+ </div>
96
+ </Overlay.UI>
97
+
98
+ </>
99
+
100
+
101
+ )
102
+ };
103
+
104
+ export default PreviewProvider;
105
+
@@ -0,0 +1,31 @@
1
+ import React, { createContext} from "react";
2
+ import useUser from "../hooks/useUser";
3
+
4
+ export const UserContext = createContext<UserContextProps>({
5
+ user: { id: -1, username: "anonymous", name: "Anonymous", email: "", title: "", presence: "", avatar_url: "" }
6
+ });
7
+
8
+ type Props = {
9
+ client: any, // pass client here to avoid circular references from WeavyContext
10
+ children: React.ReactNode
11
+ }
12
+
13
+ const UserProvider = ({ children, client }: Props) => {
14
+
15
+ const { isLoading, data } = useUser(client);
16
+
17
+ return (
18
+ <>
19
+ {!isLoading && data &&
20
+ <UserContext.Provider value={{ user: data }}>
21
+ {children}
22
+ </UserContext.Provider>
23
+ }
24
+ </>
25
+
26
+
27
+ )
28
+ };
29
+
30
+ export default UserProvider;
31
+
@@ -0,0 +1,66 @@
1
+ import React, { createContext } from "react";
2
+ import { QueryClient, QueryClientProvider } from "react-query";
3
+ import UserProvider from "./UserContext";
4
+ import dayjs from 'dayjs';
5
+ import relativeTime from 'dayjs/plugin/relativeTime';
6
+ import utc from 'dayjs/plugin/utc';
7
+ import timezone from 'dayjs/plugin/timezone';
8
+ import localizedFormat from 'dayjs/plugin/localizedFormat';
9
+ import PreviewProvider from "./PreviewContext";
10
+ import WeavyClient from "../client/WeavyClient";
11
+
12
+ dayjs.extend(relativeTime);
13
+ dayjs.extend(utc);
14
+ dayjs.extend(timezone);
15
+ dayjs.extend(localizedFormat);
16
+
17
+
18
+ export const WeavyContext = createContext<WeavyContextProps>({
19
+ client: null,
20
+ options: {}
21
+ });
22
+
23
+ type Props = {
24
+ children: React.ReactNode,
25
+ client: WeavyClient,
26
+ options?: WeavyContextOptions
27
+ }
28
+
29
+ const queryClient = new QueryClient({
30
+ defaultOptions: {
31
+ queries: {
32
+ cacheTime: 1000 * 60 * 60 * 24, // 24 hours
33
+ },
34
+ },
35
+ })
36
+
37
+ const WeavyProvider = ({ children, client, options }: Props) => {
38
+
39
+ let defaultOptions: WeavyContextOptions = {
40
+ zoomAuthenticationUrl: undefined,
41
+ teamsAuthenticationUrl: undefined,
42
+ enableCloudFiles: true,
43
+ filebrowserUrl: "https://filebrowser.weavycloud.com/index10.html"
44
+ };
45
+
46
+ let opts = { ...defaultOptions, ...options }
47
+
48
+ return (
49
+ <>
50
+ {client &&
51
+ <QueryClientProvider client={queryClient}>
52
+ <WeavyContext.Provider value={{ client, options: opts }}>
53
+ <UserProvider client={client}>
54
+ <PreviewProvider>
55
+ {children}
56
+ </PreviewProvider>
57
+ </UserProvider>
58
+ </WeavyContext.Provider>
59
+ </QueryClientProvider>
60
+ }
61
+ </>
62
+ )
63
+ };
64
+
65
+ export default WeavyProvider;
66
+
@@ -0,0 +1,32 @@
1
+ import { useContext } from "react";
2
+ import { useQuery } from "react-query";
3
+ import { WeavyContext } from "../contexts/WeavyContext";
4
+
5
+ /// GET the conversation badge
6
+ export default function useBadge() {
7
+ const { client } = useContext(WeavyContext);
8
+
9
+ if (!client) {
10
+ throw new Error('useBadge must be used within an WeavyProvider');
11
+ }
12
+
13
+ const getBadge = async () => {
14
+
15
+ const response = await fetch(client.uri + "/api/conversations/badge", {
16
+ headers: {
17
+ "content-type": "application/json",
18
+ "Authorization": "Bearer " + await client.tokenFactory()
19
+ }
20
+ });
21
+ if(response.ok){
22
+ const data = await response.json();
23
+ return data;
24
+ }
25
+
26
+ return null;
27
+
28
+ };
29
+
30
+
31
+ return useQuery<BadgeType>("badge", getBadge);
32
+ }
@@ -0,0 +1,32 @@
1
+ import { useContext } from "react";
2
+ import { useQuery } from "react-query";
3
+ import { WeavyContext } from "../contexts/WeavyContext";
4
+
5
+ /// GET a specific conversation
6
+ export default function useChat(id: string, options: any) {
7
+ const { client } = useContext(WeavyContext);
8
+
9
+ if (!client) {
10
+ throw new Error('useChat must be used within an WeavyProvider');
11
+ }
12
+
13
+ const getConversation = async () => {
14
+
15
+ const response = await fetch(client.uri + "/api/apps/idf/" + id, {
16
+ headers: {
17
+ "content-type": "application/json",
18
+ "Authorization": "Bearer " + await client.tokenFactory()
19
+ }
20
+ });
21
+ if(response.ok){
22
+ const data = await response.json();
23
+ return data;
24
+ }
25
+
26
+ return null;
27
+
28
+ };
29
+
30
+
31
+ return useQuery<ConversationType>(["chat", id], getConversation, options);
32
+ }
@@ -0,0 +1,28 @@
1
+ import { useContext } from "react";
2
+
3
+ import { useQuery } from "react-query";
4
+ import { WeavyContext } from "../contexts/WeavyContext";
5
+
6
+ /// GET a specific conversation
7
+ export default function useConversation(id: number | null, options: any) {
8
+ const { client } = useContext(WeavyContext);
9
+
10
+ if (!client) {
11
+ throw new Error('useConversation must be used within an WeavyProvider');
12
+ }
13
+
14
+ const getConversation = async () => {
15
+
16
+ const response = await fetch(client.uri + "/api/conversations/" + id, {
17
+ headers: {
18
+ "content-type": "application/json",
19
+ "Authorization": "Bearer " + await client.tokenFactory()
20
+ }
21
+ });
22
+ const data = await response.json();
23
+ return data;
24
+ };
25
+
26
+
27
+ return useQuery<ConversationType>(["conversation", id], getConversation, options);
28
+ }
@@ -0,0 +1,27 @@
1
+ import { useContext } from "react";
2
+ import { useQuery } from "react-query";
3
+ import { WeavyContext } from "../contexts/WeavyContext";
4
+
5
+ /// GET all conversations
6
+ export default function useConversations() {
7
+ const { client } = useContext(WeavyContext);
8
+
9
+ if (!client) {
10
+ throw new Error('useConversations must be used within an WeavyProvider');
11
+ }
12
+
13
+ const getConversations = async () => {
14
+
15
+ const response = await fetch(client.uri + "/api/conversations?contextual=false", {
16
+ headers: {
17
+ "content-type": "application/json",
18
+ "Authorization": "Bearer " + await client.tokenFactory()
19
+ }
20
+ });
21
+ const data = await response.json();
22
+ return data;
23
+ };
24
+
25
+
26
+ return useQuery<ConversationsResult>("conversations", getConversations);
27
+ }
@@ -0,0 +1,22 @@
1
+ import debounce from "lodash.debounce";
2
+ import { useCallback, useEffect, useRef } from "react";
3
+
4
+ export default function useDebounce(cb: any, delay: number) {
5
+
6
+ const inputsRef = useRef({cb, delay}); // mutable ref like with useThrottle
7
+ useEffect(() => { inputsRef.current = { cb, delay }; }); //also track cur. delay
8
+ return useCallback(
9
+ debounce((...args) => {
10
+ // Debounce is an async callback. Cancel it, if in the meanwhile
11
+ // (1) component has been unmounted (see isMounted in snippet)
12
+ // (2) delay has changed
13
+ if (inputsRef.current.delay === delay)
14
+ inputsRef.current.cb(...args);
15
+ }, delay, {
16
+ leading: false,
17
+ trailing: true
18
+ }
19
+ ),
20
+ [delay, debounce]
21
+ );
22
+ }
@@ -0,0 +1,43 @@
1
+ import { useCallback, useState } from "react";
2
+
3
+ const events: object[] = [];
4
+ const callbacks: any = {};
5
+
6
+ function useForceUpdate() {
7
+ const [, setState] = useState({});
8
+ return useCallback(() => setState({}), []);
9
+ }
10
+
11
+ export default function useEvents() {
12
+
13
+ const forceUpdate = useForceUpdate();
14
+ const runCallbacks = (callbackList: Function[], data: any) => {
15
+ if (callbackList) {
16
+ callbackList.forEach(cb => cb(data));
17
+ forceUpdate();
18
+ }
19
+
20
+ }
21
+
22
+ const dispatch = (event: string, data: any) => {
23
+ events.push({ event, data, created: Date.now() });
24
+ runCallbacks(callbacks[event], data);
25
+ }
26
+
27
+ const on = (event: string, cb: Function) => {
28
+ if (callbacks[event]) {
29
+ callbacks[event].push(cb);
30
+ } else {
31
+ callbacks[event] = [cb];
32
+ }
33
+
34
+ // Return a cleanup function to unbind event
35
+ return () => callbacks[event] = callbacks[event].filter((i: any) => i !== cb);
36
+ }
37
+
38
+ const off = (event: string, cb: Function) => {
39
+ callbacks[event] = callbacks[event].filter((i: any) => i !== cb);
40
+ }
41
+
42
+ return { dispatch, on, off, events };
43
+ }