quickblox-react-ui-kit 0.4.2-beta.6 → 0.4.2-beta.7

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.
@@ -0,0 +1,133 @@
1
+ declare module 'media-recorder-js' {
2
+ interface QBMediaRecorderConstructorProps {
3
+ /** Preferred MIME type */
4
+ mimeType?: string
5
+ workerPath?: string
6
+ /**
7
+ * The minimum number of milliseconds of data to return
8
+ * in a single Blob, fire 'ondataavaible' callback
9
+ * (isn't need to use with 'audio/wav' of 'audio/mp3')
10
+ *
11
+ * @default 1000
12
+ */
13
+ timeslice?: number
14
+ /**
15
+ * What to do with a muted input MediaStreamTrack,
16
+ * e.g. insert black frames/zero audio volume in the recording
17
+ * or ignore altogether
18
+ *
19
+ * @default true
20
+ */
21
+ ignoreMutedMedia?: boolean
22
+ /** Recording start event handler */
23
+ onstart?: VoidFunction
24
+ /** Recording stop event handler */
25
+ onstop?: (file: Blob) => void
26
+ /** Recording pause event handler */
27
+ onpause?: VoidFunction
28
+ /** Recording resume event handler */
29
+ onresume?: VoidFunction
30
+ /** Error event handler */
31
+ onerror?: (error: unknown) => void
32
+ /**
33
+ * `dataavailable` event handler.
34
+ * The Blob of recorded data is contained in this event (callback
35
+ * isn't supported if use 'audio/wav' of 'audio/mp3' for recording)
36
+ */
37
+ ondataavailable?: (event: { data: Blob }) => void
38
+ }
39
+
40
+ class QBMediaRecorder {
41
+ constructor(config: QBMediaRecorderConstructorProps)
42
+
43
+ /**
44
+ * Switch recording Blob objects to the specified
45
+ * MIME type if `MediaRecorder` support it.
46
+ */
47
+ toggleMimeType(mimeType: string): void
48
+
49
+ /**
50
+ * Returns current `MediaRecorder` state
51
+ */
52
+ getState(): 'inactive' | 'recording' | 'paused'
53
+
54
+ /**
55
+ * Starts recording a stream.
56
+ * Fires `onstart` callback.
57
+ */
58
+ start(stream: MediaStream): void
59
+
60
+ /**
61
+ * Stops recording a stream
62
+ *
63
+ * @fires `onstop` callback and passing there Blob recorded
64
+ */
65
+ stop(): void
66
+
67
+ /** Pausing stream recording */
68
+ pause(): void
69
+
70
+ /** Resumes stream recording */
71
+ resume(): void
72
+
73
+ /**
74
+ * Change record source
75
+ */
76
+ change(stream: MediaStream): void
77
+
78
+ /**
79
+ * Create a file from blob and download as file.
80
+ * This method will call `stop` if recording is in progress.
81
+ *
82
+ * @param {string} filename Name of video file to be downloaded
83
+ * (default to `Date.now()`)
84
+ */
85
+ download(filename?: string): void
86
+
87
+ _getBlobRecorded(): Blob
88
+
89
+ callbacks: Pick<
90
+ QBMediaRecorderConstructorProps,
91
+ | 'onstart'
92
+ | 'onstop'
93
+ | 'onpause'
94
+ | 'onresume'
95
+ | 'ondataavailable'
96
+ | 'onerror'
97
+ >
98
+
99
+ /**
100
+ * Checks capability of recording in the environment.
101
+ * Checks `MediaRecorder`, `MediaRecorder.isTypeSupported` and `Blob`.
102
+ */
103
+ static isAvailable(): boolean
104
+
105
+ /**
106
+ * Checks if AudioContext API is available.
107
+ * Checks `window.AudioContext` or `window.webkitAudioContext`.
108
+ */
109
+ static isAudioContext(): boolean
110
+ /**
111
+ * The `QBMediaRecorder.isTypeSupported()` static method returns
112
+ * a Boolean which is true if the MIME type specified is one
113
+ * the user agent should be able to successfully record.
114
+ * @param mimeType The MIME media type to check.
115
+ *
116
+ * @returns true if the `MediaRecorder` implementation is capable of
117
+ * recording `Blob` objects for the specified MIME type. Recording may
118
+ * still fail if there are insufficient resources to support the
119
+ * recording and encoding process. If the value is false, the user
120
+ * agent is incapable of recording the specified format.
121
+ */
122
+
123
+ static isTypeSupported(mimeType: string): boolean
124
+
125
+ /**
126
+ * Return supported mime types
127
+ * @param type video or audio (dafault to 'video')
128
+ */
129
+ static getSupportedMimeTypes(type: 'audio' | 'video' = 'video'): string[]
130
+ }
131
+
132
+ export default QBMediaRecorder
133
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickblox-react-ui-kit",
3
- "version": "0.4.2-beta.6",
3
+ "version": "0.4.2-beta.7",
4
4
  "main": "dist/index-ui.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -13,6 +13,7 @@
13
13
  "qb-ai-rephrase": "^0.1.2",
14
14
  "qb-ai-translate": "^0.1.2",
15
15
  "quickblox": "^2.19.2",
16
+ "media-recorder-js": "^2.1.0",
16
17
  "react": "^18.2.0",
17
18
  "react-dom": "^18.2.0",
18
19
  "react-router-dom": "^6.11.1",
@@ -145,7 +145,7 @@ export class DefaultConfigurations {
145
145
  },
146
146
  configAIApi: {
147
147
  AIAnswerAssistWidgetConfig: {
148
- smartChatAssistantId: '6633a1300fea600001bd6e71',
148
+ smartChatAssistantId: '',
149
149
  organizationName: 'Quickblox',
150
150
  openAIModel: 'gpt-3.5-turbo',
151
151
  apiKey: '',
@@ -158,7 +158,7 @@ export class DefaultConfigurations {
158
158
  },
159
159
  },
160
160
  AITranslateWidgetConfig: {
161
- smartChatAssistantId: '6633a1300fea600001bd6e71',
161
+ smartChatAssistantId: '',
162
162
  organizationName: 'Quickblox',
163
163
  openAIModel: 'gpt-3.5-turbo',
164
164
  apiKey: '',
@@ -346,7 +346,7 @@ export default function MessageItem({
346
346
  }
347
347
  >
348
348
  {message.attachments && message.attachments.length > 0 ? (
349
- <div>
349
+ <>
350
350
  {message.attachments.map((attachment) => {
351
351
  return (
352
352
  <AttachmentBubble
@@ -355,7 +355,7 @@ export default function MessageItem({
355
355
  />
356
356
  );
357
357
  })}
358
- </div>
358
+ </>
359
359
  ) : (
360
360
  <TextBubble
361
361
  text={
@@ -90,11 +90,38 @@ export default function useDialogViewModel(
90
90
  });
91
91
  }
92
92
 
93
+ const getSender = async (sender_id: number) => {
94
+ const getUser: GetUsersByIdsUseCase = new GetUsersByIdsUseCase(
95
+ new UsersRepository(
96
+ currentContext.storage.LOCAL_DATA_SOURCE,
97
+ currentContext.storage.REMOTE_DATA_SOURCE,
98
+ ),
99
+ [sender_id],
100
+ );
101
+
102
+ let userEntity: UserEntity | undefined;
103
+
104
+ await getUser
105
+ .execute()
106
+ // eslint-disable-next-line promise/always-return
107
+ .then((data) => {
108
+ // eslint-disable-next-line prefer-destructuring
109
+ userEntity = data[0];
110
+ })
111
+ .catch((e) => {
112
+ console.log('have ERROR get users :', JSON.stringify(e));
113
+ });
114
+
115
+ return userEntity;
116
+ };
117
+
93
118
  async function getMessages(currentPagination?: Pagination) {
94
119
  setLoading(true);
95
120
 
96
121
  let participants: Array<number> = [];
97
122
  let userDictionary: Record<number, UserEntity> = {};
123
+ let userMissingDictionary: Record<number, UserEntity> = {};
124
+ let messagesDialog: MessageEntity[] = [];
98
125
 
99
126
  if (dialog?.type === DialogType.group) {
100
127
  participants = (dialog as GroupDialogEntity).participantIds;
@@ -124,7 +151,6 @@ export default function useDialogViewModel(
124
151
  return obj;
125
152
  }, {});
126
153
 
127
- setLoading(false);
128
154
  setError('');
129
155
  })
130
156
  .catch((e) => {
@@ -152,88 +178,84 @@ export default function useDialogViewModel(
152
178
  } messages:${JSON.stringify(data)}`,
153
179
  );
154
180
 
155
- const ResultMessages = data.ResultData.map((message) => {
156
- const obj = { ...message };
157
-
158
- console.log('have sender id:', message.sender_id);
159
-
160
- if (userDictionary) {
161
- obj.sender = userDictionary[message.sender_id];
162
- if (
163
- obj.sender &&
164
- obj.sender.full_name &&
165
- regex &&
166
- !regex.test(obj.sender.full_name)
167
- ) {
168
- obj.sender.full_name = 'Unknown';
169
- }
170
- }
171
-
172
- return obj;
173
- });
174
-
175
- console.log(`result messages:${JSON.stringify(ResultMessages)}`);
176
- // setMessages(ResultMessages);
177
- setMessages((prevState) => {
178
- const newItems: MessageEntity[] =
179
- currentPagination === undefined ||
180
- currentPagination?.getCurrentPage() === 0
181
- ? [...ResultMessages]
182
- : [...prevState, ...ResultMessages];
183
-
184
- return newItems;
185
- });
186
- // eslint-disable-next-line promise/always-return
187
- // if (
188
- // dialog?.type === DialogType.private ||
189
- // dialog?.type === DialogType.group
190
- // ) {
191
- // const updDialog = { ...dialog };
192
- //
193
- // updDialog.unreadMessageCount = 0;
194
- // setDialog(updDialog);
195
- //
196
- // informDataSources(updDialog);
197
- //
198
- // }
199
- setLoading(false);
181
+ messagesDialog = data.ResultData;
200
182
  setPagination(data.CurrentPagination);
201
183
  setError('');
202
184
  })
203
185
  .catch((e) => {
204
- console.log('have ERROR get users :', JSON.stringify(e));
186
+ console.log('have ERROR get messages :', JSON.stringify(e));
205
187
  setLoading(false);
206
188
  setError((e as unknown as Error).message);
207
189
  });
208
- //
209
190
 
210
- console.log('EXECUTE USE CASE MessagesViewModelWithMockUseCase EXECUTED');
211
- }
191
+ //
212
192
 
213
- const getSender = async (sender_id: number) => {
214
- const getUser: GetUsersByIdsUseCase = new GetUsersByIdsUseCase(
215
- new UsersRepository(
216
- currentContext.storage.LOCAL_DATA_SOURCE,
217
- currentContext.storage.REMOTE_DATA_SOURCE,
218
- ),
219
- [sender_id],
193
+ const senderIds = Array.from(
194
+ new Set(messagesDialog.map((msg) => msg.sender_id)),
220
195
  );
196
+ const missingSenderIds = senderIds.filter((id) => !(id in userDictionary));
221
197
 
222
- let userEntity: UserEntity | undefined;
198
+ const getMissingSenderUsersFromDialogByIdsUseCase: GetUsersByIdsUseCase =
199
+ new GetUsersByIdsUseCase(
200
+ new UsersRepository(LOCAL_DATA_SOURCE, REMOTE_DATA_SOURCE),
201
+ missingSenderIds,
202
+ );
223
203
 
224
- await getUser
204
+ await getMissingSenderUsersFromDialogByIdsUseCase
225
205
  .execute()
226
206
  // eslint-disable-next-line promise/always-return
227
207
  .then((data) => {
228
- // eslint-disable-next-line prefer-destructuring
229
- userEntity = data[0];
208
+ userMissingDictionary = data.reduce((acc, item) => {
209
+ const obj = acc;
210
+
211
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
212
+ // @ts-ignore
213
+ acc[item.id] = item;
214
+
215
+ return obj;
216
+ }, {});
217
+
218
+ setError('');
230
219
  })
231
220
  .catch((e) => {
232
- console.log('have ERROR get users :', JSON.stringify(e));
221
+ console.log('have ERROR get missing users :', JSON.stringify(e));
222
+ setLoading(false);
223
+ setError((e as unknown as Error).message);
233
224
  });
234
225
 
235
- return userEntity;
236
- };
226
+ userDictionary = { ...userDictionary, ...userMissingDictionary };
227
+
228
+ const ResultMessages = messagesDialog.map((message) => {
229
+ const obj = { ...message };
230
+
231
+ if (userDictionary) {
232
+ obj.sender = userDictionary[message.sender_id];
233
+ if (
234
+ obj.sender &&
235
+ obj.sender.full_name &&
236
+ regex &&
237
+ !regex.test(obj.sender.full_name)
238
+ ) {
239
+ obj.sender.full_name = 'Unknown';
240
+ }
241
+ }
242
+
243
+ return obj;
244
+ });
245
+
246
+ console.log(`result messages:${JSON.stringify(ResultMessages)}`);
247
+ setMessages((prevState) => {
248
+ const newItems: MessageEntity[] =
249
+ currentPagination === undefined ||
250
+ currentPagination?.getCurrentPage() === 0
251
+ ? [...ResultMessages]
252
+ : [...prevState, ...ResultMessages];
253
+
254
+ return newItems;
255
+ });
256
+ setLoading(false);
257
+ console.log('EXECUTE USE CASE MessagesViewModelWithMockUseCase EXECUTED');
258
+ }
237
259
 
238
260
  const dialogUpdateHandler = (dialogInfo: DialogEventInfo) => {
239
261
  console.log('call dialogUpdateHandler in useDialogViewModel');
@@ -157,6 +157,27 @@ export default function useDialogListViewModel(
157
157
  });
158
158
  }
159
159
 
160
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
161
+ function informDataSources(item: DialogEntity) {
162
+ const updateCurrentDialogInDataSourceUseCase: UpdateCurrentDialogInDataSourceUseCase =
163
+ new UpdateCurrentDialogInDataSourceUseCase(
164
+ new DialogsRepository(
165
+ currentContext.storage.LOCAL_DATA_SOURCE,
166
+ remoteDataSourceMock,
167
+ ),
168
+ item as GroupDialogEntity,
169
+ QBConfig,
170
+ );
171
+
172
+ updateCurrentDialogInDataSourceUseCase.execute().catch((e) => {
173
+ console.log(
174
+ 'Error updateCurrentDialogInDataSourceUseCase: ',
175
+ stringifyError(e),
176
+ );
177
+ throw new Error(stringifyError(e));
178
+ });
179
+ }
180
+
160
181
  const dialogUpdateHandler = (dialogInfo: DialogEventInfo) => {
161
182
  console.log('call dialogUpdateHandler in useDialogListView:', dialogInfo);
162
183
  if (
@@ -256,7 +277,12 @@ export default function useDialogListViewModel(
256
277
  setDialogs((prevDialogs) => {
257
278
  const newDialogs = prevDialogs.map((dialog) => {
258
279
  if (dialog.id === dialogInfo.dialogInfo?.id) {
259
- return dialogInfo.dialogInfo as PublicDialogEntity;
280
+ const updatedDialogInfo = {
281
+ ...dialogInfo.dialogInfo,
282
+ unreadMessageCount: 0,
283
+ };
284
+
285
+ return updatedDialogInfo as PublicDialogEntity;
260
286
  }
261
287
 
262
288
  return dialog;
@@ -270,12 +296,6 @@ export default function useDialogListViewModel(
270
296
  new Date(a.updatedAt).getTime();
271
297
  });
272
298
 
273
- // const sortedData = [...newDialogs].sort((a, b) => {
274
- // return (
275
- // new Date(b.lastMessage.dateSent).getTime() - new Date(a.lastMessage.dateSent).getTime()
276
- // );
277
- // });
278
-
279
299
  return sortedData;
280
300
  });
281
301
  }
@@ -534,27 +554,6 @@ export default function useDialogListViewModel(
534
554
  return Promise.resolve(resultEnity);
535
555
  };
536
556
 
537
- // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
538
- function informDataSources(item: DialogEntity) {
539
- const updateCurrentDialogInDataSourceUseCase: UpdateCurrentDialogInDataSourceUseCase =
540
- new UpdateCurrentDialogInDataSourceUseCase(
541
- new DialogsRepository(
542
- currentContext.storage.LOCAL_DATA_SOURCE,
543
- remoteDataSourceMock,
544
- ),
545
- item as GroupDialogEntity,
546
- QBConfig,
547
- );
548
-
549
- updateCurrentDialogInDataSourceUseCase.execute().catch((e) => {
550
- console.log(
551
- 'Error updateCurrentDialogInDataSourceUseCase: ',
552
- stringifyError(e),
553
- );
554
- throw new Error(stringifyError(e));
555
- });
556
- }
557
-
558
557
  return {
559
558
  get entity(): DialogEntity {
560
559
  return newDialog as DialogEntity;
@@ -435,32 +435,34 @@ const QuickBloxUIKitDesktopLayout: React.FC<
435
435
  />
436
436
  ) : null
437
437
  }
438
- renderDialogList={(handleSelectDialog) =>
439
- // eslint-disable-next-line no-nested-ternary
440
- dialogsViewModel?.loading ? (
441
- <div
442
- className="dialog-list__loader-container"
443
- style={{
444
- display: 'flex',
445
- flexDirection: 'row',
446
- alignItems: 'center',
447
- justifyContent: 'center',
448
- }}
449
- >
450
- <Loader size="md" className="dialog-list__loader" />
451
- </div>
452
- ) : searchedDialogs.length > 0 ? (
453
- searchedDialogs.map((dlg, index) =>
454
- renderDialogItem(dlg, index, handleSelectDialog),
455
- )
456
- ) : (
457
- <Placeholder
458
- icon={<ChatSvg />}
459
- text="There are no dialogs."
460
- className="dialog-empty-chat-placeholder"
461
- />
462
- )
463
- }
438
+ renderDialogList={(handleSelectDialog) => (
439
+ <>
440
+ {dialogsViewModel?.loading && (
441
+ <div
442
+ className="dialog-list__loader-container"
443
+ style={{
444
+ display: 'flex',
445
+ flexDirection: 'row',
446
+ alignItems: 'center',
447
+ justifyContent: 'center',
448
+ }}
449
+ >
450
+ <Loader size="md" className="dialog-list__loader" />
451
+ </div>
452
+ )}
453
+ {searchedDialogs.length > 0 ? (
454
+ searchedDialogs.map((dlg, index) =>
455
+ renderDialogItem(dlg, index, handleSelectDialog),
456
+ )
457
+ ) : (
458
+ <Placeholder
459
+ icon={<ChatSvg />}
460
+ text="There are no dialogs."
461
+ className="dialog-empty-chat-placeholder"
462
+ />
463
+ )}
464
+ </>
465
+ )}
464
466
  />
465
467
  ) : null
466
468
  }
@@ -25,7 +25,9 @@
25
25
  height: 24px;
26
26
 
27
27
  &--color{
28
- fill: var(--main-elements)
28
+ fill: var(--main-elements);
29
+ width: 24px;
30
+ height: 24px;
29
31
  }
30
32
  }
31
33
  }
package/src/QBconfig.ts CHANGED
@@ -10,7 +10,7 @@ export const QBConfig: QBUIKitConfig = {
10
10
  },
11
11
  configAIApi: {
12
12
  AIAnswerAssistWidgetConfig: {
13
- smartChatAssistantId: '6633a1300fea600001bd6e71',
13
+ smartChatAssistantId: '',
14
14
  organizationName: 'Quickblox',
15
15
  openAIModel: 'gpt-3.5-turbo',
16
16
  apiKey: '',
@@ -23,7 +23,7 @@ export const QBConfig: QBUIKitConfig = {
23
23
  },
24
24
  },
25
25
  AITranslateWidgetConfig: {
26
- smartChatAssistantId: '6633a1300fea600001bd6e71',
26
+ smartChatAssistantId: '',
27
27
  organizationName: 'Quickblox',
28
28
  openAIModel: 'gpt-3.5-turbo',
29
29
  apiKey: '',