@webinex/chatify 1.0.0-build2 → 1.0.0-rc2

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 (56) hide show
  1. package/package.json +90 -90
  2. package/src/ChatifyContext.tsx +41 -41
  3. package/src/core/action.tsx +319 -319
  4. package/src/core/client.ts +137 -137
  5. package/src/core/index.ts +14 -14
  6. package/src/core/models.tsx +75 -75
  7. package/src/core/reducer.tsx +86 -86
  8. package/src/core/state.ts +38 -38
  9. package/src/core/useAccounts.tsx +11 -11
  10. package/src/core/useAddChat.tsx +12 -12
  11. package/src/core/useAddMember.tsx +12 -12
  12. package/src/core/useAddMessage.tsx +40 -40
  13. package/src/core/useChat.tsx +12 -12
  14. package/src/core/useChatList.tsx +10 -10
  15. package/src/core/useLoadMore.tsx +42 -42
  16. package/src/core/useMessages.tsx +23 -23
  17. package/src/core/useMutation.tsx +48 -48
  18. package/src/core/useQuery.tsx +45 -45
  19. package/src/core/useReadMonitor.tsx +31 -31
  20. package/src/core/useRemoveMember.tsx +12 -12
  21. package/src/core/useSignalRMonitor.tsx +52 -52
  22. package/src/index.ts +3 -3
  23. package/src/ui/AddChat.tsx +55 -55
  24. package/src/ui/AddChatButton.tsx +19 -19
  25. package/src/ui/Aside.tsx +41 -41
  26. package/src/ui/Avatar.tsx +18 -18
  27. package/src/ui/Chat.tsx +26 -26
  28. package/src/ui/ChatBody.tsx +43 -43
  29. package/src/ui/ChatHeader.tsx +33 -33
  30. package/src/ui/ChatHeaderMembers.tsx +30 -30
  31. package/src/ui/ChatHeaderSettingsButton.tsx +26 -26
  32. package/src/ui/ChatListItem.tsx +101 -101
  33. package/src/ui/ChatName.tsx +15 -15
  34. package/src/ui/ChatSettings.tsx +59 -59
  35. package/src/ui/Chatify.tsx +106 -106
  36. package/src/ui/Footer.tsx +7 -7
  37. package/src/ui/Header.tsx +14 -14
  38. package/src/ui/Icon.tsx +21 -21
  39. package/src/ui/InputForm.tsx +51 -51
  40. package/src/ui/LoadMore.tsx +40 -40
  41. package/src/ui/Main.tsx +16 -16
  42. package/src/ui/Message.tsx +118 -118
  43. package/src/ui/MessageSkeleton.tsx +19 -19
  44. package/src/ui/SendingMessage.tsx +50 -50
  45. package/src/ui/SystemMessage.tsx +32 -32
  46. package/src/ui/index.ts +11 -11
  47. package/src/ui/localizer.tsx +68 -68
  48. package/src/ui/styles/aside.scss +94 -94
  49. package/src/ui/styles/index.scss +248 -248
  50. package/src/ui/styles/keyframes.scss +14 -14
  51. package/src/ui/styles/mixins.scss +20 -20
  52. package/src/ui/styles/variables.scss +10 -10
  53. package/src/util/customize.tsx +45 -45
  54. package/src/util/index.ts +3 -3
  55. package/src/util/uniqBy.tsx +17 -17
  56. package/src/util/uniqId.tsx +3 -3
