@nualang/nualang-ui-components 0.1.1419 → 0.1.1421
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/README.md +31 -0
- package/dist/Chat/BottomBar/BottomBar.js +23 -10
- package/dist/Chat/Chat.js +2 -0
- package/dist/Dialogs/ResponsiveDialog/ResponsiveDialog.js +5 -1
- package/dist/Dialogs/SelectExercise/SelectExercise.js +390 -15
- package/dist/Exercises/Bot/Bot.js +1 -0
- package/dist/Exercises/ListenAndSpeak/ListenAndSpeak.js +18 -8
- package/dist/Exercises/Pronouncer/Pronouncer.js +18 -8
- package/dist/Exercises/Roleplay/Games/ActItOutGameListeningAndSpeaking/ActItOutGame.js +18 -8
- package/dist/Exercises/Roleplay/Games/ActItOutGameSpeaking/ActItOutGame.js +18 -9
- package/dist/Lists/CourseSelection/CourseSelection.js +43 -54
- package/dist/Lists/ExerciseSelection/ExerciseSelection.js +197 -414
- package/dist/Lists/Exercises/Exercises.js +56 -25
- package/dist/Lists/Leaderboard/Leaderboard.js +4 -4
- package/dist/Lists/PhraseListSelection/PhraseListSelection.js +95 -157
- package/dist/Lists/RoleplaySelection/RoleplaySelection.js +158 -174
- package/dist/Live/Game/Game.js +19 -45
- package/dist/Live/GameMembers/GameMembers.js +4 -0
- package/dist/Live/GameSettings/GameSettings.js +333 -176
- package/dist/Live/HowToJoin/HowToJoin.js +204 -103
- package/dist/Live/WaitingRoom/WaitingRoom.js +421 -290
- package/dist/hooks/useRecognition.js +208 -37
- package/package.json +2 -1
|
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from "react";
|
|
|
2
2
|
import { Joyride, STATUS } from "react-joyride";
|
|
3
3
|
import { makeStyles } from "tss-react/mui";
|
|
4
4
|
import { useTheme } from "@mui/material/styles";
|
|
5
|
-
import { Grid, Slide, IconButton, Button, Toolbar, AppBar, Dialog, Typography, Box, Tooltip } from "@mui/material";
|
|
5
|
+
import { Grid, Slide, IconButton, Button, Toolbar, AppBar, Dialog, Typography, Box, Tooltip, CircularProgress } from "@mui/material";
|
|
6
6
|
import { styled } from "@mui/system";
|
|
7
7
|
import LightbulbIcon from "@mui/icons-material/Lightbulb";
|
|
8
8
|
import Replay from "@mui/icons-material/Replay";
|
|
@@ -228,9 +228,12 @@ function Pronouncer({
|
|
|
228
228
|
});
|
|
229
229
|
}
|
|
230
230
|
if (recognizing && interimTranscript === "" && finalTranscript === "") {
|
|
231
|
-
// Set a timer to stop listening if no audio is detected after 10 seconds
|
|
231
|
+
// Set a timer to stop listening if no audio is detected after 10 seconds.
|
|
232
|
+
// Without the snackbar here the mic just switches off with no
|
|
233
|
+
// explanation, indistinguishable from the student stopping it themselves.
|
|
232
234
|
timer = setTimeout(() => {
|
|
233
235
|
stopListening();
|
|
236
|
+
openSnackbar?.("We didn't hear anything, please try again.", "warning");
|
|
234
237
|
}, 10000);
|
|
235
238
|
}
|
|
236
239
|
return () => clearTimeout(timer);
|
|
@@ -467,6 +470,12 @@ function Pronouncer({
|
|
|
467
470
|
}
|
|
468
471
|
};
|
|
469
472
|
const isListeningIndicatorTextVisible = recognizing && interimTranscript === "" && finalTranscript === "" ? true : false;
|
|
473
|
+
|
|
474
|
+
// isRecognizingDisabled spans the async mic start/stop window (permission
|
|
475
|
+
// prompt, audio worklet setup, socket teardown) - without a distinct label
|
|
476
|
+
// and icon here the button just goes inert with no explanation.
|
|
477
|
+
const isMicBusy = isRecognizingDisabled;
|
|
478
|
+
const micButtonLabel = isMicBusy ? t("loading") : recognizing ? t("click_to_stop") : t("click_to_speak");
|
|
470
479
|
const navigate = useNavigate();
|
|
471
480
|
const goBackToAssignment = () => {
|
|
472
481
|
navigate(`/classrooms/${classroomId}#Assignments`);
|
|
@@ -751,32 +760,33 @@ function Pronouncer({
|
|
|
751
760
|
[theme.breakpoints.down("md")]: {
|
|
752
761
|
height: "50px",
|
|
753
762
|
width: "200px",
|
|
754
|
-
color: "white",
|
|
755
763
|
padding: 2,
|
|
756
764
|
fontSize: "16px",
|
|
757
765
|
borderRadius: 3
|
|
758
766
|
},
|
|
759
767
|
height: "70px",
|
|
760
768
|
width: "250px",
|
|
761
|
-
color: "white",
|
|
762
|
-
backgroundColor: recognizing ? "#d32f2f" : "#2d7b30",
|
|
763
769
|
padding: 1,
|
|
764
770
|
fontSize: "20px",
|
|
765
771
|
borderRadius: 5,
|
|
766
772
|
border: "1.5px solid transparent",
|
|
767
773
|
"&:hover": {
|
|
768
|
-
backgroundColor: recognizing ? "#d32f2f" : "#2d7b30",
|
|
769
774
|
border: "1.5px solid black"
|
|
770
775
|
}
|
|
771
776
|
}),
|
|
772
777
|
onClick: () => handleMic(currentPhrase ? currentPhrase.phrase : ""),
|
|
773
|
-
startIcon:
|
|
778
|
+
startIcon: isMicBusy ? /*#__PURE__*/_jsx(CircularProgress, {
|
|
779
|
+
size: 20,
|
|
780
|
+
color: "inherit"
|
|
781
|
+
}) : recognizing ? /*#__PURE__*/_jsx(MicOffIcon, {}) : /*#__PURE__*/_jsx(Mic, {}),
|
|
774
782
|
color: recognizing ? "error" : "success",
|
|
775
783
|
disabled: isSpeaking || !isSocketConnected || isRecognizingDisabled,
|
|
784
|
+
"aria-busy": isMicBusy,
|
|
785
|
+
"aria-label": micButtonLabel,
|
|
776
786
|
id: "voiceInput",
|
|
777
787
|
variant: "contained",
|
|
778
788
|
ref: btnRef,
|
|
779
|
-
children:
|
|
789
|
+
children: micButtonLabel
|
|
780
790
|
})
|
|
781
791
|
}), /*#__PURE__*/_jsx(Grid, {
|
|
782
792
|
children: /*#__PURE__*/_jsx(Typography, {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from "react";
|
|
2
|
-
import { Box, Button, Typography, Grid, Avatar, Dialog } from "@mui/material";
|
|
2
|
+
import { Box, Button, Typography, Grid, Avatar, Dialog, CircularProgress } from "@mui/material";
|
|
3
3
|
import { makeStyles } from "tss-react/mui";
|
|
4
4
|
import Person from "@mui/icons-material/Person";
|
|
5
5
|
import Mic from "@mui/icons-material/Mic";
|
|
@@ -101,6 +101,12 @@ function AnswerOptions({
|
|
|
101
101
|
buttonRef.current.focus();
|
|
102
102
|
}
|
|
103
103
|
}, [isSpeaking, isSocketConnected]);
|
|
104
|
+
|
|
105
|
+
// isRecognizingDisabled spans the async mic start/stop window (permission
|
|
106
|
+
// prompt, audio worklet setup, socket teardown) - without a distinct label
|
|
107
|
+
// and icon here the button just goes inert with no explanation.
|
|
108
|
+
const isMicBusy = isRecognizingDisabled;
|
|
109
|
+
const micButtonLabel = isMicBusy ? t("loading") : recognizing ? t("click_to_stop") : t("click_to_speak");
|
|
104
110
|
return /*#__PURE__*/_jsxs(Box, {
|
|
105
111
|
sx: theme => ({
|
|
106
112
|
display: "flex",
|
|
@@ -176,27 +182,28 @@ function AnswerOptions({
|
|
|
176
182
|
children: [/*#__PURE__*/_jsx(Button, {
|
|
177
183
|
sx: theme => ({
|
|
178
184
|
[theme.breakpoints.down("md")]: {
|
|
179
|
-
color: "white",
|
|
180
185
|
borderRadius: 3
|
|
181
186
|
},
|
|
182
|
-
color: "white",
|
|
183
|
-
backgroundColor: recognizing ? "#d32f2f" : "#2d7b30",
|
|
184
187
|
mb: theme.spacing(2),
|
|
185
188
|
border: "1.5px solid transparent",
|
|
186
189
|
"&:hover": {
|
|
187
|
-
backgroundColor: recognizing ? "#d32f2f" : "#2d7b30",
|
|
188
190
|
border: "1.5px solid black"
|
|
189
191
|
}
|
|
190
192
|
}),
|
|
191
193
|
onClick: () => handleMic(text),
|
|
192
|
-
startIcon:
|
|
194
|
+
startIcon: isMicBusy ? /*#__PURE__*/_jsx(CircularProgress, {
|
|
195
|
+
size: 20,
|
|
196
|
+
color: "inherit"
|
|
197
|
+
}) : recognizing ? /*#__PURE__*/_jsx(MicOffIcon, {}) : /*#__PURE__*/_jsx(Mic, {}),
|
|
193
198
|
color: recognizing ? "error" : "success",
|
|
194
199
|
disabled: isSpeaking || !isSocketConnected || isRecognizingDisabled,
|
|
195
200
|
id: "voiceInput",
|
|
196
201
|
variant: "contained",
|
|
197
202
|
size: "large",
|
|
198
203
|
ref: buttonRef,
|
|
199
|
-
|
|
204
|
+
"aria-busy": isMicBusy,
|
|
205
|
+
"aria-label": micButtonLabel,
|
|
206
|
+
children: micButtonLabel
|
|
200
207
|
}), /*#__PURE__*/_jsx(Typography, {
|
|
201
208
|
role: "status",
|
|
202
209
|
variant: "h6",
|
|
@@ -434,9 +441,12 @@ function RoleplayGame({
|
|
|
434
441
|
useEffect(() => {
|
|
435
442
|
let timer;
|
|
436
443
|
if (recognizing && interimTranscript === "" && finalTranscript === "") {
|
|
437
|
-
// Set a timer to stop listening if no audio is detected after 10 seconds
|
|
444
|
+
// Set a timer to stop listening if no audio is detected after 10 seconds.
|
|
445
|
+
// Without the snackbar here the mic just switches off with no
|
|
446
|
+
// explanation, indistinguishable from the student stopping it themselves.
|
|
438
447
|
timer = setTimeout(() => {
|
|
439
448
|
stopListening();
|
|
449
|
+
openSnackbar?.("We didn't hear anything, please try again.", "warning");
|
|
440
450
|
}, 10000);
|
|
441
451
|
}
|
|
442
452
|
return () => clearTimeout(timer);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from "react";
|
|
2
|
-
import { Box, Button, Typography, Grid, Avatar, Dialog } from "@mui/material";
|
|
2
|
+
import { Box, Button, Typography, Grid, Avatar, Dialog, CircularProgress } from "@mui/material";
|
|
3
3
|
import { makeStyles } from "tss-react/mui";
|
|
4
4
|
import Person from "@mui/icons-material/Person";
|
|
5
5
|
import RecordVoiceOverIcon from "@mui/icons-material/RecordVoiceOver";
|
|
@@ -92,6 +92,12 @@ function AnswerOptions({
|
|
|
92
92
|
buttonRef.current.focus();
|
|
93
93
|
}
|
|
94
94
|
}, [isSpeaking, isSocketConnected]);
|
|
95
|
+
|
|
96
|
+
// isRecognizingDisabled spans the async mic start/stop window (permission
|
|
97
|
+
// prompt, audio worklet setup, socket teardown) - without a distinct label
|
|
98
|
+
// and icon here the button just goes inert with no explanation.
|
|
99
|
+
const isMicBusy = isRecognizingDisabled;
|
|
100
|
+
const micButtonLabel = isMicBusy ? t("loading") : recognizing ? t("click_to_stop") : t("click_to_speak");
|
|
95
101
|
return /*#__PURE__*/_jsxs(Box, {
|
|
96
102
|
sx: theme => ({
|
|
97
103
|
display: "flex",
|
|
@@ -167,28 +173,28 @@ function AnswerOptions({
|
|
|
167
173
|
children: [/*#__PURE__*/_jsx(Button, {
|
|
168
174
|
sx: theme => ({
|
|
169
175
|
[theme.breakpoints.down("md")]: {
|
|
170
|
-
color: "white",
|
|
171
176
|
borderRadius: 3
|
|
172
177
|
},
|
|
173
|
-
color: "white",
|
|
174
|
-
backgroundColor: recognizing ? "#d32f2f" : "#2d7b30",
|
|
175
178
|
mb: theme.spacing(2),
|
|
176
179
|
border: "1.5px solid transparent",
|
|
177
180
|
"&:hover": {
|
|
178
|
-
backgroundColor: recognizing ? "#d32f2f" : "#2d7b30",
|
|
179
181
|
border: "1.5px solid black"
|
|
180
182
|
}
|
|
181
183
|
}),
|
|
182
184
|
onClick: () => handleMic(text),
|
|
183
|
-
startIcon:
|
|
185
|
+
startIcon: isMicBusy ? /*#__PURE__*/_jsx(CircularProgress, {
|
|
186
|
+
size: 20,
|
|
187
|
+
color: "inherit"
|
|
188
|
+
}) : recognizing ? /*#__PURE__*/_jsx(MicOffIcon, {}) : /*#__PURE__*/_jsx(Mic, {}),
|
|
184
189
|
color: recognizing ? "error" : "success",
|
|
185
190
|
disabled: isSpeaking || !isSocketConnected || isRecognizingDisabled,
|
|
186
191
|
id: "voiceInput",
|
|
187
192
|
variant: "contained",
|
|
188
193
|
size: "large",
|
|
189
194
|
ref: buttonRef,
|
|
190
|
-
"aria-
|
|
191
|
-
|
|
195
|
+
"aria-busy": isMicBusy,
|
|
196
|
+
"aria-label": ariaLiveText || micButtonLabel,
|
|
197
|
+
children: micButtonLabel
|
|
192
198
|
}), /*#__PURE__*/_jsx(Typography, {
|
|
193
199
|
role: "status",
|
|
194
200
|
variant: "h6",
|
|
@@ -427,9 +433,12 @@ function RoleplayGame({
|
|
|
427
433
|
useEffect(() => {
|
|
428
434
|
let timer;
|
|
429
435
|
if (recognizing && interimTranscript === "" && finalTranscript === "") {
|
|
430
|
-
// Set a timer to stop listening if no audio is detected after 10 seconds
|
|
436
|
+
// Set a timer to stop listening if no audio is detected after 10 seconds.
|
|
437
|
+
// Without the snackbar here the mic just switches off with no
|
|
438
|
+
// explanation, indistinguishable from the student stopping it themselves.
|
|
431
439
|
timer = setTimeout(() => {
|
|
432
440
|
stopListening();
|
|
441
|
+
openSnackbar?.("We didn't hear anything, please try again.", "warning");
|
|
433
442
|
}, 10000);
|
|
434
443
|
}
|
|
435
444
|
return () => clearTimeout(timer);
|
|
@@ -3,27 +3,23 @@ import { List, ListItem, ListItemButton, ListItemText, Collapse, Avatar, Box, To
|
|
|
3
3
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
4
4
|
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
|
5
5
|
import ExerciseSelection from "../ExerciseSelection/ExerciseSelection";
|
|
6
|
+
import { topicHasScenario } from "../RoleplaySelection/RoleplaySelection";
|
|
6
7
|
import { courses as courseQuerys } from "@nualang/nualang-api-and-queries/Queries";
|
|
7
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
9
|
function Course({
|
|
9
10
|
course,
|
|
10
11
|
index,
|
|
11
12
|
getCourseSections,
|
|
12
|
-
getRoleplays,
|
|
13
13
|
getPhraseLists,
|
|
14
14
|
username,
|
|
15
|
-
handleCreateGame,
|
|
16
15
|
t,
|
|
17
|
-
isExerciseSelected,
|
|
18
|
-
setIsExerciseSelected,
|
|
19
16
|
isDialogOpen,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
isCreator,
|
|
17
|
+
sourceType,
|
|
18
|
+
selectedListIds,
|
|
19
|
+
onTogglePhraseList,
|
|
20
|
+
onToggleTopicAll,
|
|
21
|
+
selectedScenarioId,
|
|
22
|
+
onSelectScenario,
|
|
27
23
|
courseSectionTopicId
|
|
28
24
|
}) {
|
|
29
25
|
const [open, setOpen] = useState(false);
|
|
@@ -32,7 +28,7 @@ function Course({
|
|
|
32
28
|
return response;
|
|
33
29
|
}, {
|
|
34
30
|
courseId: course.courseId,
|
|
35
|
-
username
|
|
31
|
+
username,
|
|
36
32
|
filters: {
|
|
37
33
|
limit: 1000
|
|
38
34
|
}
|
|
@@ -40,12 +36,19 @@ function Course({
|
|
|
40
36
|
enabled: !!course.courseId && isDialogOpen
|
|
41
37
|
});
|
|
42
38
|
useEffect(() => {
|
|
43
|
-
if (courseSectionTopicId
|
|
39
|
+
if (courseSectionTopicId?.split("|")[0] === course.courseId) {
|
|
44
40
|
setOpen(true);
|
|
45
41
|
}
|
|
46
42
|
}, [courseSectionTopicId]);
|
|
47
43
|
const courseSections = useMemo(() => sectionsQuery.isSuccess && Array.isArray(sectionsQuery.data.Items) && sectionsQuery.data.Items.length ? sectionsQuery.data.Items : [], [sectionsQuery.data, sectionsQuery.isSuccess]);
|
|
48
|
-
const isCourseEmpty = courseSections.length === 0 && !sectionsQuery.isLoading
|
|
44
|
+
const isCourseEmpty = courseSections.length === 0 && !sectionsQuery.isLoading;
|
|
45
|
+
|
|
46
|
+
// In scenarios mode, once this course's sections have loaded, hide the whole
|
|
47
|
+
// course if none of its topics have scenarios.
|
|
48
|
+
const hasScenarios = courseSections.some(sec => (sec.topics || []).some(tp => topicHasScenario(tp.roleplays)));
|
|
49
|
+
if (sourceType === "scenarios" && sectionsQuery.isSuccess && !hasScenarios) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
49
52
|
return /*#__PURE__*/_jsxs(List, {
|
|
50
53
|
disablePadding: true,
|
|
51
54
|
children: [/*#__PURE__*/_jsx(Tooltip, {
|
|
@@ -54,7 +57,7 @@ function Course({
|
|
|
54
57
|
disablePadding: true,
|
|
55
58
|
children: /*#__PURE__*/_jsx(ListItemButton, {
|
|
56
59
|
onClick: () => setOpen(!open),
|
|
57
|
-
disabled: isCourseEmpty
|
|
60
|
+
disabled: isCourseEmpty,
|
|
58
61
|
children: /*#__PURE__*/_jsxs(Box, {
|
|
59
62
|
sx: {
|
|
60
63
|
display: "flex",
|
|
@@ -79,11 +82,11 @@ function Course({
|
|
|
79
82
|
})]
|
|
80
83
|
}), open ? /*#__PURE__*/_jsx(ExpandLessIcon, {
|
|
81
84
|
sx: {
|
|
82
|
-
color: "
|
|
85
|
+
color: "text.secondary"
|
|
83
86
|
}
|
|
84
87
|
}) : /*#__PURE__*/_jsx(ExpandMoreIcon, {
|
|
85
88
|
sx: {
|
|
86
|
-
color: "
|
|
89
|
+
color: "text.secondary"
|
|
87
90
|
}
|
|
88
91
|
})]
|
|
89
92
|
})
|
|
@@ -95,25 +98,20 @@ function Course({
|
|
|
95
98
|
unmountOnExit: true,
|
|
96
99
|
children: /*#__PURE__*/_jsx(Box, {
|
|
97
100
|
sx: {
|
|
98
|
-
ml:
|
|
101
|
+
ml: 7
|
|
99
102
|
},
|
|
100
103
|
children: /*#__PURE__*/_jsx(ExerciseSelection, {
|
|
101
104
|
t: t,
|
|
102
105
|
sections: courseSections,
|
|
103
106
|
isLoading: sectionsQuery.isLoading,
|
|
104
|
-
handleCreateGame: handleCreateGame,
|
|
105
107
|
courseId: course.courseId,
|
|
106
|
-
isExerciseSelected: isExerciseSelected,
|
|
107
|
-
setIsExerciseSelected: setIsExerciseSelected,
|
|
108
|
-
getRoleplays: getRoleplays,
|
|
109
108
|
getPhraseLists: getPhraseLists,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
isCreator: isCreator,
|
|
109
|
+
sourceType: sourceType,
|
|
110
|
+
selectedListIds: selectedListIds,
|
|
111
|
+
onTogglePhraseList: onTogglePhraseList,
|
|
112
|
+
onToggleTopicAll: onToggleTopicAll,
|
|
113
|
+
selectedScenarioId: selectedScenarioId,
|
|
114
|
+
onSelectScenario: onSelectScenario,
|
|
117
115
|
courseSectionTopicId: courseSectionTopicId
|
|
118
116
|
})
|
|
119
117
|
})
|
|
@@ -121,44 +119,35 @@ function Course({
|
|
|
121
119
|
}, index);
|
|
122
120
|
}
|
|
123
121
|
function CourseSelection({
|
|
124
|
-
courses,
|
|
122
|
+
courses = [],
|
|
125
123
|
getCourseSections,
|
|
126
|
-
getRoleplays,
|
|
127
124
|
getPhraseLists,
|
|
128
125
|
username,
|
|
129
|
-
handleCreateGame,
|
|
130
126
|
t,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
isCreator,
|
|
140
|
-
courseSectionTopicId,
|
|
141
|
-
...otherProps
|
|
127
|
+
isDialogOpen,
|
|
128
|
+
sourceType,
|
|
129
|
+
selectedListIds,
|
|
130
|
+
onTogglePhraseList,
|
|
131
|
+
onToggleTopicAll,
|
|
132
|
+
selectedScenarioId,
|
|
133
|
+
onSelectScenario,
|
|
134
|
+
courseSectionTopicId
|
|
142
135
|
}) {
|
|
143
136
|
return /*#__PURE__*/_jsx("div", {
|
|
144
137
|
children: courses.map((course, index) => /*#__PURE__*/_jsx(Course, {
|
|
145
138
|
course: course,
|
|
146
139
|
index: index,
|
|
147
140
|
getCourseSections: getCourseSections,
|
|
148
|
-
getRoleplays: getRoleplays,
|
|
149
141
|
getPhraseLists: getPhraseLists,
|
|
150
142
|
username: username,
|
|
151
|
-
handleCreateGame: handleCreateGame,
|
|
152
143
|
t: t,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
isAssignment: isAssignment,
|
|
161
|
-
isCreator: isCreator,
|
|
144
|
+
isDialogOpen: isDialogOpen,
|
|
145
|
+
sourceType: sourceType,
|
|
146
|
+
selectedListIds: selectedListIds,
|
|
147
|
+
onTogglePhraseList: onTogglePhraseList,
|
|
148
|
+
onToggleTopicAll: onToggleTopicAll,
|
|
149
|
+
selectedScenarioId: selectedScenarioId,
|
|
150
|
+
onSelectScenario: onSelectScenario,
|
|
162
151
|
courseSectionTopicId: courseSectionTopicId
|
|
163
152
|
}, course.courseId))
|
|
164
153
|
});
|