agora-appbuilder-core 4.1.8-beta.1 → 4.1.8-beta.11

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 (26) hide show
  1. package/package.json +2 -2
  2. package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -1
  3. package/template/agora-rn-uikit/src/Rtc/Join.tsx +18 -9
  4. package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
  5. package/template/defaultConfig.js +4 -3
  6. package/template/global.d.ts +1 -0
  7. package/template/package.json +1 -1
  8. package/template/src/atoms/TextInput.tsx +3 -0
  9. package/template/src/components/Controls.tsx +17 -15
  10. package/template/src/components/common/GenericPopup.tsx +0 -1
  11. package/template/src/components/common/data-table.tsx +42 -15
  12. package/template/src/components/controls/useControlPermissionMatrix.tsx +7 -4
  13. package/template/src/components/recordings/RecordingItemRow.tsx +289 -0
  14. package/template/src/components/recordings/RecordingsDateTable.tsx +99 -25
  15. package/template/src/components/recordings/TextTrackItemRow.tsx +120 -0
  16. package/template/src/components/room-info/useRoomInfo.tsx +1 -0
  17. package/template/src/components/{stt-transcript/STTTranscriptTable.tsx → text-tracks/TextTracksTable.tsx} +61 -50
  18. package/template/src/components/{stt-transcript/ViewSTTTranscriptModal.tsx → text-tracks/ViewTextTracksModal.tsx} +7 -7
  19. package/template/src/components/text-tracks/useFetchSTTTranscript.tsx +262 -0
  20. package/template/src/language/default-labels/videoCallScreenLabels.ts +7 -7
  21. package/template/src/pages/VideoCall.tsx +1 -1
  22. package/template/src/subComponents/ChatInput.tsx +72 -1
  23. package/template/src/subComponents/recording/useRecording.tsx +19 -4
  24. package/template/src/utils/useCreateRoom.ts +1 -1
  25. package/template/src/utils/useJoinRoom.ts +5 -1
  26. package/template/src/components/stt-transcript/useFetchSTTTranscript.tsx +0 -193
@@ -1,30 +1,67 @@
1
1
  import React, {useState, useEffect} from 'react';
2
2
  import {View, Text} from 'react-native';
3
- import {style} from './style';
4
- import {RTableHeader, RTableBody, RTableFooter} from './recording-table';
5
- import {useRecording} from '../../subComponents/recording/useRecording';
3
+ import {
4
+ APIStatus,
5
+ FetchRecordingData,
6
+ useRecording,
7
+ } from '../../subComponents/recording/useRecording';
6
8
  import events from '../../rtm-events-api';
7
9
  import {EventNames} from '../../rtm-events';
10
+ import {style, TableBody, TableHeader} from '../common/data-table';
11
+ import Loading from '../../subComponents/Loading';
12
+ import ImageIcon from '../../atoms/ImageIcon';
13
+ import RecordingItemRow from './RecordingItemRow';
14
+ import GenericPopup from '../common/GenericPopup';
15
+ import {downloadS3Link} from '../../utils/common';
16
+ import {useControlPermissionMatrix} from '../controls/useControlPermissionMatrix';
17
+
18
+ function EmptyRecordingState() {
19
+ return (
20
+ <View style={style.infotextContainer}>
21
+ <View>
22
+ <ImageIcon
23
+ iconType="plain"
24
+ name="info"
25
+ tintColor={'#777777'}
26
+ iconSize={32}
27
+ />
28
+ </View>
29
+ <View>
30
+ <Text style={[style.infoText, style.pt10, style.pl10]}>
31
+ No recording found for this meeting
32
+ </Text>
33
+ </View>
34
+ </View>
35
+ );
36
+ }
37
+
38
+ const defaultPageNumber = 1;
8
39
 
