@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.
- package/.github/workflows/publish.yml +16 -0
- package/LICENSE.md +21 -0
- package/README.md +110 -0
- package/changelog.md +50 -0
- package/dist/cjs/index.js +39 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/types/client/WeavyClient.d.ts +16 -0
- package/dist/cjs/types/components/Attachment.d.ts +13 -0
- package/dist/cjs/types/components/Avatar.d.ts +11 -0
- package/dist/cjs/types/components/Chat.d.ts +4 -0
- package/dist/cjs/types/components/Conversation.d.ts +4 -0
- package/dist/cjs/types/components/ConversationBadge.d.ts +3 -0
- package/dist/cjs/types/components/ConversationForm.d.ts +7 -0
- package/dist/cjs/types/components/ConversationList.d.ts +3 -0
- package/dist/cjs/types/components/ConversationListItem.d.ts +4 -0
- package/dist/cjs/types/components/File.d.ts +9 -0
- package/dist/cjs/types/components/FileBrowser.d.ts +6 -0
- package/dist/cjs/types/components/Image.d.ts +16 -0
- package/dist/cjs/types/components/Meeting.d.ts +8 -0
- package/dist/cjs/types/components/MeetingCard.d.ts +6 -0
- package/dist/cjs/types/components/Meetings.d.ts +6 -0
- package/dist/cjs/types/components/Message.d.ts +4 -0
- package/dist/cjs/types/components/Messages.d.ts +9 -0
- package/dist/cjs/types/components/Messenger.d.ts +4 -0
- package/dist/cjs/types/components/NewConversation.d.ts +3 -0
- package/dist/cjs/types/components/Presence.d.ts +7 -0
- package/dist/cjs/types/components/Reactions.d.ts +13 -0
- package/dist/cjs/types/components/SearchUsers.d.ts +7 -0
- package/dist/cjs/types/components/SeenBy.d.ts +9 -0
- package/dist/cjs/types/components/Typing.d.ts +8 -0
- package/dist/cjs/types/contexts/MessengerContext.d.ts +8 -0
- package/dist/cjs/types/contexts/PreviewContext.d.ts +7 -0
- package/dist/cjs/types/contexts/UserContext.d.ts +8 -0
- package/dist/cjs/types/contexts/WeavyContext.d.ts +10 -0
- package/dist/cjs/types/hooks/useBadge.d.ts +1 -0
- package/dist/cjs/types/hooks/useChat.d.ts +1 -0
- package/dist/cjs/types/hooks/useConversation.d.ts +1 -0
- package/dist/cjs/types/hooks/useConversations.d.ts +1 -0
- package/dist/cjs/types/hooks/useDebounce.d.ts +2 -0
- package/dist/cjs/types/hooks/useEvents.d.ts +6 -0
- package/dist/cjs/types/hooks/useFileUploader.d.ts +1 -0
- package/dist/cjs/types/hooks/useMembers.d.ts +1 -0
- package/dist/cjs/types/hooks/useMessages.d.ts +1 -0
- package/dist/cjs/types/hooks/useMutateChat.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateConversation.d.ts +3 -0
- package/dist/cjs/types/hooks/useMutateConversationName.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateDeleteReaction.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateExternalBlobs.d.ts +3 -0
- package/dist/cjs/types/hooks/useMutateMeeting.d.ts +3 -0
- package/dist/cjs/types/hooks/useMutateMembers.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateMessage.d.ts +9 -0
- package/dist/cjs/types/hooks/useMutatePinned.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateReaction.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateRead.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateRemoveMembers.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateTyping.d.ts +3 -0
- package/dist/cjs/types/hooks/usePresence.d.ts +1 -0
- package/dist/cjs/types/hooks/usePreview.d.ts +4 -0
- package/dist/cjs/types/hooks/useReactions.d.ts +3 -0
- package/dist/cjs/types/hooks/useSearchUsers.d.ts +1 -0
- package/dist/cjs/types/hooks/useThrottle.d.ts +2 -0
- package/dist/cjs/types/hooks/useUser.d.ts +1 -0
- package/dist/cjs/types/index.d.ts +15 -0
- package/dist/cjs/types/types/Chat.d.ts +3 -0
- package/dist/cjs/types/types/Conversation.d.ts +4 -0
- package/dist/cjs/types/types/ConversationListItem.d.ts +4 -0
- package/dist/cjs/types/types/Message.d.ts +15 -0
- package/dist/cjs/types/types/Messenger.d.ts +3 -0
- package/dist/cjs/types/types/types.d.ts +150 -0
- package/dist/cjs/types/ui/Button.d.ts +4 -0
- package/dist/cjs/types/ui/Dropdown.d.ts +19 -0
- package/dist/cjs/types/ui/Icon.d.ts +10 -0
- package/dist/cjs/types/ui/Overlay.d.ts +12 -0
- package/dist/cjs/types/utils/fileUtilities.d.ts +5 -0
- package/dist/cjs/types/utils/styles.d.ts +17 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/types/client/WeavyClient.d.ts +16 -0
- package/dist/esm/types/components/Attachment.d.ts +13 -0
- package/dist/esm/types/components/Avatar.d.ts +11 -0
- package/dist/esm/types/components/Chat.d.ts +4 -0
- package/dist/esm/types/components/Conversation.d.ts +4 -0
- package/dist/esm/types/components/ConversationBadge.d.ts +3 -0
- package/dist/esm/types/components/ConversationForm.d.ts +7 -0
- package/dist/esm/types/components/ConversationList.d.ts +3 -0
- package/dist/esm/types/components/ConversationListItem.d.ts +4 -0
- package/dist/esm/types/components/File.d.ts +9 -0
- package/dist/esm/types/components/FileBrowser.d.ts +6 -0
- package/dist/esm/types/components/Image.d.ts +16 -0
- package/dist/esm/types/components/Meeting.d.ts +8 -0
- package/dist/esm/types/components/MeetingCard.d.ts +6 -0
- package/dist/esm/types/components/Meetings.d.ts +6 -0
- package/dist/esm/types/components/Message.d.ts +4 -0
- package/dist/esm/types/components/Messages.d.ts +9 -0
- package/dist/esm/types/components/Messenger.d.ts +4 -0
- package/dist/esm/types/components/NewConversation.d.ts +3 -0
- package/dist/esm/types/components/Presence.d.ts +7 -0
- package/dist/esm/types/components/Reactions.d.ts +13 -0
- package/dist/esm/types/components/SearchUsers.d.ts +7 -0
- package/dist/esm/types/components/SeenBy.d.ts +9 -0
- package/dist/esm/types/components/Typing.d.ts +8 -0
- package/dist/esm/types/contexts/MessengerContext.d.ts +8 -0
- package/dist/esm/types/contexts/PreviewContext.d.ts +7 -0
- package/dist/esm/types/contexts/UserContext.d.ts +8 -0
- package/dist/esm/types/contexts/WeavyContext.d.ts +10 -0
- package/dist/esm/types/hooks/useBadge.d.ts +1 -0
- package/dist/esm/types/hooks/useChat.d.ts +1 -0
- package/dist/esm/types/hooks/useConversation.d.ts +1 -0
- package/dist/esm/types/hooks/useConversations.d.ts +1 -0
- package/dist/esm/types/hooks/useDebounce.d.ts +2 -0
- package/dist/esm/types/hooks/useEvents.d.ts +6 -0
- package/dist/esm/types/hooks/useFileUploader.d.ts +1 -0
- package/dist/esm/types/hooks/useMembers.d.ts +1 -0
- package/dist/esm/types/hooks/useMessages.d.ts +1 -0
- package/dist/esm/types/hooks/useMutateChat.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateConversation.d.ts +3 -0
- package/dist/esm/types/hooks/useMutateConversationName.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateDeleteReaction.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateExternalBlobs.d.ts +3 -0
- package/dist/esm/types/hooks/useMutateMeeting.d.ts +3 -0
- package/dist/esm/types/hooks/useMutateMembers.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateMessage.d.ts +9 -0
- package/dist/esm/types/hooks/useMutatePinned.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateReaction.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateRead.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateRemoveMembers.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateTyping.d.ts +3 -0
- package/dist/esm/types/hooks/usePresence.d.ts +1 -0
- package/dist/esm/types/hooks/usePreview.d.ts +4 -0
- package/dist/esm/types/hooks/useReactions.d.ts +3 -0
- package/dist/esm/types/hooks/useSearchUsers.d.ts +1 -0
- package/dist/esm/types/hooks/useThrottle.d.ts +2 -0
- package/dist/esm/types/hooks/useUser.d.ts +1 -0
- package/dist/esm/types/index.d.ts +15 -0
- package/dist/esm/types/types/Chat.d.ts +3 -0
- package/dist/esm/types/types/Conversation.d.ts +4 -0
- package/dist/esm/types/types/ConversationListItem.d.ts +4 -0
- package/dist/esm/types/types/Message.d.ts +15 -0
- package/dist/esm/types/types/Messenger.d.ts +3 -0
- package/dist/esm/types/types/types.d.ts +150 -0
- package/dist/esm/types/ui/Button.d.ts +4 -0
- package/dist/esm/types/ui/Dropdown.d.ts +19 -0
- package/dist/esm/types/ui/Icon.d.ts +10 -0
- package/dist/esm/types/ui/Overlay.d.ts +12 -0
- package/dist/esm/types/utils/fileUtilities.d.ts +5 -0
- package/dist/esm/types/utils/styles.d.ts +17 -0
- package/dist/index.d.ts +98 -0
- package/package.json +47 -0
- package/rollup.config.js +41 -0
- package/src/client/WeavyClient.ts +95 -0
- package/src/components/Attachment.tsx +33 -0
- package/src/components/Avatar.tsx +26 -0
- package/src/components/Chat.tsx +68 -0
- package/src/components/Conversation.tsx +220 -0
- package/src/components/ConversationBadge.tsx +44 -0
- package/src/components/ConversationForm.tsx +217 -0
- package/src/components/ConversationList.tsx +61 -0
- package/src/components/ConversationListItem.tsx +155 -0
- package/src/components/File.tsx +21 -0
- package/src/components/FileBrowser.tsx +86 -0
- package/src/components/Image.tsx +66 -0
- package/src/components/Meeting.tsx +21 -0
- package/src/components/MeetingCard.tsx +31 -0
- package/src/components/Meetings.tsx +58 -0
- package/src/components/Message.tsx +90 -0
- package/src/components/Messages.tsx +271 -0
- package/src/components/Messenger.tsx +34 -0
- package/src/components/NewConversation.tsx +50 -0
- package/src/components/Presence.tsx +15 -0
- package/src/components/Reactions.tsx +95 -0
- package/src/components/SearchUsers.tsx +90 -0
- package/src/components/SeenBy.tsx +26 -0
- package/src/components/Typing.tsx +131 -0
- package/src/contexts/MessengerContext.tsx +44 -0
- package/src/contexts/PreviewContext.tsx +105 -0
- package/src/contexts/UserContext.tsx +31 -0
- package/src/contexts/WeavyContext.tsx +66 -0
- package/src/hooks/useBadge.ts +32 -0
- package/src/hooks/useChat.ts +32 -0
- package/src/hooks/useConversation.ts +28 -0
- package/src/hooks/useConversations.ts +27 -0
- package/src/hooks/useDebounce.ts +22 -0
- package/src/hooks/useEvents.ts +43 -0
- package/src/hooks/useFileUploader.ts +35 -0
- package/src/hooks/useMembers.ts +27 -0
- package/src/hooks/useMessages.ts +42 -0
- package/src/hooks/useMessenger.ts +51 -0
- package/src/hooks/useMutateChat.ts +44 -0
- package/src/hooks/useMutateConversation.ts +40 -0
- package/src/hooks/useMutateConversationName.ts +41 -0
- package/src/hooks/useMutateDeleteReaction.ts +38 -0
- package/src/hooks/useMutateExternalBlobs.ts +39 -0
- package/src/hooks/useMutateMeeting.ts +39 -0
- package/src/hooks/useMutateMembers.ts +43 -0
- package/src/hooks/useMutateMessage.ts +116 -0
- package/src/hooks/useMutatePinned.ts +40 -0
- package/src/hooks/useMutateReaction.ts +38 -0
- package/src/hooks/useMutateRead.ts +40 -0
- package/src/hooks/useMutateRemoveMembers.ts +43 -0
- package/src/hooks/useMutateTyping.ts +34 -0
- package/src/hooks/usePresence.ts +32 -0
- package/src/hooks/usePreview.ts +21 -0
- package/src/hooks/useReactions.ts +53 -0
- package/src/hooks/useSearchUsers.ts +26 -0
- package/src/hooks/useThrottle.ts +13 -0
- package/src/hooks/useUser.ts +38 -0
- package/src/index.ts +33 -0
- package/src/types/Chat.ts +3 -0
- package/src/types/Conversation.ts +4 -0
- package/src/types/ConversationListItem.ts +4 -0
- package/src/types/Message.ts +16 -0
- package/src/types/Messenger.ts +3 -0
- package/src/types/emoji-toolkit.d.ts +1 -0
- package/src/types/types.ts +175 -0
- package/src/ui/Button.tsx +32 -0
- package/src/ui/Dropdown.tsx +58 -0
- package/src/ui/Icon.tsx +79 -0
- package/src/ui/Overlay.tsx +41 -0
- package/src/utils/fileUtilities.ts +230 -0
- package/src/utils/infiniteScroll.js +175 -0
- package/src/utils/scrollToBottom.js +75 -0
- package/src/utils/styles.ts +42 -0
- 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
|
+
}
|