@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.
- package/package.json +90 -90
- package/src/ChatifyContext.tsx +41 -41
- package/src/core/action.tsx +319 -319
- package/src/core/client.ts +137 -137
- package/src/core/index.ts +14 -14
- package/src/core/models.tsx +75 -75
- package/src/core/reducer.tsx +86 -86
- package/src/core/state.ts +38 -38
- package/src/core/useAccounts.tsx +11 -11
- package/src/core/useAddChat.tsx +12 -12
- package/src/core/useAddMember.tsx +12 -12
- package/src/core/useAddMessage.tsx +40 -40
- package/src/core/useChat.tsx +12 -12
- package/src/core/useChatList.tsx +10 -10
- package/src/core/useLoadMore.tsx +42 -42
- package/src/core/useMessages.tsx +23 -23
- package/src/core/useMutation.tsx +48 -48
- package/src/core/useQuery.tsx +45 -45
- package/src/core/useReadMonitor.tsx +31 -31
- package/src/core/useRemoveMember.tsx +12 -12
- package/src/core/useSignalRMonitor.tsx +52 -52
- package/src/index.ts +3 -3
- package/src/ui/AddChat.tsx +55 -55
- package/src/ui/AddChatButton.tsx +19 -19
- package/src/ui/Aside.tsx +41 -41
- package/src/ui/Avatar.tsx +18 -18
- package/src/ui/Chat.tsx +26 -26
- package/src/ui/ChatBody.tsx +43 -43
- package/src/ui/ChatHeader.tsx +33 -33
- package/src/ui/ChatHeaderMembers.tsx +30 -30
- package/src/ui/ChatHeaderSettingsButton.tsx +26 -26
- package/src/ui/ChatListItem.tsx +101 -101
- package/src/ui/ChatName.tsx +15 -15
- package/src/ui/ChatSettings.tsx +59 -59
- package/src/ui/Chatify.tsx +106 -106
- package/src/ui/Footer.tsx +7 -7
- package/src/ui/Header.tsx +14 -14
- package/src/ui/Icon.tsx +21 -21
- package/src/ui/InputForm.tsx +51 -51
- package/src/ui/LoadMore.tsx +40 -40
- package/src/ui/Main.tsx +16 -16
- package/src/ui/Message.tsx +118 -118
- package/src/ui/MessageSkeleton.tsx +19 -19
- package/src/ui/SendingMessage.tsx +50 -50
- package/src/ui/SystemMessage.tsx +32 -32
- package/src/ui/index.ts +11 -11
- package/src/ui/localizer.tsx +68 -68
- package/src/ui/styles/aside.scss +94 -94
- package/src/ui/styles/index.scss +248 -248
- package/src/ui/styles/keyframes.scss +14 -14
- package/src/ui/styles/mixins.scss +20 -20
- package/src/ui/styles/variables.scss +10 -10
- package/src/util/customize.tsx +45 -45
- package/src/util/index.ts +3 -3
- package/src/util/uniqBy.tsx +17 -17
- package/src/util/uniqId.tsx +3 -3
package/src/core/client.ts
CHANGED
|
@@ -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';
|
package/src/core/models.tsx
CHANGED
|
@@ -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
|
+
}
|
package/src/core/reducer.tsx
CHANGED
|
@@ -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
|
+
}
|