@@ -1,137 +1,137 @@
1
- import { AxiosInstance } from 'axios';
2
- import * as SignalR from '@microsoft/signalr';
3
- import {
4
- Account,
5
- AddChatRequest,
6
- AddMemberRequest,
7
- Chat,
8
- ChatListItem,
9
- Message,
10
- ReadRequest,
11
- RemoveMemberRequest,
12
- SendMessageRequest,
13
- } from './models';
14
-
15
- export interface ChatifySignalRConfig {
16
- hubUri?: string;
17
- accessTokenFactory?: () => Promise<string>;
18
- headersFactory?: () => Promise<Record<string, string>>;
19
- }
20
-
21
- const DEFAULT_SIGNALR_CONFIG: ChatifySignalRConfig = {
22
- hubUri: '/api/chatify/hub',
23
- headersFactory: () => Promise.resolve({}),
24
- };
25
-
26
- export interface ChatifyClientConfig {
27
- axios: AxiosInstance;
28
- signalR: ChatifySignalRConfig;
29
- }
30
-
31
- const METHODS = [
32
- 'chatify://new-message',
33
- 'chatify://chat-created',
34
- 'chatify://read',
35
- 'chatify://member-added',
36
- 'chatify://member-removed',
37
- ] as const;
38
-
39
- export class ChatifyClient {
40
- private _config: ChatifyClientConfig;
41
- private _connection: SignalR.HubConnection = null!;
42
- private _subscribers: Record<string, ((args: any[]) => void)[]> = {};
43
- private _reconnectSubscribers: Array<() => void> = [];
44
-
45
- constructor(config: ChatifyClientConfig) {
46
- this._config = {
47
- ...config,
48
- signalR: {
49
- ...DEFAULT_SIGNALR_CONFIG,
50
- ...config.signalR,
51
- },
52
- };
53
- }
54
-
55
- public connect = async () => {
56
- const headers = await this._config.signalR!.headersFactory!();
57
-
58
- this._connection = new SignalR.HubConnectionBuilder()
59
- .withUrl(this._config.signalR!.hubUri!, {
60
- accessTokenFactory: this._config.signalR!.accessTokenFactory
61
- ? this._config.signalR!.accessTokenFactory
62
- : undefined,
63
- headers,
64
- })
65
- .withAutomaticReconnect()
66
- .build();
67
-
68
- METHODS.forEach((x) => {
69
- this._connection.on(x, (...args) => {
70
- this._subscribers[x]?.forEach((s) => s(args));
71
- });
72
- });
73
-
74
- this._connection.onreconnected(() => this._reconnectSubscribers.forEach((subscriber) => subscriber()));
75
-
76
- await this._connection.start();
77
- };
78
-
79
- public subscribe = <T extends any[]>(method: string, subscriber: (args: T) => void) => {
80
- this._subscribers[method] = this._subscribers[method] ?? [];
81
- this._subscribers[method].push(subscriber as any);
82
- return () => this._subscribers[method].filter((x) => x !== subscriber);
83
- };
84
-
85
- public subscribeReconnect = (subscriber: () => any) => {
86
- this._reconnectSubscribers.push(subscriber);
87
- return () => this._reconnectSubscribers.filter((x) => x !== subscriber);
88
- };
89
-
90
- private get axios() {
91
- return this._config.axios;
92
- }
93
-
94
- public accounts = async () => {
95
- const { data } = await this.axios.get<Account[]>('account');
96
- return data;
97
- };
98
-
99
- public chats = async () => {
100
- const { data } = await this.axios.get<ChatListItem[]>('chat');
101
- return data;
102
- };
103
-
104
- public chat = async (id: string) => {
105
- const { data } = await this.axios.get<Chat>(`chat/${id}`);
106
- return data;
107
- };
108
-
109
- public addChat = async (args: AddChatRequest) => {
110
- const { data } = await this.axios.post<string>(`chat`, args);
111
- return data;
112
- };
113
-
114
- public messages = async (args: { chatId: string; skip?: number; take?: number }) => {
115
- const { chatId, skip = 0, take = 20 } = args;
116
- const pagingRule = encodeURIComponent(JSON.stringify({ skip, take }));
117
- const { data } = await this.axios.get<Message[]>(`chat/${chatId}/message?pagingRule=${pagingRule}`);
118
- return data;
119
- };
120
-
121
- public send = async (request: SendMessageRequest) => {
122
- const { chatId, ...rest } = request;
123
- await this.axios.post(`chat/${chatId}/message`, rest);
124
- };
125
-
126
- public read = async (request: ReadRequest) => {
127
- await this.axios.put(`chat/message/read`, request);
128
- };
129
-
130
- public removeMember = async (request: RemoveMemberRequest) => {
131
- await this.axios.delete(`chat/${request.chatId}/member`, { data: request });
132
- };
133
-
134
- public addMember = async (request: AddMemberRequest) => {
135
- await this.axios.post(`chat/${request.chatId}/member`, request);
136
- };
137
- }
1
+ import { AxiosInstance } from 'axios';
2
+ import * as SignalR from '@microsoft/signalr';
3
+ import {
4
+ Account,
5
+ AddChatRequest,
6
+ AddMemberRequest,
7
+ Chat,
8
+ ChatListItem,
9
+ Message,
10
+ ReadRequest,
11
+ RemoveMemberRequest,
12
+ SendMessageRequest,
13
+ } from './models';
14
+
15
+ export interface ChatifySignalRConfig {
16
+ hubUri?: string;
17
+ accessTokenFactory?: () => Promise<string>;
18
+ headersFactory?: () => Promise<Record<string, string>>;
19
+ }
20
+
21
+ const DEFAULT_SIGNALR_CONFIG: ChatifySignalRConfig = {
22
+ hubUri: '/api/chatify/hub',
23
+ headersFactory: () => Promise.resolve({}),
24
+ };
25
+
26
+ export interface ChatifyClientConfig {
27
+ axios: AxiosInstance;
28
+ signalR: ChatifySignalRConfig;
29
+ }
30
+
31
+ const METHODS = [
32
+ 'chatify://new-message',
33
+ 'chatify://chat-created',
34
+ 'chatify://read',
35
+ 'chatify://member-added',
36
+ 'chatify://member-removed',
37
+ ] as const;
38
+
39
+ export class ChatifyClient {
40
+ private _config: ChatifyClientConfig;
41
+ private _connection: SignalR.HubConnection = null!;
42
+ private _subscribers: Record<string, ((args: any[]) => void)[]> = {};
43
+ private _reconnectSubscribers: Array<() => void> = [];
44
+
45
+ constructor(config: ChatifyClientConfig) {
46
+ this._config = {
47
+ ...config,
48
+ signalR: {
49
+ ...DEFAULT_SIGNALR_CONFIG,
50
+ ...config.signalR,
51
+ },
52
+ };
53
+ }
54
+
55
+ public connect = async () => {
56
+ const headers = await this._config.signalR!.headersFactory!();
57
+
58
+ this._connection = new SignalR.HubConnectionBuilder()
59
+ .withUrl(this._config.signalR!.hubUri!, {
60
+ accessTokenFactory: this._config.signalR!.accessTokenFactory
61
+ ? this._config.signalR!.accessTokenFactory
62
+ : undefined,
63
+ headers,
64
+ })
65
+ .withAutomaticReconnect()
66
+ .build();
67
+
68
+ METHODS.forEach((x) => {
69
+ this._connection.on(x, (...args) => {
70
+ this._subscribers[x]?.forEach((s) => s(args));
71
+ });
72
+ });
73
+
74
+ this._connection.onreconnected(() => this._reconnectSubscribers.forEach((subscriber) => subscriber()));
75
+
76
+ await this._connection.start();
77
+ };
78
+
79
+ public subscribe = <T extends any[]>(method: string, subscriber: (args: T) => void) => {
80
+ this._subscribers[method] = this._subscribers[method] ?? [];
81
+ this._subscribers[method].push(subscriber as any);
82
+ return () => this._subscribers[method].filter((x) => x !== subscriber);
83
+ };
84
+
85
+ public subscribeReconnect = (subscriber: () => any) => {
86
+ this._reconnectSubscribers.push(subscriber);
87
+ return () => this._reconnectSubscribers.filter((x) => x !== subscriber);
88
+ };
89
+
90
+ private get axios() {
91
+ return this._config.axios;
92
+ }
93
+
94
+ public accounts = async () => {
95
+ const { data } = await this.axios.get<Account[]>('account');
96
+ return data;
97
+ };
98
+
99
+ public chats = async () => {
100
+ const { data } = await this.axios.get<ChatListItem[]>('chat');
101
+ return data;
102
+ };
103
+
104
+ public chat = async (id: string) => {
105
+ const { data } = await this.axios.get<Chat>(`chat/${id}`);
106
+ return data;
107
+ };
108
+
109
+ public addChat = async (args: AddChatRequest) => {
110
+ const { data } = await this.axios.post<string>(`chat`, args);
111
+ return data;
112
+ };
113
+
114
+ public messages = async (args: { chatId: string; skip?: number; take?: number }) => {
115
+ const { chatId, skip = 0, take = 20 } = args;
116
+ const pagingRule = encodeURIComponent(JSON.stringify({ skip, take }));
117
+ const { data } = await this.axios.get<Message[]>(`chat/${chatId}/message?pagingRule=${pagingRule}`);
118
+ return data;
119
+ };
120
+
121
+ public send = async (request: SendMessageRequest) => {
122
+ const { chatId, ...rest } = request;
123
+ await this.axios.post(`chat/${chatId}/message`, rest);
124
+ };
125
+
126
+ public read = async (request: ReadRequest) => {
127
+ await this.axios.put(`chat/message/read`, request);
128
+ };
129
+
130
+ public removeMember = async (request: RemoveMemberRequest) => {
131
+ await this.axios.delete(`chat/${request.chatId}/member`, { data: request });
132
+ };
133
+
134
+ public addMember = async (request: AddMemberRequest) => {
135
+ await this.axios.post(`chat/${request.chatId}/member`, request);
136
+ };
137
+ }
package/src/core/index.ts CHANGED
@@ -1,14 +1,14 @@
1
- export * from './models';
2
- export * from './client';
3
- export * from './action';
4
- export * from './reducer';
5
- export * from './useChat';
6
- export * from './useChatList';
7
- export * from './useLoadMore';
8
- export * from './useMessages';
9
- export * from './useSignalRMonitor';
10
- export * from './useAccounts';
11
- export * from './useAddChat';
12
- export * from './useReadMonitor';
13
- export * from './useAddMember';
14
- export * from './useRemoveMember';
1
+ export * from './models';
2
+ export * from './client';
3
+ export * from './action';
4
+ export * from './reducer';
5
+ export * from './useChat';
6
+ export * from './useChatList';
7
+ export * from './useLoadMore';
8
+ export * from './useMessages';
9
+ export * from './useSignalRMonitor';
10
+ export * from './useAccounts';
11
+ export * from './useAddChat';
12
+ export * from './useReadMonitor';
13
+ export * from './useAddMember';
14
+ export * from './useRemoveMember';
@@ -1,75 +1,75 @@
1
- export interface File {
2
- name: string;
3
- bytes: number;
4
- ref: string;
5
- }
6
-
7
- export interface Account {
8
- id: string;
9
- name: string;
10
- avatar: string;
11
- }
12
-
13
- export interface Message {
14
- id: string;
15
- chatId: string;
16
- text: string;
17
- files: File[];
18
- sentAt: string;
19
- sentBy: Account;
20
- read: boolean;
21
- requestId?: string;
22
- }
23
-
24
- export interface Chat {
25
- id: string;
26
- name: string;
27
- members: Account[];
28
- }
29
-
30
- export interface ChatListItem {
31
- id: string;
32
- name: string;
33
- message: Message;
34
- unreadCount: number;
35
- }
36
-
37
- export interface SendMessageRequest {
38
- chatId: string;
39
- text: string;
40
- files: File[];
41
- requestId: string;
42
- }
43
-
44
- export interface MessageBody {
45
- text: string;
46
- files: File[];
47
- }
48
-
49
- export interface AddChatRequest {
50
- requestId: string;
51
- name: string;
52
- message?: MessageBody;
53
- members: string[];
54
- }
55
-
56
- export interface ReadRequest {
57
- ids: string[];
58
- }
59
-
60
- export interface ReadEvent {
61
- messageId: string;
62
- chatId: string;
63
- }
64
-
65
- export interface RemoveMemberRequest {
66
- chatId: string;
67
- accountId: string;
68
- deleteHistory: boolean;
69
- }
70
-
71
- export interface AddMemberRequest {
72
- chatId: string;
73
- accountId: string;
74
- withHistory: boolean;
75
- }
1
+ export interface File {
2
+ name: string;
3
+ bytes: number;
4
+ ref: string;
5
+ }
6
+
7
+ export interface Account {
8
+ id: string;
9
+ name: string;
10
+ avatar: string;
11
+ }
12
+
13
+ export interface Message {
14
+ id: string;
15
+ chatId: string;
16
+ text: string;
17
+ files: File[];
18
+ sentAt: string;
19
+ sentBy: Account;
20
+ read: boolean;
21
+ requestId?: string;
22
+ }
23
+
24
+ export interface Chat {
25
+ id: string;
26
+ name: string;
27
+ members: Account[];
28
+ }
29
+
30
+ export interface ChatListItem {
31
+ id: string;
32
+ name: string;
33
+ message: Message;
34
+ unreadCount: number;
35
+ }
36
+
37
+ export interface SendMessageRequest {
38
+ chatId: string;
39
+ text: string;
40
+ files: File[];
41
+ requestId: string;
42
+ }
43
+
44
+ export interface MessageBody {
45
+ text: string;
46
+ files: File[];
47
+ }
48
+
49
+ export interface AddChatRequest {
50
+ requestId: string;
51
+ name: string;
52
+ message?: MessageBody;
53
+ members: string[];
54
+ }
55
+
56
+ export interface ReadRequest {
57
+ ids: string[];
58
+ }
59
+
60
+ export interface ReadEvent {
61
+ messageId: string;
62
+ chatId: string;
63
+ }
64
+
65
+ export interface RemoveMemberRequest {
66
+ chatId: string;
67
+ accountId: string;
68
+ deleteHistory: boolean;
69
+ }
70
+
71
+ export interface AddMemberRequest {
72
+ chatId: string;
73
+ accountId: string;
74
+ withHistory: boolean;
75
+ }
@@ -1,86 +1,86 @@
1
- import React, { PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
2
- import { ACTIONS, Action } from './action';
3
- import { INITIAL_STATE, State } from './state';
4
- import { shallowEqual } from 'shallow-equal';
5
-
6
- function reducer(state: State, action: Action) {
7
- const result = ACTIONS[action.type](state, action.data as any);
8
- console.debug('[Chatify]: ', action, result);
9
- return result;
10
- }
11
-
12
- export type Reducer = ReturnType<typeof useReducer>;
13
-
14
- type Subscriber = (state: State) => void;
15
-
16
- export function useReducer() {
17
- const state = useRef(INITIAL_STATE);
18
- const subscribers = useRef<Subscriber[]>([]);
19
-
20
- const dispatch = useCallback((action: Action) => {
21
- const newState = reducer(state.current, action);
22
- if (state.current === newState) {
23
- return;
24
- }
25
-
26
- state.current = newState;
27
- subscribers.current.forEach((s) => s(newState));
28
- }, []);
29
-
30
- const subscribe = useCallback((subscriber: Subscriber) => {
31
- subscribers.current.push(subscriber);
32
- return () => {
33
- subscribers.current = subscribers.current.filter((x) => x !== subscriber);
34
- };
35
- }, []);
36
-
37
- return [state, dispatch, subscribe] as const;
38
- }
39
-
40
- const ReducerContext = React.createContext<Reducer>(null!);
41
- export function ReducerProvider({ children }: PropsWithChildren<{}>) {
42
- const reducer = useReducer();
43
- return <ReducerContext.Provider value={reducer}>{children}</ReducerContext.Provider>;
44
- }
45
-
46
- export function useDispatch() {
47
- return useContext(ReducerContext)[1];
48
- }
49
-
50
- export function useStateRef() {
51
- const [reducerState] = useContext(ReducerContext);
52
- return reducerState;
53
- }
54
-
55
- export function useSelect<TResult>(
56
- select: (state: State) => TResult,
57
-
58
- // TODO: make deps work
59
- deps: any[],
60
- ): TResult {
61
- const [reducerState, , subscribe] = useContext(ReducerContext);
62
- const prev = useRef(select(reducerState.current));
63
- const [state, setState] = useState(prev.current);
64
-
65
- useEffect(() => {
66
- const value = select(reducerState.current);
67
- if (!shallowEqual<any>(prev.current, value)) {
68
- prev.current = value;
69
- setState(value);
70
- }
71
-
72
- return subscribe((state: State) => {
73
- const newValue = select(state);
74
- if (shallowEqual<any>(newValue, prev.current)) {
75
- return;
76
- }
77
-
78
- prev.current = newValue;
79
- setState(newValue);
80
- });
81
-
82
- // eslint-disable-next-line react-hooks/exhaustive-deps
83
- }, []);
84
-
85
- return state;
86
- }
1
+ import React, { PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
2
+ import { ACTIONS, Action } from './action';
3
+ import { INITIAL_STATE, State } from './state';
4
+ import { shallowEqual } from 'shallow-equal';
5
+
6
+ function reducer(state: State, action: Action) {
7
+ const result = ACTIONS[action.type](state, action.data as any);
8
+ console.debug('[Chatify]: ', action, result);
9
+ return result;
10
+ }
11
+
12
+ export type Reducer = ReturnType<typeof useReducer>;
13
+
14
+ type Subscriber = (state: State) => void;
15
+
16
+ export function useReducer() {
17
+ const state = useRef(INITIAL_STATE);
18
+ const subscribers = useRef<Subscriber[]>([]);
19
+
20
+ const dispatch = useCallback((action: Action) => {
21
+ const newState = reducer(state.current, action);
22
+ if (state.current === newState) {
23
+ return;
24
+ }
25
+
26
+ state.current = newState;
27
+ subscribers.current.forEach((s) => s(newState));
28
+ }, []);
29
+
30
+ const subscribe = useCallback((subscriber: Subscriber) => {
31
+ subscribers.current.push(subscriber);
32
+ return () => {
33
+ subscribers.current = subscribers.current.filter((x) => x !== subscriber);
34
+ };
35
+ }, []);
36
+
37
+ return [state, dispatch, subscribe] as const;
38
+ }
39
+
40
+ const ReducerContext = React.createContext<Reducer>(null!);
41
+ export function ReducerProvider({ children }: PropsWithChildren<{}>) {
42
+ const reducer = useReducer();
43
+ return <ReducerContext.Provider value={reducer}>{children}</ReducerContext.Provider>;
44
+ }
45
+
46
+ export function useDispatch() {
47
+ return useContext(ReducerContext)[1];
48
+ }
49
+
50
+ export function useStateRef() {
51
+ const [reducerState] = useContext(ReducerContext);
52
+ return reducerState;
53
+ }
54
+
55
+ export function useSelect<TResult>(
56
+ select: (state: State) => TResult,
57
+
58
+ // TODO: make deps work
59
+ deps: any[],
60
+ ): TResult {
61
+ const [reducerState, , subscribe] = useContext(ReducerContext);
62
+ const prev = useRef(select(reducerState.current));
63
+ const [state, setState] = useState(prev.current);
64
+
65
+ useEffect(() => {
66
+ const value = select(reducerState.current);
67
+ if (!shallowEqual<any>(prev.current, value)) {
68
+ prev.current = value;
69
+ setState(value);
70
+ }
71
+
72
+ return subscribe((state: State) => {
73
+ const newValue = select(state);
74
+ if (shallowEqual<any>(newValue, prev.current)) {
75
+ return;
76
+ }
77
+
78
+ prev.current = newValue;
79
+ setState(newValue);
80
+ });
81
+
82
+ // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ }, []);
84
+
85
+ return state;
86
+ }