@nualang/nualang-ui-components 0.1.1419 → 0.1.1420
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/dist/Dialogs/ResponsiveDialog/ResponsiveDialog.js +5 -1
- package/dist/Dialogs/SelectExercise/SelectExercise.js +390 -15
- 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/package.json +1 -1
|
@@ -28,6 +28,10 @@ const Transition = /*#__PURE__*/forwardRef(function Transition(props, ref) {
|
|
|
28
28
|
export default function ResponsiveDialog({
|
|
29
29
|
open,
|
|
30
30
|
handleClose,
|
|
31
|
+
// Backdrop-click / Escape handler. Defaults to `handleClose`, but can be
|
|
32
|
+
// supplied on its own so a dialog can be dismissable without rendering the
|
|
33
|
+
// built-in footer Close button (which `handleClose` drives).
|
|
34
|
+
onClose,
|
|
31
35
|
handleSubmit,
|
|
32
36
|
t = text => text,
|
|
33
37
|
submitText,
|
|
@@ -62,7 +66,7 @@ export default function ResponsiveDialog({
|
|
|
62
66
|
fullWidth: fullWidth,
|
|
63
67
|
maxWidth: maxWidth,
|
|
64
68
|
open: open,
|
|
65
|
-
onClose: handleClose,
|
|
69
|
+
onClose: onClose || handleClose,
|
|
66
70
|
"aria-label": dialogTitle,
|
|
67
71
|
transitionDuration: transitionDuration,
|
|
68
72
|
keepMounted: keepDialogMounted,
|
|
@@ -1,46 +1,421 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useState, useMemo, useEffect } from "react";
|
|
2
|
+
import { Box, Typography, Button } from "@mui/material";
|
|
3
|
+
import { makeStyles } from "tss-react/mui";
|
|
4
|
+
import MenuBookIcon from "@mui/icons-material/MenuBook";
|
|
5
|
+
import ForumIcon from "@mui/icons-material/Forum";
|
|
6
|
+
import PlaylistAddCheckIcon from "@mui/icons-material/PlaylistAddCheck";
|
|
7
|
+
import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
|
|
8
|
+
import HearingIcon from "@mui/icons-material/Hearing";
|
|
9
|
+
import ExtensionIcon from "@mui/icons-material/Extension";
|
|
10
|
+
import EditNoteIcon from "@mui/icons-material/EditNote";
|
|
11
|
+
import LiveTvIcon from "@mui/icons-material/LiveTv";
|
|
12
|
+
import AutoStoriesIcon from "@mui/icons-material/AutoStories";
|
|
2
13
|
import CourseSelection from "../../Lists/CourseSelection/CourseSelection";
|
|
3
14
|
import ResponsiveDialog from "../ResponsiveDialog/ResponsiveDialog";
|
|
4
|
-
import { Box } from "@mui/system";
|
|
5
|
-
import { DialogContentText } from "@mui/material";
|
|
6
15
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
|
+
const useStyles = makeStyles()(() => ({
|
|
17
|
+
content: {
|
|
18
|
+
padding: 0
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
const GAME_TYPES = [{
|
|
22
|
+
id: "translation",
|
|
23
|
+
labelKey: "translation",
|
|
24
|
+
Icon: SwapHorizIcon,
|
|
25
|
+
needsDefinitions: false
|
|
26
|
+
}, {
|
|
27
|
+
id: "listening",
|
|
28
|
+
labelKey: "listening",
|
|
29
|
+
Icon: HearingIcon,
|
|
30
|
+
needsDefinitions: false
|
|
31
|
+
}, {
|
|
32
|
+
id: "meaning",
|
|
33
|
+
labelKey: "meaning",
|
|
34
|
+
Icon: ExtensionIcon,
|
|
35
|
+
needsDefinitions: true
|
|
36
|
+
}, {
|
|
37
|
+
id: "meaning-read-and-write",
|
|
38
|
+
labelKey: "meaning_read_write",
|
|
39
|
+
Icon: EditNoteIcon,
|
|
40
|
+
needsDefinitions: true
|
|
41
|
+
}];
|
|
42
|
+
|
|
43
|
+
// The single game mode for scenarios, shown in the footer like the phrase-list
|
|
44
|
+
// exercise toggle.
|
|
45
|
+
const STORY_GAME_TYPE = {
|
|
46
|
+
id: "roleplay-story",
|
|
47
|
+
labelKey: "roleplay-story",
|
|
48
|
+
Icon: AutoStoriesIcon
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Fisher-Yates in-place shuffle (inlined to avoid pulling the utils barrel,
|
|
52
|
+
// which imports RiveScript, into the dialog bundle).
|
|
53
|
+
function shuffleInPlace(array) {
|
|
54
|
+
for (let i = array.length - 1; i > 0; i -= 1) {
|
|
55
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
56
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
57
|
+
}
|
|
58
|
+
return array;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Merge every selected list's phrases into one pool, tagging each phrase with
|
|
62
|
+
// its source list so the game can later draw evenly across lists, and dropping
|
|
63
|
+
// exact-duplicate phrase strings.
|
|
64
|
+
function dedupeMergedPhrases(lists) {
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const out = [];
|
|
67
|
+
lists.forEach(list => {
|
|
68
|
+
(list.phrases || []).forEach(p => {
|
|
69
|
+
const key = (p?.phrase || "").trim().toLowerCase();
|
|
70
|
+
if (key && seen.has(key)) return;
|
|
71
|
+
if (key) seen.add(key);
|
|
72
|
+
out.push({
|
|
73
|
+
...p,
|
|
74
|
+
sourceListId: list.phraseListId
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function mergePhrases(lists) {
|
|
81
|
+
// Shuffle the merged pool by default so lists interleave rather than play in
|
|
82
|
+
// per-list blocks (the waiting-room game start reshuffles too).
|
|
83
|
+
return shuffleInPlace(dedupeMergedPhrases(lists));
|
|
84
|
+
}
|
|
7
85
|
function SelectExercise({
|
|
8
86
|
t = text => text,
|
|
9
87
|
open,
|
|
10
88
|
handleClose,
|
|
11
|
-
courses,
|
|
89
|
+
courses = [],
|
|
12
90
|
getCourseSections,
|
|
13
|
-
getRoleplays,
|
|
14
91
|
getPhraseLists,
|
|
15
92
|
handleCreateGame,
|
|
16
93
|
username,
|
|
17
|
-
courseSectionTopicId = ""
|
|
94
|
+
courseSectionTopicId = "",
|
|
95
|
+
initialSourceType = "phrases"
|
|
18
96
|
}) {
|
|
19
|
-
const
|
|
97
|
+
const {
|
|
98
|
+
classes
|
|
99
|
+
} = useStyles();
|
|
100
|
+
const [sourceType, setSourceType] = useState(initialSourceType);
|
|
101
|
+
// Map<phraseListId, enrichedList> — keeps the phrase counts and definition
|
|
102
|
+
// flags without re-fetching, order-independent (the pool is shuffled anyway).
|
|
103
|
+
const [selectedLists, setSelectedLists] = useState(() => new Map());
|
|
104
|
+
const [selectedScenario, setSelectedScenario] = useState(null);
|
|
105
|
+
const [gameType, setGameType] = useState("translation");
|
|
106
|
+
|
|
107
|
+
// Selection is one-off per game; reset every time the dialog opens.
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (open) {
|
|
110
|
+
setSourceType(initialSourceType);
|
|
111
|
+
setSelectedLists(new Map());
|
|
112
|
+
setSelectedScenario(null);
|
|
113
|
+
setGameType("translation");
|
|
114
|
+
}
|
|
115
|
+
}, [open]);
|
|
116
|
+
const isPhrases = sourceType === "phrases";
|
|
117
|
+
const togglePhraseList = list => {
|
|
118
|
+
setSelectedLists(prev => {
|
|
119
|
+
const next = new Map(prev);
|
|
120
|
+
if (next.has(list.phraseListId)) next.delete(list.phraseListId);else next.set(list.phraseListId, list);
|
|
121
|
+
return next;
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const toggleTopicAll = lists => {
|
|
125
|
+
setSelectedLists(prev => {
|
|
126
|
+
const next = new Map(prev);
|
|
127
|
+
const allSelected = lists.every(l => next.has(l.phraseListId));
|
|
128
|
+
lists.forEach(l => {
|
|
129
|
+
if (allSelected) next.delete(l.phraseListId);else next.set(l.phraseListId, l);
|
|
130
|
+
});
|
|
131
|
+
return next;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const selectedListsArr = useMemo(() => [...selectedLists.values()], [selectedLists]);
|
|
135
|
+
// Count the deduped merged pool (matching what actually gets played) when the
|
|
136
|
+
// phrases are loaded; otherwise fall back to the sum of the lists' counts.
|
|
137
|
+
const totalPhrases = useMemo(() => {
|
|
138
|
+
const allHavePhrases = selectedListsArr.every(l => Array.isArray(l.phrases));
|
|
139
|
+
return allHavePhrases ? dedupeMergedPhrases(selectedListsArr).length : selectedListsArr.reduce((n, l) => n + (l.phraseCount || 0), 0);
|
|
140
|
+
}, [selectedListsArr]);
|
|
141
|
+
const allHaveDefinitions = selectedListsArr.length > 0 && selectedListsArr.every(l => l.hasDefinitions);
|
|
142
|
+
const availableGameTypes = GAME_TYPES.filter(g => !g.needsDefinitions || allHaveDefinitions);
|
|
143
|
+
const effectiveGameType = availableGameTypes.some(g => g.id === gameType) ? gameType : availableGameTypes[0]?.id;
|
|
144
|
+
|
|
145
|
+
// Footer exercise segments + count are mode-aware: phrase lists show the game
|
|
146
|
+
// types and "n lists · n phrases"; scenarios show a single Story mode and the
|
|
147
|
+
// selected scenario's message count.
|
|
148
|
+
const footerGameTypes = isPhrases ? availableGameTypes : [STORY_GAME_TYPE];
|
|
149
|
+
const activeFooterGameType = isPhrases ? effectiveGameType : STORY_GAME_TYPE.id;
|
|
150
|
+
const canPlay = isPhrases ? selectedListsArr.length > 0 : !!selectedScenario;
|
|
151
|
+
const handlePlay = () => {
|
|
152
|
+
if (!canPlay) return;
|
|
153
|
+
if (isPhrases) {
|
|
154
|
+
const first = selectedListsArr[0];
|
|
155
|
+
handleCreateGame({
|
|
156
|
+
courseId: first.courseId,
|
|
157
|
+
courseSectionTopicId: first.courseSectionTopicId,
|
|
158
|
+
exercise: effectiveGameType,
|
|
159
|
+
phraseListIds: selectedListsArr.map(l => l.phraseListId),
|
|
160
|
+
// Hydrated list metadata for the waiting room's source strip.
|
|
161
|
+
phraseLists: selectedListsArr.map(l => ({
|
|
162
|
+
phraseListId: l.phraseListId,
|
|
163
|
+
phraseListName: l.phraseListName,
|
|
164
|
+
picture: l.picture || null,
|
|
165
|
+
phraseCount: l.phraseCount,
|
|
166
|
+
topicName: l.topicName ?? "",
|
|
167
|
+
courseSectionTopicId: l.courseSectionTopicId ?? null,
|
|
168
|
+
topicVideoUrl: l.topicVideoUrl ?? null
|
|
169
|
+
})),
|
|
170
|
+
phrases: mergePhrases(selectedListsArr),
|
|
171
|
+
shuffle: true,
|
|
172
|
+
timer: 30,
|
|
173
|
+
answerOption: 0,
|
|
174
|
+
topicName: first.topicName ?? ""
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
handleCreateGame({
|
|
178
|
+
courseSectionTopicId: selectedScenario.courseSectionTopicId,
|
|
179
|
+
exercise: "roleplay-story",
|
|
180
|
+
roleplayId: selectedScenario.roleplayId,
|
|
181
|
+
timer: 30,
|
|
182
|
+
answerOption: 0,
|
|
183
|
+
topicName: selectedScenario.topicName ?? ""
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const sourceTab = active => ({
|
|
188
|
+
display: "flex",
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
gap: 0.75,
|
|
191
|
+
px: 2,
|
|
192
|
+
py: 1,
|
|
193
|
+
fontSize: 14,
|
|
194
|
+
fontWeight: 600,
|
|
195
|
+
cursor: "pointer",
|
|
196
|
+
...(active ? {
|
|
197
|
+
color: "primary.contrastText",
|
|
198
|
+
backgroundColor: "primary.main"
|
|
199
|
+
} : {
|
|
200
|
+
color: "text.secondary",
|
|
201
|
+
backgroundColor: "background.paper"
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
const countLabel = isPhrases ? selectedListsArr.length ? t("lists_and_phrases_selected", {
|
|
205
|
+
lists: selectedListsArr.length,
|
|
206
|
+
phrases: totalPhrases,
|
|
207
|
+
count: selectedListsArr.length
|
|
208
|
+
}) : t("no_lists_selected") : selectedScenario ? t("scenario_selected") : t("no_scenario_selected");
|
|
20
209
|
return /*#__PURE__*/_jsxs(ResponsiveDialog, {
|
|
21
210
|
open: open,
|
|
22
|
-
|
|
211
|
+
onClose: handleClose,
|
|
23
212
|
dialogTitle: t("play_live"),
|
|
24
213
|
maxWidth: "md",
|
|
25
|
-
|
|
26
|
-
|
|
214
|
+
dialogContentClassName: classes.content,
|
|
215
|
+
children: [/*#__PURE__*/_jsx(Box, {
|
|
216
|
+
sx: {
|
|
217
|
+
display: "flex",
|
|
218
|
+
alignItems: "center",
|
|
219
|
+
justifyContent: "space-between",
|
|
220
|
+
gap: 3,
|
|
221
|
+
flexWrap: "wrap",
|
|
222
|
+
px: 3,
|
|
223
|
+
pt: 1
|
|
224
|
+
},
|
|
225
|
+
children: /*#__PURE__*/_jsxs(Box, {
|
|
226
|
+
sx: {
|
|
227
|
+
display: "flex",
|
|
228
|
+
border: "1px solid",
|
|
229
|
+
borderColor: "divider",
|
|
230
|
+
borderRadius: 1,
|
|
231
|
+
overflow: "hidden",
|
|
232
|
+
flex: "none"
|
|
233
|
+
},
|
|
234
|
+
children: [/*#__PURE__*/_jsxs(Box, {
|
|
235
|
+
role: "button",
|
|
236
|
+
tabIndex: 0,
|
|
237
|
+
"aria-pressed": isPhrases,
|
|
238
|
+
onClick: () => setSourceType("phrases"),
|
|
239
|
+
onKeyDown: e => {
|
|
240
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
setSourceType("phrases");
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
sx: sourceTab(isPhrases),
|
|
246
|
+
children: [/*#__PURE__*/_jsx(MenuBookIcon, {
|
|
247
|
+
sx: {
|
|
248
|
+
fontSize: 18
|
|
249
|
+
}
|
|
250
|
+
}), t("source_type_phrase_lists")]
|
|
251
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
252
|
+
role: "button",
|
|
253
|
+
tabIndex: 0,
|
|
254
|
+
"aria-pressed": !isPhrases,
|
|
255
|
+
onClick: () => setSourceType("scenarios"),
|
|
256
|
+
onKeyDown: e => {
|
|
257
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
setSourceType("scenarios");
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
sx: sourceTab(!isPhrases),
|
|
263
|
+
children: [/*#__PURE__*/_jsx(ForumIcon, {
|
|
264
|
+
sx: {
|
|
265
|
+
fontSize: 18
|
|
266
|
+
}
|
|
267
|
+
}), t("source_type_scenarios")]
|
|
268
|
+
})]
|
|
269
|
+
})
|
|
27
270
|
}), /*#__PURE__*/_jsx(Box, {
|
|
28
271
|
sx: {
|
|
29
|
-
mt:
|
|
272
|
+
mt: 2,
|
|
273
|
+
px: 3,
|
|
274
|
+
pb: 2.5,
|
|
275
|
+
minHeight: 280
|
|
30
276
|
},
|
|
31
277
|
children: /*#__PURE__*/_jsx(CourseSelection, {
|
|
32
278
|
courses: courses,
|
|
33
279
|
getCourseSections: getCourseSections,
|
|
34
|
-
getRoleplays: getRoleplays,
|
|
35
280
|
getPhraseLists: getPhraseLists,
|
|
36
281
|
username: username,
|
|
37
|
-
handleCreateGame: handleCreateGame,
|
|
38
282
|
t: t,
|
|
39
|
-
isExerciseSelected: isExerciseSelected,
|
|
40
|
-
setIsExerciseSelected: setIsExerciseSelected,
|
|
41
283
|
isDialogOpen: open,
|
|
284
|
+
sourceType: sourceType,
|
|
285
|
+
selectedListIds: selectedLists,
|
|
286
|
+
onTogglePhraseList: togglePhraseList,
|
|
287
|
+
onToggleTopicAll: toggleTopicAll,
|
|
288
|
+
selectedScenarioId: selectedScenario?.roleplayId || null,
|
|
289
|
+
onSelectScenario: setSelectedScenario,
|
|
42
290
|
courseSectionTopicId: courseSectionTopicId
|
|
43
291
|
})
|
|
292
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
293
|
+
sx: {
|
|
294
|
+
position: "sticky",
|
|
295
|
+
bottom: 0,
|
|
296
|
+
borderTop: "1px solid",
|
|
297
|
+
borderColor: "divider",
|
|
298
|
+
backgroundColor: "background.default",
|
|
299
|
+
px: 3,
|
|
300
|
+
py: 1.5,
|
|
301
|
+
display: "flex",
|
|
302
|
+
flexDirection: "column",
|
|
303
|
+
gap: 1.5,
|
|
304
|
+
zIndex: 1
|
|
305
|
+
},
|
|
306
|
+
children: [/*#__PURE__*/_jsxs(Box, {
|
|
307
|
+
sx: {
|
|
308
|
+
display: "flex",
|
|
309
|
+
alignItems: "center",
|
|
310
|
+
gap: 2,
|
|
311
|
+
flexWrap: "wrap"
|
|
312
|
+
},
|
|
313
|
+
children: [/*#__PURE__*/_jsxs(Box, {
|
|
314
|
+
sx: {
|
|
315
|
+
display: "flex",
|
|
316
|
+
alignItems: "center",
|
|
317
|
+
gap: 1
|
|
318
|
+
},
|
|
319
|
+
children: [isPhrases ? /*#__PURE__*/_jsx(PlaylistAddCheckIcon, {
|
|
320
|
+
sx: {
|
|
321
|
+
fontSize: 20,
|
|
322
|
+
color: "primary.main"
|
|
323
|
+
}
|
|
324
|
+
}) : /*#__PURE__*/_jsx(ForumIcon, {
|
|
325
|
+
sx: {
|
|
326
|
+
fontSize: 20,
|
|
327
|
+
color: "primary.main"
|
|
328
|
+
}
|
|
329
|
+
}), /*#__PURE__*/_jsx(Typography, {
|
|
330
|
+
sx: {
|
|
331
|
+
fontSize: 14,
|
|
332
|
+
fontWeight: 600,
|
|
333
|
+
color: "text.primary"
|
|
334
|
+
},
|
|
335
|
+
children: countLabel
|
|
336
|
+
})]
|
|
337
|
+
}), /*#__PURE__*/_jsx(Box, {
|
|
338
|
+
sx: {
|
|
339
|
+
display: "flex",
|
|
340
|
+
alignItems: "center",
|
|
341
|
+
border: "1px solid",
|
|
342
|
+
borderColor: "divider",
|
|
343
|
+
borderRadius: 1,
|
|
344
|
+
overflow: "hidden",
|
|
345
|
+
backgroundColor: "background.paper"
|
|
346
|
+
},
|
|
347
|
+
children: footerGameTypes.map(g => {
|
|
348
|
+
const active = activeFooterGameType === g.id;
|
|
349
|
+
const {
|
|
350
|
+
Icon
|
|
351
|
+
} = g;
|
|
352
|
+
return /*#__PURE__*/_jsxs(Box, {
|
|
353
|
+
onClick: () => isPhrases && setGameType(g.id),
|
|
354
|
+
sx: {
|
|
355
|
+
display: "flex",
|
|
356
|
+
alignItems: "center",
|
|
357
|
+
gap: 0.75,
|
|
358
|
+
px: 1.5,
|
|
359
|
+
py: 1,
|
|
360
|
+
fontSize: 14,
|
|
361
|
+
cursor: isPhrases ? "pointer" : "default",
|
|
362
|
+
transition: "background-color 200ms cubic-bezier(0.4,0,0.2,1)",
|
|
363
|
+
...(active ? {
|
|
364
|
+
color: "primary.contrastText",
|
|
365
|
+
backgroundColor: "primary.main",
|
|
366
|
+
fontWeight: 600,
|
|
367
|
+
...(isPhrases ? {
|
|
368
|
+
"&:hover": {
|
|
369
|
+
backgroundColor: "primary.dark"
|
|
370
|
+
}
|
|
371
|
+
} : {})
|
|
372
|
+
} : {
|
|
373
|
+
color: "text.secondary",
|
|
374
|
+
"&:hover": {
|
|
375
|
+
backgroundColor: "action.hover"
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
},
|
|
379
|
+
children: [/*#__PURE__*/_jsx(Icon, {
|
|
380
|
+
sx: {
|
|
381
|
+
fontSize: 18
|
|
382
|
+
}
|
|
383
|
+
}), t(g.labelKey)]
|
|
384
|
+
}, g.id);
|
|
385
|
+
})
|
|
386
|
+
})]
|
|
387
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
388
|
+
sx: {
|
|
389
|
+
display: "flex",
|
|
390
|
+
alignItems: "center",
|
|
391
|
+
justifyContent: "flex-end",
|
|
392
|
+
gap: 1
|
|
393
|
+
},
|
|
394
|
+
children: [/*#__PURE__*/_jsx(Button, {
|
|
395
|
+
onClick: handleClose,
|
|
396
|
+
color: "primary",
|
|
397
|
+
sx: {
|
|
398
|
+
fontWeight: 600
|
|
399
|
+
},
|
|
400
|
+
children: t("close")
|
|
401
|
+
}), /*#__PURE__*/_jsx(Button, {
|
|
402
|
+
variant: "contained",
|
|
403
|
+
onClick: handlePlay,
|
|
404
|
+
disabled: !canPlay,
|
|
405
|
+
startIcon: /*#__PURE__*/_jsx(LiveTvIcon, {}),
|
|
406
|
+
"data-cy": "play-live",
|
|
407
|
+
sx: {
|
|
408
|
+
fontWeight: 600,
|
|
409
|
+
borderRadius: "25px",
|
|
410
|
+
px: 2.25,
|
|
411
|
+
backgroundColor: "secondary.main",
|
|
412
|
+
"&:hover": {
|
|
413
|
+
backgroundColor: "secondary.dark"
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
children: t("play_live")
|
|
417
|
+
})]
|
|
418
|
+
})]
|
|
44
419
|
})]
|
|
45
420
|
});
|
|
46
421
|
}
|
|
@@ -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
|
});
|