9
40
  function RecordingsDateTable(props) {
10
- const [state, setState] = React.useState({
41
+ const [state, setState] = React.useState<{
42
+ status: APIStatus;
43
+ data: {
44
+ recordings: FetchRecordingData['recordings'];
45
+ pagination: FetchRecordingData['pagination'];
46
+ };
47
+ error: Error;
48
+ }>({
11
49
  status: 'idle',
12
50
  data: {
13
- pagination: {},
14
51
  recordings: [],
52
+ pagination: {total: 0, limit: 10, page: defaultPageNumber},
15
53
  },
16
54
  error: null,
17
55
  });
18
- const {
19
- status,
20
- data: {pagination, recordings},
21
- error,
22
- } = state;
56
+
57
+ const [currentPage, setCurrentPage] = useState(defaultPageNumber);
23
58
 
24
59
  const {fetchRecordings} = useRecording();
60
+ const canAccessAllTextTracks =
61
+ useControlPermissionMatrix('viewAllTextTracks');
25
62
 
26
- const defaultPageNumber = 1;
27
- const [currentPage, setCurrentPage] = useState(defaultPageNumber);
63
+ // message for any download‐error popup
64
+ const [errorSnack, setErrorSnack] = React.useState<string | undefined>();
28
65
 
29
66
  const onRecordingDeleteCallback = () => {
30
67
  setCurrentPage(defaultPageNumber);
@@ -38,7 +75,7 @@ function RecordingsDateTable(props) {
38
75
  };
39
76
  }, []);
40
77
 
41
- const getRecordings = pageNumber => {
78
+ const getRecordings = (pageNumber: number) => {
42
79
  setState(prev => ({...prev, status: 'pending'}));
43
80
  fetchRecordings(pageNumber).then(
44
81
  response =>
@@ -47,8 +84,13 @@ function RecordingsDateTable(props) {
47
84
  status: 'resolved',
48
85
  data: {
49
86
  recordings: response?.recordings || [],
50
- pagination: response?.pagination || {},
87
+ pagination: response?.pagination || {
88
+ total: 0,
89
+ limit: 10,
90
+ page: defaultPageNumber,
91
+ },
51
92
  },
93
+ error: null,
52
94
  })),
53
95
  error => setState(prev => ({...prev, status: 'rejected', error})),
54
96
  );
@@ -58,26 +100,58 @@ function RecordingsDateTable(props) {
58
100
  getRecordings(currentPage);
59
101
  }, [currentPage]);
60
102
 
61
- if (status === 'rejected') {
103
+ if (state.status === 'rejected') {
62
104
  return (
63
105
  <Text style={[style.ttime, style.pv10, style.ph20]}>
64
- {error?.message}
106
+ {state.error?.message}
65
107
  </Text>
66
108
  );
67
109
  }
110
+ const onTextTrackDownload = (textTrackLink: string) => {
111
+ downloadS3Link(textTrackLink).catch((err: Error) => {
112
+ setErrorSnack(err.message || 'Download failed');
113
+ });
114
+ };
115
+
116
+ const headers = canAccessAllTextTracks
117
+ ? ['', 'Date/Time', 'Duration', 'Actions']
118
+ : ['Date/Time', 'Duration', 'Actions'];
119
+
68
120
  return (
69
121
  <View style={style.ttable}>
70
- <RTableHeader />
71
- <RTableBody
72
- status={status}
73
- recordings={recordings}
74
- onDeleteAction={props?.onDeleteAction}
122
+ <TableHeader
123
+ columns={headers}
124
+ firstCellStyle={canAccessAllTextTracks ? style.thIconCell : {}}
125
+ lastCellStyle={style.alignCellToRight}
75
126
  />
76
- <RTableFooter
77
- currentPage={currentPage}
78
- setCurrentPage={setCurrentPage}
79
- pagination={pagination}
127
+ <TableBody
128
+ status={state.status}
129
+ items={state.data.recordings}
130
+ loadingComponent={
131
+ <Loading background="transparent" text="Fetching recordingss.." />
132
+ }
133
+ renderRow={item => (
134
+ <RecordingItemRow
135
+ key={item.id}
136
+ item={item}
137
+ onDeleteAction={props?.onDeleteAction}
138
+ onTextTrackDownload={onTextTrackDownload}
139
+ showTextTracks={canAccessAllTextTracks}
140
+ />
141
+ )}
142
+ emptyComponent={<EmptyRecordingState />}
80
143
  />
144
+ {/** ERROR POPUP **/}
145
+ {errorSnack && (
146
+ <GenericPopup
147
+ title="Error"
148
+ variant="error"
149
+ message={errorSnack}
150
+ visible={true}
151
+ setVisible={() => setErrorSnack(undefined)}
152
+ onConfirm={() => setErrorSnack(undefined)}
153
+ />
154
+ )}
81
155
  </View>
82
156
  );
83
157
  }
@@ -0,0 +1,120 @@
1
+ import React from 'react';
2
+ import {View, Text, TouchableOpacity} from 'react-native';
3
+ import IconButtonWithToolTip from '../../atoms/IconButton';
4
+ import Tooltip from '../../atoms/Tooltip';
5
+ import Clipboard from '../../subComponents/Clipboard';
6
+ import Spacer from '../../atoms/Spacer';
7
+ import PlatformWrapper from '../../utils/PlatformWrapper';
8
+ import {FetchSTTTranscriptResponse} from '../text-tracks/useFetchSTTTranscript';
9
+ import {style} from '../common/data-table';
10
+ import ImageIcon from '../../atoms/ImageIcon';
11
+
12
+ interface TextTrackItemRowProps {
13
+ item: FetchSTTTranscriptResponse['stts'][0];
14
+ onTextTrackDownload: (link: string) => void;
15
+ }
16
+
17
+ export default function TextTrackItemRow({
18
+ item,
19
+ onTextTrackDownload,
20
+ }: TextTrackItemRowProps) {
21
+ const textTrackStatus = item.status;
22
+
23
+ return (
24
+ <View style={style.td} key={item.id}>
25
+ {!item.download_url ? (
26
+ <View style={[style.tactions, {marginTop: 0}]}>
27
+ {textTrackStatus === 'STOPPING' ||
28
+ textTrackStatus === 'STARTED' ||
29
+ (textTrackStatus === 'INPROGRESS' && !item?.download_url) ? (
30
+ <Text style={style.placeHolder}>
31
+ {'The link will be generated once the meeting ends'}
32
+ </Text>
33
+ ) : (
34
+ <Text style={style.placeHolder}>{'No text-tracks found'}</Text>
35
+ )}
36
+ </View>
37
+ ) : item?.download_url?.length > 0 ? (
38
+ <View style={style.tactions}>
39
+ <View>
40
+ {item?.download_url?.map((link: string, i: number) => (
41
+ <View
42
+ key={i}
43
+ style={[
44
+ style.tactions,
45
+ //if stts contains multiple parts then we need to add some space each row
46
+ i >= 1 ? {marginTop: 8} : {},
47
+ ]}>
48
+ <View>
49
+ <IconButtonWithToolTip
50
+ hoverEffect={true}
51
+ hoverEffectStyle={style.iconButtonHoverEffect}
52
+ containerStyle={style.iconButton}
53
+ iconProps={{
54
+ name: 'download',
55
+ iconType: 'plain',
56
+ iconSize: 20,
57
+ tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
58
+ }}
59
+ onPress={() => {
60
+ onTextTrackDownload && onTextTrackDownload(link);
61
+ }}
62
+ />
63
+ </View>
64
+ <View style={[style.pl10]}>
65
+ <Tooltip
66
+ isClickable
67
+ placement="left"
68
+ toolTipMessage="Link Copied"
69
+ onPress={() => {
70
+ Clipboard.setString(link);
71
+ }}
72
+ toolTipIcon={
73
+ <>
74
+ <ImageIcon
75
+ iconType="plain"
76
+ name="tick-fill"
77
+ tintColor={$config.SEMANTIC_SUCCESS}
78
+ iconSize={20}
79
+ />
80
+ <Spacer size={8} horizontal={true} />
81
+ </>
82
+ }
83
+ fontSize={12}
84
+ renderContent={() => {
85
+ return (
86
+ <PlatformWrapper>
87
+ {(isHovered: boolean) => (
88
+ <TouchableOpacity
89
+ style={[
90
+ isHovered ? style.iconButtonHoverEffect : {},
91
+ style.iconShareLink,
92
+ ]}
93
+ onPress={() => {
94
+ Clipboard.setString(link);
95
+ }}>
96
+ <ImageIcon
97
+ iconType="plain"
98
+ name="copy-link"
99
+ iconSize={20}
100
+ tintColor={$config.SECONDARY_ACTION_COLOR}
101
+ />
102
+ </TouchableOpacity>
103
+ )}
104
+ </PlatformWrapper>
105
+ );
106
+ }}
107
+ />
108
+ </View>
109
+ </View>
110
+ ))}
111
+ </View>
112
+ </View>
113
+ ) : (
114
+ <View style={(style.tactions, {marginTop: 0})}>
115
+ <Text style={style.placeHolder}>No text-tracks found</Text>
116
+ </View>
117
+ )}
118
+ </View>
119
+ );
120
+ }
@@ -63,6 +63,7 @@ export interface RoomData {
63
63
  rtmToken?: string;
64
64
  encryptionSecret?: string;
65
65
  encryptionSecretSalt?: Uint8Array;
66
+ encryptionMode?: number;
66
67
  screenShareUid?: string;
67
68
  screenShareToken?: string;
68
69
  agents?: AIAgentInterface[];
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, {useEffect} from 'react';
2
2
  import {View, Text, TouchableOpacity} from 'react-native';
3
3
  import Tooltip from '../../atoms/Tooltip';
4
4
  import Clipboard from '../../subComponents/Clipboard';
@@ -22,20 +22,24 @@ import PlatformWrapper from '../../utils/PlatformWrapper';
22
22
 
23
23
  const headers = ['Date', 'Time', 'Status', 'Actions'];
24
24
 
25
- interface STTItemRowProps {
25
+ interface TextTrackItemRowProps {
26
26
  item: FetchSTTTranscriptResponse['stts'][0];
27
27
  onDeleteAction: (id: string) => void;
28
28
  onDownloadAction: (link: string) => void;
29
29
  }
30
30
 
31
- function STTItemRow({item, onDeleteAction, onDownloadAction}: STTItemRowProps) {
31
+ function TextTrackItemRow({
32
+ item,
33
+ onDeleteAction,
34
+ onDownloadAction,
35
+ }: TextTrackItemRowProps) {
32
36
  const [date, time] = getFormattedDateTime(item.created_at);
33
- const sttStatus = item.status;
37
+ const textTrackStatus = item.status;
34
38
 
35
39
  if (
36
- sttStatus === 'STOPPING' ||
37
- sttStatus === 'STARTED' ||
38
- (sttStatus === 'INPROGRESS' && !item?.download_url)
40
+ textTrackStatus === 'STOPPING' ||
41
+ textTrackStatus === 'STARTED' ||
42
+ (textTrackStatus === 'INPROGRESS' && !item?.download_url)
39
43
  ) {
40
44
  return (
41
45
  <View key={item.id} style={style.pt12}>
@@ -63,12 +67,14 @@ function STTItemRow({item, onDeleteAction, onDownloadAction}: STTItemRowProps) {
63
67
  <Text style={style.ttime}>{time}</Text>
64
68
  </View>
65
69
  <View style={[style.td]}>
66
- <Text style={style.ttime}>{capitalizeFirstLetter(sttStatus)}</Text>
70
+ <Text style={style.ttime}>
71
+ {capitalizeFirstLetter(textTrackStatus)}
72
+ </Text>
67
73
  </View>
68
74
  <View style={style.td}>
69
75
  {!item.download_url ? (
70
76
  <View style={[style.tactions, {marginTop: 0}]}>
71
- <Text style={style.placeHolder}>{'No transcripts found'}</Text>
77
+ <Text style={style.placeHolder}>{'No text-tracks found'}</Text>
72
78
  </View>
73
79
  ) : item?.download_url?.length > 0 ? (
74
80
  <View style={style.tactions}>
@@ -165,7 +171,7 @@ function STTItemRow({item, onDeleteAction, onDownloadAction}: STTItemRowProps) {
165
171
  </View>
166
172
  ) : (
167
173
  <View style={(style.tactions, {marginTop: 0})}>
168
- <Text style={style.placeHolder}>No transcripts found</Text>
174
+ <Text style={style.placeHolder}>No text-tracks found</Text>
169
175
  </View>
170
176
  )}
171
177
  </View>
@@ -173,7 +179,7 @@ function STTItemRow({item, onDeleteAction, onDownloadAction}: STTItemRowProps) {
173
179
  );
174
180
  }
175
181
 
176
- function EmptyTranscriptState() {
182
+ function EmptyTextTrackState() {
177
183
  return (
178
184
  <View style={style.infotextContainer}>
179
185
  <View>
@@ -186,49 +192,50 @@ function EmptyTranscriptState() {
186
192
  </View>
187
193
  <View>
188
194
  <Text style={[style.infoText, style.pt10, style.pl10]}>
189
- No transcripts found for this meeting
195
+ No text-tracks found for this meeting
190
196
  </Text>
191
197
  </View>
192
198
  </View>
193
199
  );
194
200
  }
195
201
 
196
- function ErrorTranscriptState(message: any) {
202
+ function ErrorTextTrackState({message}: {message: string}) {
197
203
  return <Text style={[style.ttime, style.pv10, style.ph20]}>{message}</Text>;
198
204
  }
199
205
 
200
- function STTTranscriptTable() {
206
+ function TextTracksTable() {
207
+ const {getSTTs, sttState, currentPage, setCurrentPage, deleteTranscript} =
208
+ useFetchSTTTranscript();
209
+
201
210
  const {
202
211
  status,
203
- stts,
204
- pagination,
205
- error,
206
- currentPage,
207
- setCurrentPage,
208
- deleteTranscript,
209
- } = useFetchSTTTranscript();
212
+ data: {stts, pagination},
213
+ error: fetchTranscriptError,
214
+ } = sttState;
210
215
 
211
- // id of STT transcript to delete
212
- const [sttIdToDelete, setSTTIdToDelete] = React.useState<string | undefined>(
213
- undefined,
214
- );
216
+ useEffect(() => {
217
+ getSTTs(currentPage);
218
+ }, [currentPage, getSTTs]);
215
219
 
216
- // message for any download‐error popup
217
- const [downloadError, setDownloadError] = React.useState<
220
+ // id of text-tracj to delete
221
+ const [textTrackIdToDelete, setTextTrackIdToDelete] = React.useState<
218
222
  string | undefined
219
- >();
223
+ >(undefined);
224
+
225
+ // message for any download‐error popup
226
+ const [errorSnack, setErrorSnack] = React.useState<string | undefined>();
220
227
 
221
228
  if (status === 'rejected') {
222
- return <ErrorTranscriptState message={error?.message} />;
229
+ return <ErrorTextTrackState message={fetchTranscriptError?.message} />;
223
230
  }
224
231
 
225
- const onDeleteSTTRecord = async () => {
232
+ const onDeleteTextTrackRecord = async (trackId: string) => {
226
233
  try {
227
- await deleteTranscript(sttIdToDelete!);
234
+ await deleteTranscript(trackId!);
228
235
  } catch (err: any) {
229
- setDownloadError(err.message);
236
+ setErrorSnack(err.message);
230
237
  } finally {
231
- setSTTIdToDelete(undefined);
238
+ setTextTrackIdToDelete(undefined);
232
239
  }
233
240
  };
234
241
 
@@ -240,23 +247,23 @@ function STTTranscriptTable() {
240
247
  status={status}
241
248
  items={stts}
242
249
  loadingComponent={
243
- <Loading background="transparent" text="Fetching transcripts.." />
250
+ <Loading background="transparent" text="Fetching text-tracks.." />
244
251
  }
245
252
  renderRow={item => (
246
- <STTItemRow
253
+ <TextTrackItemRow
247
254
  key={item.id}
248
255
  item={item}
249
256
  onDeleteAction={id => {
250
- setSTTIdToDelete(id);
257
+ setTextTrackIdToDelete(id);
251
258
  }}
252
259
  onDownloadAction={link => {
253
260
  downloadS3Link(link).catch((err: Error) => {
254
- setDownloadError(err.message || 'Download failed');
261
+ setErrorSnack(err.message || 'Download failed');
255
262
  });
256
263
  }}
257
264
  />
258
265
  )}
259
- emptyComponent={<EmptyTranscriptState />}
266
+ emptyComponent={<EmptyTextTrackState />}
260
267
  />
261
268
  <TableFooter
262
269
  currentPage={currentPage}
@@ -264,32 +271,36 @@ function STTTranscriptTable() {
264
271
  pagination={pagination}
265
272
  />
266
273
  </View>
267
- {sttIdToDelete && (
274
+ {textTrackIdToDelete && (
268
275
  <GenericPopup
269
276
  title="Delete ? "
270
277
  variant="error"
271
- message="Are you sure want to delete the transcript ? This action can't be undone."
272
- visible={!!sttIdToDelete}
273
- setVisible={() => setSTTIdToDelete(undefined)}
274
- onConfirm={onDeleteSTTRecord}
278
+ message="Are you sure want to delete the text-track ? This action can't be undone."
279
+ visible={!!textTrackIdToDelete}
280
+ setVisible={() => setTextTrackIdToDelete(undefined)}
281
+ onConfirm={() => {
282
+ const idToDelete = textTrackIdToDelete;
283
+ setTextTrackIdToDelete(undefined);
284
+ onDeleteTextTrackRecord(idToDelete);
285
+ }}
275
286
  onCancel={() => {
276
- setSTTIdToDelete(undefined);
287
+ setTextTrackIdToDelete(undefined);
277
288
  }}
278
289
  />
279
290
  )}
280
291
  {/** DOWNLOAD ERROR POPUP **/}
281
- {downloadError && (
292
+ {errorSnack && (
282
293
  <GenericPopup
283
- title="Download Error"
294
+ title="Error"
284
295
  variant="error"
285
- message={downloadError}
296
+ message={errorSnack}
286
297
  visible={true}
287
- setVisible={() => setDownloadError(undefined)}
288
- onConfirm={() => setDownloadError(undefined)}
298
+ setVisible={() => setErrorSnack(undefined)}
299
+ onConfirm={() => setErrorSnack(undefined)}
289
300
  />
290
301
  )}
291
302
  </>
292
303
  );
293
304
  }
294
305
 
295
- export default STTTranscriptTable;
306
+ export default TextTracksTable;
@@ -1,29 +1,29 @@
1
1
  import React, {SetStateAction, Dispatch} from 'react';
2
2
  import {View, StyleSheet} from 'react-native';
3
3
  import {useString} from '../../utils/useString';
4
- import {sttModalTitleIntn} from '../../language/default-labels/videoCallScreenLabels';
4
+ import {textTrackModalTitleIntn} from '../../language/default-labels/videoCallScreenLabels';
5
5
  import GenericModal from '../common/GenericModal';
6
- import STTTranscriptTable from './STTTranscriptTable';
6
+ import TextTracksTable from './TextTracksTable';
7
7
 
8
- interface ViewSTTModalProps {
8
+ interface ViewTextTracksModalProps {
9
9
  setModalOpen: Dispatch<SetStateAction<boolean>>;
10
10
  }
11
11
 
12
- export default function ViewSTTTranscriptModal(props: ViewSTTModalProps) {
12
+ export default function ViewTextTracksModal(props: ViewTextTracksModalProps) {
13
13
  const {setModalOpen} = props;
14
14
 
15
- const sttModalTitle = useString(sttModalTitleIntn)();
15
+ const textTrackModalTitle = useString(textTrackModalTitleIntn)();
16
16
 
17
17
  return (
18
18
  <GenericModal
19
19
  visible={true}
20
20
  onRequestClose={() => setModalOpen(false)}
21
21
  showCloseIcon={true}
22
- title={sttModalTitle}
22
+ title={textTrackModalTitle}
23
23
  cancelable={false}
24
24
  contentContainerStyle={style.contentContainer}>
25
25
  <View style={style.fullBody}>
26
- <STTTranscriptTable />
26
+ <TextTracksTable />
27
27
  </View>
28
28
  </GenericModal>
29
29
  );