@webinex/chatify 1.0.0-rc3 → 1.0.2-rc3

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 (98) hide show
  1. package/dist/esm/index.js +1 -1
  2. package/dist/esm/index.js.map +1 -1
  3. package/dist/esm/types/core/action.d.ts +10 -4
  4. package/dist/esm/types/core/state.d.ts +2 -1
  5. package/dist/esm/types/core/useGetAccountsQuery.d.ts +3 -1
  6. package/dist/esm/types/core/useGetChatListQuery.d.ts +3 -1
  7. package/dist/esm/types/core/useGetChatQuery.d.ts +3 -1
  8. package/dist/esm/types/ui/Icon.d.ts +8 -8
  9. package/dist/index.d.ts +21 -14
  10. package/package.json +92 -92
  11. package/src/ChatifyContext.tsx +41 -41
  12. package/src/core/action.tsx +421 -405
  13. package/src/core/client.ts +143 -143
  14. package/src/core/index.ts +17 -17
  15. package/src/core/models.tsx +82 -82
  16. package/src/core/reducer.tsx +87 -87
  17. package/src/core/state.ts +93 -92
  18. package/src/core/useAddChatMutation.tsx +22 -22
  19. package/src/core/useAddMemberMutation.tsx +25 -25
  20. package/src/core/useFetchNextMutation.tsx +28 -28
  21. package/src/core/useGetAccountsQuery.tsx +30 -33
  22. package/src/core/useGetChatListQuery.tsx +30 -33
  23. package/src/core/useGetChatQuery.tsx +31 -34
  24. package/src/core/useGetMessagesQuery.tsx +35 -35
  25. package/src/core/useReadMonitor.tsx +32 -32
  26. package/src/core/useRemoveMemberMutation.tsx +23 -23
  27. package/src/core/useSendMutation.tsx +24 -24
  28. package/src/core/useSignalRMonitor.tsx +66 -66
  29. package/src/core/useUpdateChatNameMutation.tsx +23 -23
  30. package/src/index.ts +3 -3
  31. package/src/styles/aside.scss +94 -94
  32. package/src/styles/chat.scss +222 -222
  33. package/src/styles/index.scss +79 -79
  34. package/src/styles/keyframes.scss +14 -14
  35. package/src/styles/mixins.scss +20 -20
  36. package/src/styles/settings.scss +5 -5
  37. package/src/styles/variables.scss +10 -10
  38. package/src/ui/AddChatButton.tsx +15 -15
  39. package/src/ui/Avatar.tsx +18 -18
  40. package/src/ui/ChatList/ChatList.tsx +42 -42
  41. package/src/ui/ChatList/ChatListItemBox.tsx +22 -22
  42. package/src/ui/ChatList/ChatListItemLastMessage.tsx +13 -13
  43. package/src/ui/ChatList/ChatListItemLastMessageAuthor.tsx +20 -20
  44. package/src/ui/ChatList/ChatListItemLastMessageContent.tsx +21 -21
  45. package/src/ui/ChatList/ChatListItemName.tsx +13 -13
  46. package/src/ui/ChatList/ChatListItemUnreadCount.tsx +12 -12
  47. package/src/ui/ChatList/index.ts +7 -7
  48. package/src/ui/ChatName.tsx +10 -10
  49. package/src/ui/ChatPanel/ChatBody.tsx +44 -44
  50. package/src/ui/ChatPanel/ChatHeader.tsx +33 -33
  51. package/src/ui/ChatPanel/ChatHeaderMembers.tsx +26 -26
  52. package/src/ui/ChatPanel/ChatHeaderName.tsx +67 -67
  53. package/src/ui/ChatPanel/ChatMembersButton.tsx +21 -21
  54. package/src/ui/ChatPanel/ChatMembersPanel.tsx +117 -117
  55. package/src/ui/ChatPanel/ChatPanel.tsx +27 -27
  56. package/src/ui/ChatPanel/index.ts +6 -6
  57. package/src/ui/Chatify.tsx +148 -148
  58. package/src/ui/CreateChatPanel/CreateChatForm.tsx +36 -36
  59. package/src/ui/CreateChatPanel/CreateChatMemberInput.tsx +66 -66
  60. package/src/ui/CreateChatPanel/CreateChatNameInput.tsx +25 -25
  61. package/src/ui/CreateChatPanel/CreateChatPanel.tsx +33 -33
  62. package/src/ui/CreateChatPanel/index.ts +4 -4
  63. package/src/ui/Icon.tsx +33 -33
  64. package/src/ui/InputBox/InputBox.tsx +38 -38
  65. package/src/ui/InputBox/InputFilesBox.tsx +9 -9
  66. package/src/ui/InputBox/InputSubmitButtonBox.tsx +17 -17
  67. package/src/ui/InputBox/InputTextBox.tsx +50 -50
  68. package/src/ui/InputBox/index.ts +4 -4
  69. package/src/ui/Layout/Aside.tsx +10 -10
  70. package/src/ui/Layout/Body.tsx +10 -10
  71. package/src/ui/Layout/Footer.tsx +3 -3
  72. package/src/ui/Layout/Header.tsx +10 -10
  73. package/src/ui/Layout/Main.tsx +15 -15
  74. package/src/ui/Layout/index.ts +5 -5
  75. package/src/ui/Message/MessageAuthor.tsx +13 -13
  76. package/src/ui/Message/MessageBox.tsx +20 -20
  77. package/src/ui/Message/MessageContent.tsx +13 -13
  78. package/src/ui/Message/MessageInfoBox.tsx +25 -25
  79. package/src/ui/Message/MessageRow.tsx +24 -24
  80. package/src/ui/Message/MessageText.tsx +7 -7
  81. package/src/ui/Message/index.ts +6 -6
  82. package/src/ui/MessageReadObserver.tsx +45 -45
  83. package/src/ui/MessageSkeleton.tsx +19 -19
  84. package/src/ui/Next.tsx +48 -48
  85. package/src/ui/SendingMessage/SendingMessageBox.tsx +16 -16
  86. package/src/ui/SendingMessage/SendingMessageContent.tsx +13 -13
  87. package/src/ui/SendingMessage/SendingMessageInfoBox.tsx +13 -13
  88. package/src/ui/SendingMessage/SendingMessageText.tsx +7 -7
  89. package/src/ui/SendingMessage/index.ts +4 -4
  90. package/src/ui/SystemMessage/SystemMessageBox.tsx +20 -20
  91. package/src/ui/SystemMessage/SystemMessageRow.tsx +43 -43
  92. package/src/ui/SystemMessage/index.ts +2 -2
  93. package/src/ui/customize.tsx +45 -45
  94. package/src/ui/index.ts +11 -11
  95. package/src/ui/localizer.tsx +80 -80
  96. package/src/util/index.ts +2 -2
  97. package/src/util/uniqBy.tsx +17 -17
  98. package/src/util/uniqId.tsx +3 -3
