agora-appbuilder-core 2.3.0-beta.28 → 2.3.0-beta.29
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 +1 -1
- package/template/src/App.tsx +5 -3
- package/template/src/AppWrapper.tsx +3 -2
- package/template/src/atoms/TextInput.tsx +15 -13
- package/template/src/components/Chat.tsx +146 -139
- package/template/src/components/Controls.tsx +13 -27
- package/template/src/components/GridVideo.tsx +3 -3
- package/template/src/components/Navbar.tsx +192 -172
- package/template/src/components/ParticipantsView.tsx +94 -91
- package/template/src/components/Precall.tsx +3 -3
- package/template/src/components/RTMConfigure.tsx +3 -3
- package/template/src/components/SettingsView.tsx +4 -3
- package/template/src/components/Share.tsx +20 -8
- package/template/src/components/participants/ParticipantName.tsx +3 -3
- package/template/src/components/precall/joinCallBtn.tsx +4 -4
- package/template/src/components/precall/selectDevice.tsx +3 -3
- package/template/src/components/styles.ts +6 -4
- package/template/src/components/useWakeLock.tsx +3 -3
- package/template/src/pages/Create.tsx +3 -3
- package/template/src/pages/video-call/VideoCallScreen.tsx +5 -5
- package/template/src/subComponents/ChatBubble.tsx +3 -3
- package/template/src/subComponents/ChatContainer.tsx +49 -45
- package/template/src/subComponents/LayoutIconDropdown.tsx +3 -3
- package/template/src/subComponents/NetworkQualityPill.tsx +3 -3
- package/template/src/subComponents/OpenInNativeButton.tsx +3 -3
- package/template/src/subComponents/TextWithTooltip.tsx +16 -2
- package/template/src/subComponents/chat/ChatParticipants.tsx +41 -37
- package/template/src/subComponents/{toastConfig.tsx → useToastConfig.tsx} +27 -24
- package/template/src/utils/common.tsx +5 -0
package/package.json
CHANGED
package/template/src/App.tsx
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
12
|
import React, {useState} from 'react';
|
|
13
|
+
import {Platform} from 'react-native';
|
|
13
14
|
import Join from './pages/Join';
|
|
14
15
|
import VideoCall from './pages/VideoCall';
|
|
15
16
|
import Create from './pages/Create';
|
|
@@ -17,7 +18,7 @@ import {Route, Switch, Redirect} from './components/Router';
|
|
|
17
18
|
import PrivateRoute from './components/PrivateRoute';
|
|
18
19
|
import OAuth from './components/OAuth';
|
|
19
20
|
import StoreToken from './components/StoreToken';
|
|
20
|
-
import {shouldAuthenticate
|
|
21
|
+
import {shouldAuthenticate} from './utils/common';
|
|
21
22
|
import KeyboardManager from 'react-native-keyboard-manager';
|
|
22
23
|
// commented for v1 release
|
|
23
24
|
//import {CustomRoutesInterface, CUSTOM_ROUTES_PREFIX} from 'customization-api';
|
|
@@ -30,8 +31,9 @@ import {
|
|
|
30
31
|
} from './components/meeting-info/useMeetingInfo';
|
|
31
32
|
import {SetMeetingInfoProvider} from './components/meeting-info/useSetMeetingInfo';
|
|
32
33
|
import {ShareLinkProvider} from './components/useShareLink';
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
//hook can't be used in the outside react function calls. so directly checking the platform.
|
|
36
|
+
if (Platform.OS === 'ios') {
|
|
35
37
|
KeyboardManager.setEnable(true);
|
|
36
38
|
KeyboardManager.setEnableAutoToolbar(false);
|
|
37
39
|
KeyboardManager.setShouldShowToolbarPlaceholder(false);
|
|
@@ -18,7 +18,7 @@ import {SessionProvider} from './components/SessionContext';
|
|
|
18
18
|
import {ImageBackground, SafeAreaView, StatusBar, Platform} from 'react-native';
|
|
19
19
|
import ColorConfigure from './components/ColorConfigure';
|
|
20
20
|
import Toast from '../react-native-toast-message';
|
|
21
|
-
import
|
|
21
|
+
import useToastConfig from './subComponents/useToastConfig';
|
|
22
22
|
import {isValidReactComponent} from './utils/common';
|
|
23
23
|
import DimensionProvider from './components/dimension/DimensionProvider';
|
|
24
24
|
import Error from './components/common/Error';
|
|
@@ -37,6 +37,7 @@ const AppWrapper = (props: AppWrapperProps) => {
|
|
|
37
37
|
}
|
|
38
38
|
return React.Fragment;
|
|
39
39
|
});
|
|
40
|
+
const toastConfig = useToastConfig();
|
|
40
41
|
|
|
41
42
|
return (
|
|
42
43
|
<AppRoot>
|
|
@@ -48,7 +49,7 @@ const AppWrapper = (props: AppWrapperProps) => {
|
|
|
48
49
|
// @ts-ignore textAlign not supported by TS definitions but is applied to web regardless
|
|
49
50
|
style={[{flex: 1}, Platform.select({web: {textAlign: 'left'}})]}>
|
|
50
51
|
<StatusBar hidden={true} />
|
|
51
|
-
<Toast ref={(ref) => Toast.setRef(ref)} config={
|
|
52
|
+
<Toast ref={(ref) => Toast.setRef(ref)} config={toastConfig} />
|
|
52
53
|
<StorageProvider>
|
|
53
54
|
<GraphQLProvider>
|
|
54
55
|
<Router>
|
|
@@ -11,15 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import React, {useContext} from 'react';
|
|
13
13
|
import {TextInputProps, StyleSheet, TextInput, Platform} from 'react-native';
|
|
14
|
-
import {
|
|
14
|
+
import {useIsWebInternal} from '../utils/common';
|
|
15
15
|
import {textInput} from '../../theme.json';
|
|
16
16
|
import ColorContext from '../components/ColorContext';
|
|
17
17
|
|
|
18
|
-
const isWeb = useIsWeb();
|
|
19
|
-
|
|
20
18
|
const PrimaryButton = (props: TextInputProps) => {
|
|
21
19
|
const {primaryColor} = useContext(ColorContext);
|
|
22
20
|
const {style, ...otherProps} = props;
|
|
21
|
+
const styles = useStyles();
|
|
23
22
|
return (
|
|
24
23
|
<TextInput
|
|
25
24
|
style={[
|
|
@@ -38,13 +37,16 @@ const PrimaryButton = (props: TextInputProps) => {
|
|
|
38
37
|
|
|
39
38
|
export default PrimaryButton;
|
|
40
39
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
40
|
+
const useStyles = () => {
|
|
41
|
+
const isWebInternal = useIsWebInternal();
|
|
42
|
+
return StyleSheet.create({
|
|
43
|
+
textInput,
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
noOutline: isWebInternal() ? {outlineStyle: 'none'} : {},
|
|
46
|
+
textWrapFix: Platform.select({
|
|
47
|
+
ios: {
|
|
48
|
+
paddingVertical: 5,
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
};
|
|
@@ -23,7 +23,11 @@ import ChatParticipants from '../subComponents/chat/ChatParticipants';
|
|
|
23
23
|
import ColorContext from './ColorContext';
|
|
24
24
|
import {useChatNotification} from './chat-notification/useChatNotification';
|
|
25
25
|
import {useString} from '../utils/useString';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
useIsIOS,
|
|
28
|
+
isValidReactComponent,
|
|
29
|
+
useIsWebInternal,
|
|
30
|
+
} from '../utils/common';
|
|
27
31
|
import {useChatUIControl} from './chat-ui/useChatUIControl';
|
|
28
32
|
import {useCustomization} from 'customization-implementation';
|
|
29
33
|
import {UidType} from '../../agora-rn-uikit';
|
|
@@ -43,7 +47,7 @@ const Chat = (props?: ChatProps) => {
|
|
|
43
47
|
// commented for v1 release
|
|
44
48
|
// const groupChatLabel = useString('groupChatLabel')();
|
|
45
49
|
// const privateChatLabel = useString('privateChatLabel')();
|
|
46
|
-
const
|
|
50
|
+
const isWebInternal = useIsWebInternal();
|
|
47
51
|
const groupChatLabel = 'Group';
|
|
48
52
|
const privateChatLabel = 'Private';
|
|
49
53
|
const [dim, setDim] = useState([
|
|
@@ -138,12 +142,12 @@ const Chat = (props?: ChatProps) => {
|
|
|
138
142
|
}
|
|
139
143
|
return components;
|
|
140
144
|
});
|
|
141
|
-
|
|
145
|
+
const style = useStyle();
|
|
142
146
|
return (
|
|
143
147
|
<>
|
|
144
148
|
<View
|
|
145
149
|
style={
|
|
146
|
-
|
|
150
|
+
isWebInternal()
|
|
147
151
|
? !isSmall
|
|
148
152
|
? style.chatView
|
|
149
153
|
: style.chatViewNative
|
|
@@ -238,140 +242,143 @@ const Chat = (props?: ChatProps) => {
|
|
|
238
242
|
</>
|
|
239
243
|
);
|
|
240
244
|
};
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
245
|
+
|
|
246
|
+
const useStyle = () => {
|
|
247
|
+
const isIOS = useIsIOS();
|
|
248
|
+
return StyleSheet.create({
|
|
249
|
+
chatView: {
|
|
250
|
+
width: '20%',
|
|
251
|
+
minWidth: 200,
|
|
252
|
+
maxWidth: 300,
|
|
253
|
+
backgroundColor: $config.SECONDARY_FONT_COLOR,
|
|
254
|
+
flex: 1,
|
|
255
|
+
shadowColor: $config.PRIMARY_FONT_COLOR + '80',
|
|
256
|
+
shadowOpacity: 0.5,
|
|
257
|
+
shadowOffset: {width: -2, height: 1},
|
|
258
|
+
shadowRadius: 3,
|
|
259
|
+
},
|
|
260
|
+
chatViewNative: {
|
|
261
|
+
position: 'absolute',
|
|
262
|
+
zIndex: 5,
|
|
263
|
+
width: '100%',
|
|
264
|
+
height: '100%',
|
|
265
|
+
right: 0,
|
|
266
|
+
bottom: 0,
|
|
267
|
+
backgroundColor: $config.SECONDARY_FONT_COLOR,
|
|
268
|
+
},
|
|
269
|
+
heading: {
|
|
270
|
+
backgroundColor: $config.SECONDARY_FONT_COLOR,
|
|
271
|
+
width: 150,
|
|
272
|
+
height: '7%',
|
|
273
|
+
paddingLeft: 20,
|
|
274
|
+
flexDirection: 'row',
|
|
275
|
+
},
|
|
276
|
+
headingText: {
|
|
277
|
+
flex: 1,
|
|
278
|
+
paddingLeft: 5,
|
|
279
|
+
marginVertical: 'auto',
|
|
280
|
+
fontWeight: '700',
|
|
281
|
+
color: $config.PRIMARY_FONT_COLOR,
|
|
282
|
+
fontSize: 25,
|
|
283
|
+
alignSelf: 'center',
|
|
284
|
+
justifyContent: 'center',
|
|
285
|
+
},
|
|
286
|
+
chatNav: {
|
|
287
|
+
flexDirection: 'row',
|
|
288
|
+
height: '6%',
|
|
289
|
+
},
|
|
290
|
+
chatInputContainer: {
|
|
291
|
+
backgroundColor: $config.SECONDARY_FONT_COLOR,
|
|
292
|
+
paddingBottom: 10,
|
|
293
|
+
},
|
|
294
|
+
chatInputLineSeparator: {
|
|
295
|
+
backgroundColor: $config.PRIMARY_FONT_COLOR + '80',
|
|
296
|
+
width: '100%',
|
|
297
|
+
height: 1,
|
|
298
|
+
marginHorizontal: -20,
|
|
299
|
+
alignSelf: 'center',
|
|
300
|
+
opacity: 0.5,
|
|
301
|
+
marginBottom: 10,
|
|
302
|
+
},
|
|
303
|
+
groupActive: {
|
|
304
|
+
backgroundColor: $config.SECONDARY_FONT_COLOR,
|
|
305
|
+
flex: 1,
|
|
306
|
+
height: '100%',
|
|
307
|
+
textAlign: 'center',
|
|
308
|
+
alignItems: 'center',
|
|
309
|
+
justifyContent: 'center',
|
|
310
|
+
},
|
|
311
|
+
group: {
|
|
312
|
+
backgroundColor: $config.PRIMARY_FONT_COLOR + 22,
|
|
313
|
+
flex: 1,
|
|
314
|
+
height: '100%',
|
|
315
|
+
textAlign: 'center',
|
|
316
|
+
borderBottomRightRadius: 10,
|
|
317
|
+
alignItems: 'center',
|
|
318
|
+
justifyContent: 'center',
|
|
319
|
+
},
|
|
320
|
+
privateActive: {
|
|
321
|
+
backgroundColor: $config.SECONDARY_FONT_COLOR,
|
|
322
|
+
flex: 1,
|
|
323
|
+
height: '100%',
|
|
324
|
+
alignItems: 'center',
|
|
325
|
+
justifyContent: 'center',
|
|
326
|
+
},
|
|
327
|
+
private: {
|
|
328
|
+
backgroundColor: $config.PRIMARY_FONT_COLOR + 22,
|
|
329
|
+
flex: 1,
|
|
330
|
+
height: '100%',
|
|
331
|
+
borderBottomLeftRadius: 10,
|
|
332
|
+
alignItems: 'center',
|
|
333
|
+
justifyContent: 'center',
|
|
334
|
+
},
|
|
335
|
+
groupTextActive: {
|
|
336
|
+
marginVertical: 'auto',
|
|
337
|
+
fontWeight: '700',
|
|
338
|
+
textAlign: 'center',
|
|
339
|
+
fontSize: 16,
|
|
340
|
+
color: $config.PRIMARY_FONT_COLOR,
|
|
341
|
+
justifyContent: 'center',
|
|
342
|
+
paddingVertical: 5,
|
|
343
|
+
},
|
|
344
|
+
groupText: {
|
|
345
|
+
marginVertical: 'auto',
|
|
346
|
+
fontWeight: '700',
|
|
347
|
+
textAlign: 'center',
|
|
348
|
+
fontSize: 16,
|
|
349
|
+
paddingVertical: 5,
|
|
350
|
+
justifyContent: 'center',
|
|
351
|
+
color: $config.PRIMARY_FONT_COLOR + 50,
|
|
352
|
+
},
|
|
353
|
+
chatNotification: {
|
|
354
|
+
width: 20,
|
|
355
|
+
height: 20,
|
|
356
|
+
display: 'flex',
|
|
357
|
+
alignItems: 'center',
|
|
358
|
+
justifyContent: 'center',
|
|
359
|
+
backgroundColor: $config.PRIMARY_COLOR,
|
|
360
|
+
color: $config.SECONDARY_FONT_COLOR,
|
|
361
|
+
fontFamily: isIOS() ? 'Helvetica' : 'sans-serif',
|
|
362
|
+
borderRadius: 10,
|
|
363
|
+
position: 'absolute',
|
|
364
|
+
left: 25,
|
|
365
|
+
top: -5,
|
|
366
|
+
},
|
|
367
|
+
chatNotificationPrivate: {
|
|
368
|
+
width: 20,
|
|
369
|
+
height: 20,
|
|
370
|
+
display: 'flex',
|
|
371
|
+
alignItems: 'center',
|
|
372
|
+
justifyContent: 'center',
|
|
373
|
+
backgroundColor: $config.PRIMARY_COLOR,
|
|
374
|
+
color: $config.SECONDARY_FONT_COLOR,
|
|
375
|
+
fontFamily: isIOS() ? 'Helvetica' : 'sans-serif',
|
|
376
|
+
borderRadius: 10,
|
|
377
|
+
position: 'absolute',
|
|
378
|
+
right: 20,
|
|
379
|
+
top: 0,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
};
|
|
376
383
|
|
|
377
384
|
export default Chat;
|
|
@@ -31,7 +31,7 @@ import {ClientRole} from '../../agora-rn-uikit';
|
|
|
31
31
|
import LiveStreamControls, {
|
|
32
32
|
LiveStreamControlsProps,
|
|
33
33
|
} from './livestream/views/LiveStreamControls';
|
|
34
|
-
import {
|
|
34
|
+
import {useIsWebInternal} from '../utils/common';
|
|
35
35
|
import {useMeetingInfo} from './meeting-info/useMeetingInfo';
|
|
36
36
|
import LocalEndcall, {LocalEndcallProps} from '../subComponents/LocalEndCall';
|
|
37
37
|
|
|
@@ -51,11 +51,11 @@ const Controls = () => {
|
|
|
51
51
|
const {
|
|
52
52
|
data: {isHost},
|
|
53
53
|
} = useMeetingInfo();
|
|
54
|
-
|
|
54
|
+
const {controlsHolder} = useStyle();
|
|
55
55
|
return (
|
|
56
56
|
<View
|
|
57
57
|
style={[
|
|
58
|
-
|
|
58
|
+
controlsHolder,
|
|
59
59
|
{
|
|
60
60
|
paddingHorizontal: isDesktop ? '25%' : '1%',
|
|
61
61
|
backgroundColor: $config.SECONDARY_FONT_COLOR + 80,
|
|
@@ -128,29 +128,15 @@ export const ControlsComponentsArray: ControlsComponentsArrayProps = [
|
|
|
128
128
|
LiveStreamControls,
|
|
129
129
|
];
|
|
130
130
|
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
width: 20,
|
|
142
|
-
height: 20,
|
|
143
|
-
display: 'flex',
|
|
144
|
-
alignItems: 'center',
|
|
145
|
-
justifyContent: 'center',
|
|
146
|
-
backgroundColor: $config.PRIMARY_COLOR,
|
|
147
|
-
color: $config.SECONDARY_FONT_COLOR,
|
|
148
|
-
fontFamily: isIOS() ? 'Helvetica' : 'sans-serif',
|
|
149
|
-
borderRadius: 10,
|
|
150
|
-
position: 'absolute',
|
|
151
|
-
left: 25,
|
|
152
|
-
top: -10,
|
|
153
|
-
},
|
|
154
|
-
});
|
|
131
|
+
const useStyle = () => {
|
|
132
|
+
const isWebInternal = useIsWebInternal();
|
|
133
|
+
return StyleSheet.create({
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
controlsHolder: {
|
|
136
|
+
flex: isWebInternal() ? 1.3 : 1.6,
|
|
137
|
+
...controlsHolder,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
};
|
|
155
141
|
|
|
156
142
|
export default Controls;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import {layoutComponent, useRtc} from 'customization-api';
|
|
13
13
|
import React, {useMemo, useState} from 'react';
|
|
14
14
|
import {View, StyleSheet, Dimensions, Pressable} from 'react-native';
|
|
15
|
-
import {
|
|
15
|
+
import {useIsWebInternal} from '../utils/common';
|
|
16
16
|
import {useSetPinnedLayout} from '../pages/video-call/DefaultLayouts';
|
|
17
17
|
import RenderComponent from '../pages/video-call/RenderComponent';
|
|
18
18
|
const layout = (len: number, isDesktop: boolean = true) => {
|
|
@@ -34,7 +34,7 @@ const layout = (len: number, isDesktop: boolean = true) => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const GridVideo: layoutComponent = ({renderData}) => {
|
|
37
|
-
const
|
|
37
|
+
const isWebInternal = useIsWebInternal();
|
|
38
38
|
const {dispatch} = useRtc();
|
|
39
39
|
let onLayout = (e: any) => {
|
|
40
40
|
setDim([
|
|
@@ -74,7 +74,7 @@ const GridVideo: layoutComponent = ({renderData}) => {
|
|
|
74
74
|
setPinnedLayout();
|
|
75
75
|
}}
|
|
76
76
|
style={{
|
|
77
|
-
flex:
|
|
77
|
+
flex: isWebInternal() ? 1 / dims.c : 1,
|
|
78
78
|
marginHorizontal: 'auto',
|
|
79
79
|
}}
|
|
80
80
|
key={cidx}>
|