@@ -1,143 +1,143 @@
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
- UpdateChatNameRequest,
14
- } from './models';
15
-
16
- export interface ChatifySignalRConfig {
17
- hubUri?: string;
18
- accessTokenFactory?: () => Promise<string>;
19
- headersFactory?: () => Promise<Record<string, string>>;
20
- }
21
-
22
- const DEFAULT_SIGNALR_CONFIG: ChatifySignalRConfig = {
23
- hubUri: '/api/chatify/hub',
24
- headersFactory: () => Promise.resolve({}),
25
- };
26
-
27
- export interface ChatifyClientConfig {
28
- axios: AxiosInstance;
29
- signalR: ChatifySignalRConfig;
30
- }
31
-
32
- const METHODS = [
33
- 'chatify://new-message',
34
- 'chatify://chat-created',
35
- 'chatify://read',
36
- 'chatify://member-added',
37
- 'chatify://member-removed',
38
- 'chatify://chat-name-changed',
39
- ] as const;
40
-
41
- export class ChatifyClient {
42
- private _config: ChatifyClientConfig;
43
- private _connection: SignalR.HubConnection = null!;
44
- private _subscribers: Record<string, ((args: any[]) => void)[]> = {};
45
- private _reconnectSubscribers: Array<() => void> = [];
46
-
47
- constructor(config: ChatifyClientConfig) {
48
- this._config = {
49
- ...config,
50
- signalR: {
51
- ...DEFAULT_SIGNALR_CONFIG,
52
- ...config.signalR,
53
- },
54
- };
55
- }
56
-
57
- public connect = async () => {
58
- const headers = await this._config.signalR!.headersFactory!();
59
-
60
- this._connection = new SignalR.HubConnectionBuilder()
61
- .withUrl(this._config.signalR!.hubUri!, {
62
- accessTokenFactory: this._config.signalR!.accessTokenFactory
63
- ? this._config.signalR!.accessTokenFactory
64
- : undefined,
65
- headers,
66
- })
67
- .withAutomaticReconnect()
68
- .build();
69
-
70
- METHODS.forEach((x) => {
71
- this._connection.on(x, (...args) => {
72
- this._subscribers[x]?.forEach((s) => s(args));
73
- });
74
- });
75
-
76
- this._connection.onreconnected(() => this._reconnectSubscribers.forEach((subscriber) => subscriber()));
77
-
78
- await this._connection.start();
79
- };
80
-
81
- public subscribe = <T extends any[]>(method: string, subscriber: (args: T) => void) => {
82
- this._subscribers[method] = this._subscribers[method] ?? [];
83
- this._subscribers[method].push(subscriber as any);
84
- return () => this._subscribers[method].filter((x) => x !== subscriber);
85
- };
86
-
87
- public subscribeReconnect = (subscriber: () => any) => {
88
- this._reconnectSubscribers.push(subscriber);
89
- return () => this._reconnectSubscribers.filter((x) => x !== subscriber);
90
- };
91
-
92
- private get axios() {
93
- return this._config.axios;
94
- }
95
-
96
- public accounts = async () => {
97
- const { data } = await this.axios.get<Account[]>('account');
98
- return data;
99
- };
100
-
101
- public chats = async () => {
102
- const { data } = await this.axios.get<ChatListItem[]>('chat');
103
- return data;
104
- };
105
-
106
- public chat = async (id: string) => {
107
- const { data } = await this.axios.get<Chat>(`chat/${id}`);
108
- return data;
109
- };
110
-
111
- public addChat = async (args: AddChatRequest) => {
112
- const { data } = await this.axios.post<string>(`chat`, args);
113
- return data;
114
- };
115
-
116
- public messages = async (args: { chatId: string; skip?: number; take?: number }) => {
117
- const { chatId, skip = 0, take = 20 } = args;
118
- const pagingRule = encodeURIComponent(JSON.stringify({ skip, take }));
119
- const { data } = await this.axios.get<Message[]>(`chat/${chatId}/message?pagingRule=${pagingRule}`);
120
- return data;
121
- };
122
-
123
- public send = async (request: SendMessageRequest) => {
124
- const { chatId, ...rest } = request;
125
- await this.axios.post(`chat/${chatId}/message`, rest);
126
- };
127
-
128
- public read = async (request: ReadRequest) => {
129
- await this.axios.put(`chat/message/read`, request);
130
- };
131
-
132
- public removeMember = async (request: RemoveMemberRequest) => {
133
- await this.axios.delete(`chat/${request.chatId}/member`, { data: request });
134
- };
135
-
136
- public updateChatName = async (request: UpdateChatNameRequest) => {
137
- await this.axios.put(`chat/${request.id}/name`, request);
138
- };
139
-
140
- public addMember = async (request: AddMemberRequest) => {
141
- await this.axios.post(`chat/${request.chatId}/member`, request);
142
- };
143
- }
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
+ UpdateChatNameRequest,
14
+ } from './models';
15
+
16
+ export interface ChatifySignalRConfig {
17
+ hubUri?: string;
18
+ accessTokenFactory?: () => Promise<string>;
19
+ headersFactory?: () => Promise<Record<string, string>>;
20
+ }
21
+
22
+ const DEFAULT_SIGNALR_CONFIG: ChatifySignalRConfig = {
23
+ hubUri: '/api/chatify/hub',
24
+ headersFactory: () => Promise.resolve({}),
25
+ };
26
+
27
+ export interface ChatifyClientConfig {
28
+ axios: AxiosInstance;
29
+ signalR: ChatifySignalRConfig;
30
+ }
31
+
32
+ const METHODS = [
33
+ 'chatify://new-message',
34
+ 'chatify://chat-created',
35
+ 'chatify://read',
36
+ 'chatify://member-added',
37
+ 'chatify://member-removed',
38
+ 'chatify://chat-name-changed',
39
+ ] as const;
40
+
41
+ export class ChatifyClient {
42
+ private _config: ChatifyClientConfig;
43
+ private _connection: SignalR.HubConnection = null!;
44
+ private _subscribers: Record<string, ((args: any[]) => void)[]> = {};
45
+ private _reconnectSubscribers: Array<() => void> = [];
46
+
47
+ constructor(config: ChatifyClientConfig) {
48
+ this._config = {
49
+ ...config,
50
+ signalR: {
51
+ ...DEFAULT_SIGNALR_CONFIG,
52
+ ...config.signalR,
53
+ },
54
+ };
55
+ }
56
+
57
+ public connect = async () => {
58
+ const headers = await this._config.signalR!.headersFactory!();
59
+
60
+ this._connection = new SignalR.HubConnectionBuilder()
61
+ .withUrl(this._config.signalR!.hubUri!, {
62
+ accessTokenFactory: this._config.signalR!.accessTokenFactory
63
+ ? this._config.signalR!.accessTokenFactory
64
+ : undefined,
65
+ headers,
66
+ })
67
+ .withAutomaticReconnect()
68
+ .build();
69
+
70
+ METHODS.forEach((x) => {
71
+ this._connection.on(x, (...args) => {
72
+ this._subscribers[x]?.forEach((s) => s(args));
73
+ });
74
+ });
75
+
76
+ this._connection.onreconnected(() => this._reconnectSubscribers.forEach((subscriber) => subscriber()));
77
+
78
+ await this._connection.start();
79
+ };
80
+
81
+ public subscribe = <T extends any[]>(method: string, subscriber: (args: T) => void) => {
82
+ this._subscribers[method] = this._subscribers[method] ?? [];
83
+ this._subscribers[method].push(subscriber as any);
84
+ return () => this._subscribers[method].filter((x) => x !== subscriber);
85
+ };
86
+
87
+ public subscribeReconnect = (subscriber: () => any) => {
88
+ this._reconnectSubscribers.push(subscriber);
89
+ return () => this._reconnectSubscribers.filter((x) => x !== subscriber);
90
+ };
91
+
92
+ private get axios() {
93
+ return this._config.axios;
94
+ }
95
+
96
+ public accounts = async () => {
97
+ const { data } = await this.axios.get<Account[]>('account');
98
+ return data;
99
+ };
100
+
101
+ public chats = async () => {
102
+ const { data } = await this.axios.get<ChatListItem[]>('chat');
103
+ return data;
104
+ };
105
+
106
+ public chat = async (id: string) => {
107
+ const { data } = await this.axios.get<Chat>(`chat/${id}`);
108
+ return data;
109
+ };
110
+
111
+ public addChat = async (args: AddChatRequest) => {
112
+ const { data } = await this.axios.post<string>(`chat`, args);
113
+ return data;
114
+ };
115
+
116
+ public messages = async (args: { chatId: string; skip?: number; take?: number }) => {
117
+ const { chatId, skip = 0, take = 20 } = args;
118
+ const pagingRule = encodeURIComponent(JSON.stringify({ skip, take }));
119
+ const { data } = await this.axios.get<Message[]>(`chat/${chatId}/message?pagingRule=${pagingRule}`);
120
+ return data;
121
+ };
122
+
123
+ public send = async (request: SendMessageRequest) => {
124
+ const { chatId, ...rest } = request;
125
+ await this.axios.post(`chat/${chatId}/message`, rest);
126
+ };
127
+
128
+ public read = async (request: ReadRequest) => {
129
+ await this.axios.put(`chat/message/read`, request);
130
+ };
131
+
132
+ public removeMember = async (request: RemoveMemberRequest) => {
133
+ await this.axios.delete(`chat/${request.chatId}/member`, { data: request });
134
+ };
135
+
136
+ public updateChatName = async (request: UpdateChatNameRequest) => {
137
+ await this.axios.put(`chat/${request.id}/name`, request);
138
+ };
139
+
140
+ public addMember = async (request: AddMemberRequest) => {
141
+ await this.axios.post(`chat/${request.chatId}/member`, request);
142
+ };
143
+ }
package/src/core/index.ts CHANGED
@@ -1,17 +1,17 @@
1
- export * from './models';
2
- export * from './client';
3
- export * from './action';
4
- export * from './reducer';
5
- export * from './state';
6
- export * from './useGetChatQuery';
7
- export * from './useGetChatListQuery';
8
- export * from './useFetchNextMutation';
9
- export * from './useGetMessagesQuery';
10
- export * from './useSignalRMonitor';
11
- export * from './useGetAccountsQuery';
12
- export * from './useAddChatMutation';
13
- export * from './useReadMonitor';
14
- export * from './useAddMemberMutation';
15
- export * from './useRemoveMemberMutation';
16
- export * from './useSendMutation';
17
- export * from './useUpdateChatNameMutation';
1
+ export * from './models';
2
+ export * from './client';
3
+ export * from './action';
4
+ export * from './reducer';
5
+ export * from './state';
6
+ export * from './useGetChatQuery';
7
+ export * from './useGetChatListQuery';
8
+ export * from './useFetchNextMutation';
9
+ export * from './useGetMessagesQuery';
10
+ export * from './useSignalRMonitor';
11
+ export * from './useGetAccountsQuery';
12
+ export * from './useAddChatMutation';
13
+ export * from './useReadMonitor';
14
+ export * from './useAddMemberMutation';
15
+ export * from './useRemoveMemberMutation';
16
+ export * from './useSendMutation';
17
+ export * from './useUpdateChatNameMutation';
@@ -1,82 +1,82 @@
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
- requestId?: string;
21
- }
22
-
23
- export interface Chat {
24
- id: string;
25
- name: string;
26
- members: Account[];
27
- }
28
-
29
- export interface ChatListItem {
30
- id: string;
31
- name: string;
32
- message: Message;
33
- totalUnreadCount: number;
34
- active: boolean;
35
- lastReadMessageId: string | null;
36
- }
37
-
38
- export interface SendMessageRequest {
39
- chatId: string;
40
- text: string;
41
- files: File[];
42
- requestId: string;
43
- }
44
-
45
- export interface MessageBody {
46
- text: string;
47
- files: File[];
48
- }
49
-
50
- export interface AddChatRequest {
51
- requestId: string;
52
- name: string;
53
- message?: MessageBody;
54
- members: string[];
55
- }
56
-
57
- export interface ReadRequest {
58
- id: string;
59
- }
60
-
61
- export interface ReadEvent {
62
- chatId: string;
63
- newLastReadMessageId: string;
64
- readCount: number;
65
- }
66
-
67
- export interface RemoveMemberRequest {
68
- chatId: string;
69
- accountId: string;
70
- deleteHistory: boolean;
71
- }
72
-
73
- export interface AddMemberRequest {
74
- chatId: string;
75
- accountId: string;
76
- withHistory: boolean;
77
- }
78
-
79
- export interface UpdateChatNameRequest {
80
- id: string;
81
- name: string;
82
- }
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
+ requestId?: string;
21
+ }
22
+
23
+ export interface Chat {
24
+ id: string;
25
+ name: string;
26
+ members: Account[];
27
+ }
28
+
29
+ export interface ChatListItem {
30
+ id: string;
31
+ name: string;
32
+ message: Message;
33
+ totalUnreadCount: number;
34
+ active: boolean;
35
+ lastReadMessageId: string | null;
36
+ }
37
+
38
+ export interface SendMessageRequest {
39
+ chatId: string;
40
+ text: string;
41
+ files: File[];
42
+ requestId: string;
43
+ }
44
+
45
+ export interface MessageBody {
46
+ text: string;
47
+ files: File[];
48
+ }
49
+
50
+ export interface AddChatRequest {
51
+ requestId: string;
52
+ name: string;
53
+ message?: MessageBody;
54
+ members: string[];
55
+ }
56
+
57
+ export interface ReadRequest {
58
+ id: string;
59
+ }
60
+
61
+ export interface ReadEvent {
62
+ chatId: string;
63
+ newLastReadMessageId: string;
64
+ readCount: number;
65
+ }
66
+
67
+ export interface RemoveMemberRequest {
68
+ chatId: string;
69
+ accountId: string;
70
+ deleteHistory: boolean;
71
+ }
72
+
73
+ export interface AddMemberRequest {
74
+ chatId: string;
75
+ accountId: string;
76
+ withHistory: boolean;
77
+ }
78
+
79
+ export interface UpdateChatNameRequest {
80
+ id: string;
81
+ name: string;
82
+ }
@@ -1,87 +1,87 @@
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
- import { produce } from 'immer';
6
-
7
- function reducer(state: State, action: Action) {
8
- const result = produce(state, (draft) => ACTIONS[action.type](draft, action.data as any));
9
- console.debug('[Chatify]: ', action, result);
10
- return result;
11
- }
12
-
13
- export type Reducer = ReturnType<typeof useReducer>;
14
-
15
- type Subscriber = (state: State) => void;
16
-
17
- export function useReducer() {
18
- const state = useRef(INITIAL_STATE);
19
- const subscribers = useRef<Subscriber[]>([]);
20
-
21
- const dispatch = useCallback((action: Action) => {
22
- const newState = reducer(state.current, action);
23
- if (state.current === newState) {
24
- return;
25
- }
26
-
27
- state.current = newState;
28
- subscribers.current.forEach((s) => s(newState));
29
- }, []);
30
-
31
- const subscribe = useCallback((subscriber: Subscriber) => {
32
- subscribers.current.push(subscriber);
33
- return () => {
34
- subscribers.current = subscribers.current.filter((x) => x !== subscriber);
35
- };
36
- }, []);
37
-
38
- return [state, dispatch, subscribe] as const;
39
- }
40
-
41
- const ReducerContext = React.createContext<Reducer>(null!);
42
- export function ReducerProvider({ children }: PropsWithChildren<{}>) {
43
- const reducer = useReducer();
44
- return <ReducerContext.Provider value={reducer}>{children}</ReducerContext.Provider>;
45
- }
46
-
47
- export function useDispatch() {
48
- return useContext(ReducerContext)[1];
49
- }
50
-
51
- export function useStateRef() {
52
- const [reducerState] = useContext(ReducerContext);
53
- return reducerState;
54
- }
55
-
56
- export function useSelect<TResult>(
57
- select: (state: State) => TResult,
58
-
59
- // TODO: make deps work
60
- deps: any[],
61
- ): TResult {
62
- const [reducerState, , subscribe] = useContext(ReducerContext);
63
- const prev = useRef(select(reducerState.current));
64
- const [state, setState] = useState(prev.current);
65
-
66
- useEffect(() => {
67
- const value = select(reducerState.current);
68
- if (!shallowEqual<any>(prev.current, value)) {
69
- prev.current = value;
70
- setState(value);
71
- }
72
-
73
- return subscribe((state: State) => {
74
- const newValue = select(state);
75
- if (shallowEqual<any>(newValue, prev.current)) {
76
- return;
77
- }
78
-
79
- prev.current = newValue;
80
- setState(newValue);
81
- });
82
-
83
- // eslint-disable-next-line react-hooks/exhaustive-deps
84
- }, []);
85
-
86
- return state;
87
- }
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
+ import { produce } from 'immer';
6
+
7
+ function reducer(state: State, action: Action) {
8
+ const result = produce(state, (draft) => ACTIONS[action.type](draft, action.data as any));
9
+ console.debug('[Chatify]: ', action, result);
10
+ return result;
11
+ }
12
+
13
+ export type Reducer = ReturnType<typeof useReducer>;
14
+
15
+ type Subscriber = (state: State) => void;
16
+
17
+ export function useReducer() {
18
+ const state = useRef(INITIAL_STATE);
19
+ const subscribers = useRef<Subscriber[]>([]);
20
+
21
+ const dispatch = useCallback((action: Action) => {
22
+ const newState = reducer(state.current, action);
23
+ if (state.current === newState) {
24
+ return;
25
+ }
26
+
27
+ state.current = newState;
28
+ subscribers.current.forEach((s) => s(newState));
29
+ }, []);
30
+
31
+ const subscribe = useCallback((subscriber: Subscriber) => {
32
+ subscribers.current.push(subscriber);
33
+ return () => {
34
+ subscribers.current = subscribers.current.filter((x) => x !== subscriber);
35
+ };
36
+ }, []);
37
+
38
+ return [state, dispatch, subscribe] as const;
39
+ }
40
+
41
+ const ReducerContext = React.createContext<Reducer>(null!);
42
+ export function ReducerProvider({ children }: PropsWithChildren<{}>) {
43
+ const reducer = useReducer();
44
+ return <ReducerContext.Provider value={reducer}>{children}</ReducerContext.Provider>;
45
+ }
46
+
47
+ export function useDispatch() {
48
+ return useContext(ReducerContext)[1];
49
+ }
50
+
51
+ export function useStateRef() {
52
+ const [reducerState] = useContext(ReducerContext);
53
+ return reducerState;
54
+ }
55
+
56
+ export function useSelect<TResult>(
57
+ select: (state: State) => TResult,
58
+
59
+ // TODO: make deps work
60
+ deps: any[],
61
+ ): TResult {
62
+ const [reducerState, , subscribe] = useContext(ReducerContext);
63
+ const prev = useRef(select(reducerState.current));
64
+ const [state, setState] = useState(prev.current);
65
+
66
+ useEffect(() => {
67
+ const value = select(reducerState.current);
68
+ if (!shallowEqual<any>(prev.current, value)) {
69
+ prev.current = value;
70
+ setState(value);
71
+ }
72
+
73
+ return subscribe((state: State) => {
74
+ const newValue = select(state);
75
+ if (shallowEqual<any>(newValue, prev.current)) {
76
+ return;
77
+ }
78
+
79
+ prev.current = newValue;
80
+ setState(newValue);
81
+ });
82
+
83
+ // eslint-disable-next-line react-hooks/exhaustive-deps
84
+ }, []);
85
+
86
+ return state;
87
+ }