@nualang/nualang-ui-components 0.1.1357 → 0.1.1359

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.
@@ -11,6 +11,7 @@ function SelectExercise({
11
11
  courses,
12
12
  getCourseSections,
13
13
  getRoleplays,
14
+ getPhraseLists,
14
15
  handleCreateGame,
15
16
  username,
16
17
  courseSectionTopicId = ""
@@ -29,6 +30,7 @@ function SelectExercise({
29
30
  courses: courses,
30
31
  getCourseSections: getCourseSections,
31
32
  getRoleplays: getRoleplays,
33
+ getPhraseLists: getPhraseLists,
32
34
  username: username,
33
35
  handleCreateGame: handleCreateGame,
34
36
  t: t,
@@ -222,7 +222,7 @@ function Meaning({
222
222
  const correctPhrase = question.phrase;
223
223
  const otherPhrases = phrases.filter(p => p.phrase !== correctPhrase).map(p => p.phrase);
224
224
  shuffle(otherPhrases);
225
- const distractors = otherPhrases.slice(0, 3);
225
+ const distractors = otherPhrases.slice(0, 4);
226
226
  const options = [correctPhrase, ...distractors].map((text, id) => ({
227
227
  text,
228
228
  id,
@@ -10,6 +10,7 @@ function Course({
10
10
  index,
11
11
  getCourseSections,
12
12
  getRoleplays,
13
+ getPhraseLists,
13
14
  username,
14
15
  handleCreateGame,
15
16
  t,
@@ -99,6 +100,7 @@ function Course({
99
100
  isExerciseSelected: isExerciseSelected,
100
101
  setIsExerciseSelected: setIsExerciseSelected,
101
102
  getRoleplays: getRoleplays,
103
+ getPhraseLists: getPhraseLists,
102
104
  handleSelectExercise: handleSelectExercise,
103
105
  selectedExercises: selectedExercises,
104
106
  useCase: useCase,
@@ -116,6 +118,7 @@ function CourseSelection({
116
118
  courses,
117
119
  getCourseSections,
118
120
  getRoleplays,
121
+ getPhraseLists,
119
122
  username,
120
123
  handleCreateGame,
121
124
  t,
@@ -137,6 +140,7 @@ function CourseSelection({
137
140
  index: index,
138
141
  getCourseSections: getCourseSections,
139
142
  getRoleplays: getRoleplays,
143
+ getPhraseLists: getPhraseLists,
140
144
  username: username,
141
145
  handleCreateGame: handleCreateGame,
142
146
  t: t,
@@ -3,15 +3,13 @@ import { ListSubheader, List, ListItemIcon, ListItemText, ListItemAvatar, Collap
3
3
  import { useTheme } from "@mui/material/styles";
4
4
  import useMediaQuery from "@mui/material/useMediaQuery";
5
5
  import ImageSearchIcon from "@mui/icons-material/ImageSearch";
6
- import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
7
- import HearingIcon from "@mui/icons-material/Hearing";
8
- import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
9
6
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
10
7
  import ExpandLessIcon from "@mui/icons-material/ExpandLess";
11
- import RecordVoiceOverIcon from "@mui/icons-material/RecordVoiceOver";
12
8
  import ForumIcon from "@mui/icons-material/Forum";
13
9
  import OndemandVideoIcon from "@mui/icons-material/OndemandVideo";
14
10
  import RoleplaySelection from "../RoleplaySelection/RoleplaySelection";
11
+ import PhraseListSelection from "../PhraseListSelection/PhraseListSelection";
12
+ import MenuBookIcon from '@mui/icons-material/MenuBook';
15
13
  import { CardInfoSectionItem } from "../../Cards/CardElements";
16
14
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
17
15
  function Exercise({
@@ -19,71 +17,43 @@ function Exercise({
19
17
  description,
20
18
  courseSectionTopicId,
21
19
  handleCreateGame,
22
- section,
23
20
  t,
24
21
  isExerciseSelected,
25
- setIsExerciseSelected,
26
22
  roleplays,
27
- phrases,
28
23
  handleSelectExercise = null,
29
24
  useCase,
30
- isCreator
25
+ getPhraseLists,
26
+ courseId,
27
+ sectionId,
28
+ topicId,
29
+ setIsExerciseSelected,
30
+ phrases
31
31
  }) {
32
- const [listeningHidden, setListeningHidden] = useState(false);
33
- const [translationHidden, setTranslationHidden] = useState(false);
34
- const [pronunciationHidden, setPronunciationHidden] = useState(false);
35
32
  const noRoleplays = name.toLowerCase() === "roleplays" && roleplays.length === 0;
33
+ const noPhrases = name.toLowerCase() === "phrases" && !phrases?.length && !getPhraseLists;
36
34
  const [roleplaysOpen, setRoleplaysOpen] = useState(noRoleplays ? false : true);
37
- const handleToggleRoleplays = () => {
38
- setRoleplaysOpen(!roleplaysOpen);
39
- };
40
- const handleExerciseSelected = () => {
41
- setIsExerciseSelected(true);
42
- handleCreateGame({
43
- courseSectionTopicId: courseSectionTopicId,
44
- exercise: name,
45
- timer: 30,
46
- answerOption: 0
47
- });
48
- };
49
- useEffect(() => {
50
- section.topics.forEach(topic => {
51
- if (topic.hiddenExercises && topic.hiddenExercises.includes("listening")) {
52
- setListeningHidden(true);
53
- }
54
- if (topic.hiddenExercises && topic.hiddenExercises.includes("translation")) {
55
- setTranslationHidden(true);
56
- }
57
- if (topic.hiddenExercises && topic.hiddenExercises.includes("pronunciation")) {
58
- setPronunciationHidden(true);
59
- }
60
- });
61
- }, [section]);
62
- const isValidExercise = phrases.length >= 2 || name.toLowerCase() === "roleplays";
63
- if (name.toLowerCase() === "listening" && listeningHidden || name.toLowerCase() === "translation" && translationHidden || name.toLowerCase() === "pronunciation" && pronunciationHidden) {
64
- return null;
65
- }
35
+ const [phraseListsOpen, setPhraseListsOpen] = useState(false);
66
36
  return /*#__PURE__*/_jsxs(Tooltip, {
67
- title: name === "roleplays" && noRoleplays ? t("topic_no_roleplays") : !isValidExercise ? t("minimum_two_phrases") : "",
37
+ title: (name === "roleplays" && noRoleplays ? t("topic_no_roleplays") : "") || (name === "phrases" && noPhrases ? t("topic_no_phrases") : ""),
68
38
  children: [/*#__PURE__*/_jsxs(ListItemButton, {
69
39
  onClick: () => {
70
- name.toLowerCase() === "roleplays" ? handleToggleRoleplays() : handleExerciseSelected();
40
+ if (name.toLowerCase() === "roleplays") {
41
+ setRoleplaysOpen(!roleplaysOpen);
42
+ } else if (name.toLowerCase() === "phrases") {
43
+ setPhraseListsOpen(!phraseListsOpen);
44
+ }
71
45
  },
72
46
  display: "flex",
73
47
  alignItems: "center",
74
48
  justifyContent: "space-between",
75
- disabled: isExerciseSelected || !isValidExercise || noRoleplays,
49
+ disabled: isExerciseSelected || noRoleplays || noPhrases,
76
50
  sx: theme => ({
77
51
  [theme.breakpoints.up("sm")]: {
78
52
  ml: 3.5
79
53
  }
80
54
  }),
81
- children: [name.toLowerCase() === "listening" && /*#__PURE__*/_jsx(ListItemIcon, {
82
- children: /*#__PURE__*/_jsx(HearingIcon, {})
83
- }), name.toLowerCase() === "translation" && /*#__PURE__*/_jsx(ListItemIcon, {
84
- children: /*#__PURE__*/_jsx(SwapHorizIcon, {})
85
- }), name.toLowerCase() === "pronunciation" && /*#__PURE__*/_jsx(ListItemIcon, {
86
- children: /*#__PURE__*/_jsx(RecordVoiceOverIcon, {})
55
+ children: [name.toLowerCase() === "phrases" && /*#__PURE__*/_jsx(ListItemIcon, {
56
+ children: /*#__PURE__*/_jsx(MenuBookIcon, {})
87
57
  }), name.toLowerCase() === "roleplays" && /*#__PURE__*/_jsx(ListItemIcon, {
88
58
  children: /*#__PURE__*/_jsx(ForumIcon, {})
89
59
  }), /*#__PURE__*/_jsx(Box, {
@@ -92,13 +62,30 @@ function Exercise({
92
62
  primary: t(name),
93
63
  secondary: t(description)
94
64
  })
65
+ }), name.toLowerCase() === "phrases" && /*#__PURE__*/_jsx(ListItemIcon, {
66
+ children: phraseListsOpen ? /*#__PURE__*/_jsx(ExpandLessIcon, {}) : /*#__PURE__*/_jsx(ExpandMoreIcon, {})
95
67
  }), name.toLowerCase() === "roleplays" && /*#__PURE__*/_jsx(ListItemIcon, {
96
68
  children: roleplaysOpen ? /*#__PURE__*/_jsx(ExpandLessIcon, {}) : /*#__PURE__*/_jsx(ExpandMoreIcon, {})
97
- }), (name.toLowerCase() === "listening" || name.toLowerCase() === "translation" || name.toLowerCase() === "pronunciation") && /*#__PURE__*/_jsx(ListItemIcon, {
98
- children: /*#__PURE__*/_jsx(PlayCircleOutlineIcon, {
99
- color: "primary"
100
- })
101
69
  })]
70
+ }), name.toLowerCase() === "phrases" && getPhraseLists && /*#__PURE__*/_jsx(Collapse, {
71
+ in: phraseListsOpen,
72
+ timeout: "auto",
73
+ unmountOnExit: true,
74
+ children: /*#__PURE__*/_jsx(Box, {
75
+ ml: 7,
76
+ children: /*#__PURE__*/_jsx(PhraseListSelection, {
77
+ getPhraseLists: getPhraseLists,
78
+ courseId: courseId,
79
+ sectionId: sectionId,
80
+ topicId: topicId,
81
+ handleCreateGame: handleCreateGame,
82
+ courseSectionTopicId: courseSectionTopicId,
83
+ t: t,
84
+ isExerciseSelected: isExerciseSelected,
85
+ setIsExerciseSelected: setIsExerciseSelected,
86
+ isExpanded: phraseListsOpen
87
+ })
88
+ })
102
89
  }), name.toLowerCase() === "roleplays" && /*#__PURE__*/_jsx(Collapse, {
103
90
  in: roleplaysOpen,
104
91
  timeout: "auto",
@@ -135,6 +122,7 @@ function ExerciseList({
135
122
  useCase,
136
123
  handleStartExercise,
137
124
  isCreator,
125
+ getPhraseLists,
138
126
  ...otherProps
139
127
  }) {
140
128
  const courseSectionTopicId = `${courseId}|${sectionId}|${topicId}`;
@@ -161,6 +149,7 @@ function ExerciseList({
161
149
  useCase: useCase,
162
150
  handleStartExercise: handleStartExercise,
163
151
  isCreator: isCreator,
152
+ getPhraseLists: getPhraseLists,
164
153
  ...otherProps
165
154
  }, keyId);
166
155
  })
@@ -199,6 +188,7 @@ function Topic({
199
188
  handleStartExercise,
200
189
  isCreator,
201
190
  courseSectionTopicId,
191
+ getPhraseLists,
202
192
  ...otherProps
203
193
  }) {
204
194
  const theme = useTheme();
@@ -226,11 +216,8 @@ function Topic({
226
216
  }
227
217
  }, [courseSectionTopicId, courseId, sectionId, topicId]);
228
218
  const exercises = [{
229
- name: "translation",
230
- description: `translation_exercise_desc`
231
- }, {
232
- name: "listening",
233
- description: `listening_exercise_desc`
219
+ name: "phrases",
220
+ description: `phrases_exercise_desc`
234
221
  }, {
235
222
  name: "roleplays",
236
223
  description: `roleplay_exercise_desc`
@@ -241,7 +228,7 @@ function Topic({
241
228
  const hasNonEmptyRoleplay = roleplays.some(roleplay => roleplay?.script?.length !== 0 && roleplay?.script?.some(line => line.text !== undefined));
242
229
  return /*#__PURE__*/_jsx(_Fragment, {
243
230
  children: /*#__PURE__*/_jsxs(Tooltip, {
244
- title: phrases.length === 0 ? t("topic_no_phrases") : "",
231
+ title: !phrases?.length && !getPhraseLists ? t("topic_no_phrases") : "",
245
232
  placement: "top",
246
233
  children: [!isTopicHidden && /*#__PURE__*/_jsxs(ListItemButton, {
247
234
  ref: topicRef,
@@ -251,7 +238,7 @@ function Topic({
251
238
  justifyContent: "space-between",
252
239
  onClick: toggleExerciseList,
253
240
  "data-cy": "topic-list-item",
254
- disabled: phrases.length === 0 && !hasNonEmptyRoleplay || isExerciseSelected,
241
+ disabled: !phrases?.length && !getPhraseLists && !hasNonEmptyRoleplay || isExerciseSelected,
255
242
  children: [isLargeScreen && /*#__PURE__*/_jsx(ListItemAvatar, {
256
243
  children: picture ? /*#__PURE__*/_jsx(Avatar, {
257
244
  src: picture,
@@ -329,6 +316,7 @@ function Topic({
329
316
  useCase: useCase,
330
317
  handleStartExercise: handleStartExercise,
331
318
  isCreator: isCreator,
319
+ getPhraseLists: getPhraseLists,
332
320
  ...otherProps
333
321
  })
334
322
  })]
@@ -352,6 +340,7 @@ function Section({
352
340
  handleStartExercise,
353
341
  isCreator,
354
342
  courseSectionTopicId,
343
+ getPhraseLists,
355
344
  ...otherProps
356
345
  }) {
357
346
  const root = {
@@ -390,7 +379,8 @@ function Section({
390
379
  useCase: useCase,
391
380
  handleStartExercise: handleStartExercise,
392
381
  isCreator: isCreator,
393
- courseSectionTopicId: courseSectionTopicId
382
+ courseSectionTopicId: courseSectionTopicId,
383
+ getPhraseLists: getPhraseLists
394
384
  }, `${sectionId}-${topic.topicId}`))
395
385
  })
396
386
  });
@@ -410,6 +400,7 @@ function SectionList({
410
400
  handleStartExercise,
411
401
  isCreator,
412
402
  courseSectionTopicId,
403
+ getPhraseLists,
413
404
  ...otherProps
414
405
  }) {
415
406
  return /*#__PURE__*/_jsx(_Fragment, {
@@ -431,6 +422,7 @@ function SectionList({
431
422
  handleStartExercise: handleStartExercise,
432
423
  isCreator: isCreator,
433
424
  courseSectionTopicId: courseSectionTopicId,
425
+ getPhraseLists: getPhraseLists,
434
426
  ...otherProps
435
427
  }, section.sectionId);
436
428
  }) : /*#__PURE__*/_jsx(Typography, {
@@ -457,6 +449,7 @@ function ExerciseSelection({
457
449
  useCase,
458
450
  handleStartExercise,
459
451
  courseSectionTopicId,
452
+ getPhraseLists,
460
453
  ...otherProps
461
454
  }) {
462
455
  if (isLoading) {
@@ -477,6 +470,7 @@ function ExerciseSelection({
477
470
  getRoleplays: getRoleplays,
478
471
  handleStartExercise: handleStartExercise,
479
472
  courseSectionTopicId: courseSectionTopicId,
473
+ getPhraseLists: getPhraseLists,
480
474
  ...otherProps
481
475
  })
482
476
  });
@@ -498,6 +492,7 @@ function ExerciseSelection({
498
492
  handleSelectExercise: handleSelectExercise,
499
493
  useCase: useCase,
500
494
  courseSectionTopicId: courseSectionTopicId,
495
+ getPhraseLists: getPhraseLists,
501
496
  ...otherProps
502
497
  })
503
498
  });
@@ -1027,7 +1027,7 @@ function Exercises(props) {
1027
1027
  };
1028
1028
  const numberOfPhraseListCompletions = (completions, phraseListId) => {
1029
1029
  if (isEmptyArray(completions)) return 0;
1030
- return completions.filter(c => ["translation", "listening", "pronunciation"].includes(c.exercise) && c.phraseListId === phraseListId).length;
1030
+ return completions.filter(c => ["translation", "listening", "pronunciation"].includes(c.exercise) && (c.phraseListId === phraseListId || !c.phraseListId && phraseListId?.startsWith('leg-'))).length;
1031
1031
  };
1032
1032
 
1033
1033
  // ── phraseLists drag state ──
@@ -0,0 +1,206 @@
1
+ import { useState } from "react";
2
+ import { Box, Button, ListItemText, ListItemButton, Avatar, ListItemIcon, Collapse, CircularProgress, Typography } from "@mui/material";
3
+ import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
4
+ import HearingIcon from "@mui/icons-material/Hearing";
5
+ import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
6
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
7
+ import ExpandLessIcon from "@mui/icons-material/ExpandLess";
8
+ import ImageSearchIcon from "@mui/icons-material/ImageSearch";
9
+ import ExtensionIcon from "@mui/icons-material/Extension";
10
+ import { courses as courseQuerys } from "@nualang/nualang-api-and-queries/Queries";
11
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ function PhraseList({
13
+ phraseList,
14
+ handleCreateGame,
15
+ courseSectionTopicId,
16
+ isPhraseListSelected,
17
+ setIsPhraseListSelected,
18
+ t,
19
+ isExerciseSelected,
20
+ setIsExerciseSelected
21
+ }) {
22
+ const [open, setOpen] = useState(false);
23
+ const isTranslationHidden = phraseList?.hiddenPhraseGroupGames?.translation;
24
+ const isListeningHidden = phraseList?.hiddenPhraseGroupGames?.listening;
25
+ const isMeaningHidden = phraseList?.hiddenPhraseGroupGames?.meaning;
26
+ const hasSelectableGame = !isTranslationHidden || !isListeningHidden || !isMeaningHidden;
27
+ if (!hasSelectableGame) return null;
28
+ const handleGameSelected = exercise => () => {
29
+ setIsPhraseListSelected(true);
30
+ setIsExerciseSelected(true);
31
+ handleCreateGame({
32
+ courseSectionTopicId,
33
+ exercise,
34
+ timer: 30,
35
+ answerOption: 0,
36
+ phraseListId: phraseList.phraseListId,
37
+ phrases: phraseList.phrases || []
38
+ });
39
+ };
40
+ return /*#__PURE__*/_jsxs("div", {
41
+ children: [/*#__PURE__*/_jsxs(ListItemButton, {
42
+ onClick: () => setOpen(!open),
43
+ disabled: isPhraseListSelected || isExerciseSelected,
44
+ sx: {
45
+ display: "flex",
46
+ alignItems: "center",
47
+ justifyContent: "space-between",
48
+ flexDirection: {
49
+ xs: "column",
50
+ sm: "row"
51
+ }
52
+ },
53
+ children: [/*#__PURE__*/_jsxs(Box, {
54
+ display: "flex",
55
+ alignItems: "center",
56
+ width: {
57
+ xs: "100%",
58
+ sm: "85%"
59
+ },
60
+ children: [phraseList.picture ? /*#__PURE__*/_jsx(Avatar, {
61
+ src: phraseList.picture,
62
+ alt: phraseList.phraseListName
63
+ }) : /*#__PURE__*/_jsx(Avatar, {
64
+ alt: phraseList.phraseListName,
65
+ children: /*#__PURE__*/_jsx(ImageSearchIcon, {})
66
+ }), /*#__PURE__*/_jsx(ListItemText, {
67
+ primary: phraseList.phraseListName,
68
+ secondary: phraseList.description,
69
+ sx: {
70
+ marginLeft: 2
71
+ }
72
+ })]
73
+ }), /*#__PURE__*/_jsx(ListItemIcon, {
74
+ children: open ? /*#__PURE__*/_jsx(ExpandLessIcon, {
75
+ sx: {
76
+ color: "#757575"
77
+ }
78
+ }) : /*#__PURE__*/_jsx(ExpandMoreIcon, {
79
+ sx: {
80
+ color: "#757575"
81
+ }
82
+ })
83
+ })]
84
+ }), /*#__PURE__*/_jsx(Collapse, {
85
+ in: open,
86
+ timeout: "auto",
87
+ unmountOnExit: true,
88
+ children: /*#__PURE__*/_jsxs(Box, {
89
+ ml: 3.5,
90
+ children: [!isTranslationHidden && /*#__PURE__*/_jsxs(ListItemButton, {
91
+ onClick: handleGameSelected("translation"),
92
+ disabled: isPhraseListSelected || isExerciseSelected,
93
+ children: [/*#__PURE__*/_jsx(ListItemIcon, {
94
+ children: /*#__PURE__*/_jsx(SwapHorizIcon, {})
95
+ }), /*#__PURE__*/_jsx(ListItemText, {
96
+ primary: t("translation"),
97
+ secondary: t("translation_exercise_desc")
98
+ }), /*#__PURE__*/_jsx(ListItemIcon, {
99
+ children: /*#__PURE__*/_jsx(PlayCircleOutlineIcon, {
100
+ color: "primary"
101
+ })
102
+ })]
103
+ }), !isListeningHidden && /*#__PURE__*/_jsxs(ListItemButton, {
104
+ onClick: handleGameSelected("listening"),
105
+ disabled: isPhraseListSelected || isExerciseSelected,
106
+ children: [/*#__PURE__*/_jsx(ListItemIcon, {
107
+ children: /*#__PURE__*/_jsx(HearingIcon, {})
108
+ }), /*#__PURE__*/_jsx(ListItemText, {
109
+ primary: t("listening"),
110
+ secondary: t("listening_exercise_desc")
111
+ }), /*#__PURE__*/_jsx(ListItemIcon, {
112
+ children: /*#__PURE__*/_jsx(PlayCircleOutlineIcon, {
113
+ color: "primary"
114
+ })
115
+ })]
116
+ }), !isMeaningHidden && /*#__PURE__*/_jsxs(ListItemButton, {
117
+ onClick: handleGameSelected("meaning"),
118
+ disabled: isPhraseListSelected || isExerciseSelected,
119
+ children: [/*#__PURE__*/_jsx(ListItemIcon, {
120
+ children: /*#__PURE__*/_jsx(ExtensionIcon, {})
121
+ }), /*#__PURE__*/_jsx(ListItemText, {
122
+ primary: t("meaning"),
123
+ secondary: t("meaning_exercise_desc")
124
+ }), /*#__PURE__*/_jsx(ListItemIcon, {
125
+ children: /*#__PURE__*/_jsx(PlayCircleOutlineIcon, {
126
+ color: "primary"
127
+ })
128
+ })]
129
+ })]
130
+ })
131
+ })]
132
+ });
133
+ }
134
+ function PhraseListSelection({
135
+ getPhraseLists,
136
+ courseId,
137
+ sectionId,
138
+ topicId,
139
+ handleCreateGame,
140
+ courseSectionTopicId,
141
+ t,
142
+ isExerciseSelected,
143
+ isExpanded,
144
+ setIsExerciseSelected
145
+ }) {
146
+ const [isPhraseListSelected, setIsPhraseListSelected] = useState(false);
147
+ const phraseListsQuery = courseQuerys.useCourseSectionTopicPhraseLists(async (cId, sId, tId, filters) => getPhraseLists(cId, sId, tId, filters), {
148
+ courseId,
149
+ sectionId,
150
+ topicId,
151
+ filters: {
152
+ limit: 1000,
153
+ returnvalues: "phrases"
154
+ }
155
+ }, {
156
+ enabled: !!courseId && !!sectionId && !!topicId && isExpanded
157
+ });
158
+ const phraseLists = phraseListsQuery.isSuccess && Array.isArray(phraseListsQuery.data?.Items) ? phraseListsQuery.data.Items : [];
159
+ if (phraseListsQuery.isError) {
160
+ return /*#__PURE__*/_jsxs(Box, {
161
+ px: 2,
162
+ py: 1,
163
+ children: [/*#__PURE__*/_jsx(Typography, {
164
+ variant: "body2",
165
+ color: "error",
166
+ children: t("phrase_lists_error")
167
+ }), /*#__PURE__*/_jsx(Button, {
168
+ size: "small",
169
+ onClick: () => phraseListsQuery.refetch(),
170
+ children: t("retry")
171
+ })]
172
+ });
173
+ }
174
+ if (phraseListsQuery.isLoading) {
175
+ return /*#__PURE__*/_jsx(Box, {
176
+ display: "flex",
177
+ justifyContent: "center",
178
+ py: 1,
179
+ children: /*#__PURE__*/_jsx(CircularProgress, {
180
+ size: 24
181
+ })
182
+ });
183
+ }
184
+ if (phraseListsQuery.isSuccess && phraseLists.length === 0) {
185
+ return /*#__PURE__*/_jsx(Box, {
186
+ px: 2,
187
+ py: 1,
188
+ children: /*#__PURE__*/_jsx(Typography, {
189
+ variant: "body2",
190
+ color: "textSecondary",
191
+ children: t("no_phrase_lists")
192
+ })
193
+ });
194
+ }
195
+ return phraseLists.map((phraseList, index) => /*#__PURE__*/_jsx(PhraseList, {
196
+ phraseList: phraseList,
197
+ handleCreateGame: handleCreateGame,
198
+ courseSectionTopicId: courseSectionTopicId,
199
+ isPhraseListSelected: isPhraseListSelected,
200
+ setIsPhraseListSelected: setIsPhraseListSelected,
201
+ t: t,
202
+ isExerciseSelected: isExerciseSelected,
203
+ setIsExerciseSelected: setIsExerciseSelected
204
+ }, phraseList.phraseListId || index));
205
+ }
206
+ export default PhraseListSelection;
@@ -16,6 +16,7 @@ import WaitingRoom from "../WaitingRoom/WaitingRoom";
16
16
  import LiveListener from "../LiveListener/LiveListener";
17
17
  import LiveTranslator from "../LiveTranslator/LiveTranslator";
18
18
  import LiveStory from "../LiveRoleplay/LiveStory/LiveStory";
19
+ import LiveMeaning from "../LiveMeaning/LiveMeaning";
19
20
  import HowToJoin from "../HowToJoin/HowToJoin";
20
21
  import GameMembers from "../GameMembers/GameMembers";
21
22
  import GameSettings from "../GameSettings/GameSettings";
@@ -510,6 +511,23 @@ export default function Game({
510
511
  handleShowLeaderboard: handleShowLeaderboard,
511
512
  ...otherProps
512
513
  });
514
+ } else if (exercise === "meaning") {
515
+ return /*#__PURE__*/_jsx(LiveMeaning, {
516
+ t: t,
517
+ currentQuestion: currentQuestion,
518
+ isCreator: isCreator,
519
+ languageInformation: languageInformation,
520
+ time: time,
521
+ setCurrentAnswer: setCurrentAnswer,
522
+ gameMembersTotal: membersInRoom?.length,
523
+ exercise: exercise,
524
+ voiceLanguageCode: voiceLanguageCode,
525
+ voice: voice,
526
+ voices: voices,
527
+ answerAttempt: answerAttempt,
528
+ handleShowLeaderboard: handleShowLeaderboard,
529
+ ...otherProps
530
+ });
513
531
  } else if (exercise === "roleplay-story") {
514
532
  return /*#__PURE__*/_jsx(LiveStory, {
515
533
  t: t,
@@ -0,0 +1,380 @@
1
+ import { useState, useEffect } from "react";
2
+ import { Grid, Button, Typography, Box } from "@mui/material";
3
+ import { makeStyles } from "tss-react/mui";
4
+ import PauseIcon from "@mui/icons-material/Pause";
5
+ import PlayIcon from "@mui/icons-material/PlayArrow";
6
+ import { CountdownCircleTimer } from "react-countdown-circle-timer";
7
+ import { useTheme } from "@mui/material/styles";
8
+ import AnswerResult from "../../Misc/AnswerResult/AnswerResult";
9
+ import ExerciseBottomBar from "../../Misc/ExerciseBottomBar/ExerciseBottomBar";
10
+ import DefaultButton from "../../Misc/DefaultColourButton/DefaultColourButton";
11
+ import ChatBubble from "../../Misc/ChatBubble/ChatBubble";
12
+ import WaveFormLite from "../../Misc/WaveFormLite/WaveFormLite";
13
+ import GameControls from "../GameControls/GameControls";
14
+ import GameQuestionResults from "../GameQuestionResults/GameQuestionResults";
15
+ import getVoiceImages from "../../utils/voiceImages";
16
+ import { removeExtraWhiteSpaces } from "../../utils/index";
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ const useStyles = makeStyles()(theme => ({
19
+ flex: {
20
+ [theme.breakpoints.down("md")]: {
21
+ display: "none"
22
+ },
23
+ flex: 1
24
+ },
25
+ root: {
26
+ [theme.breakpoints.down("md")]: {
27
+ padding: theme.spacing(2)
28
+ },
29
+ flexGrow: 1,
30
+ width: "100%",
31
+ padding: theme.spacing(4)
32
+ },
33
+ image: {
34
+ [theme.breakpoints.down("md")]: {
35
+ maxWidth: "200px",
36
+ maxHeight: "150px"
37
+ },
38
+ maxWidth: "250px",
39
+ height: "auto",
40
+ maxHeight: "200px",
41
+ borderRadius: "20px"
42
+ },
43
+ nualaSpeaking: {
44
+ [theme.breakpoints.down("md")]: {
45
+ height: "80px"
46
+ },
47
+ height: "100px",
48
+ minHeight: "80px",
49
+ minWidth: "80px",
50
+ borderRadius: "20px"
51
+ },
52
+ wordButton: {
53
+ textTransform: "none"
54
+ }
55
+ }));
56
+ function LiveMeaning({
57
+ t = text => text,
58
+ languageInformation,
59
+ isCreator,
60
+ currentQuestion,
61
+ isTimeUp,
62
+ time,
63
+ handleTimeUp,
64
+ handleShowLeaderboard,
65
+ wordOptions,
66
+ isAnswered,
67
+ setIsAnswered,
68
+ answerAttempt,
69
+ isCorrectResultOpen,
70
+ isIncorrectResultOpen,
71
+ handleContinue,
72
+ handleNextQuestion,
73
+ setCurrentAnswer,
74
+ gameMembersAnsweredTotal,
75
+ gameMembersTotal,
76
+ gameMembersCorrectAnswerTotal,
77
+ exercise,
78
+ voice,
79
+ voicePitch,
80
+ voiceLanguageCode,
81
+ voices,
82
+ pollyResult,
83
+ isSpeaking,
84
+ speak,
85
+ learnLang = "",
86
+ forLang = "",
87
+ languageTag
88
+ }) {
89
+ const {
90
+ classes
91
+ } = useStyles();
92
+ const theme = useTheme();
93
+ const [isPaused, setIsPaused] = useState(false);
94
+ const [selectedOption, setSelectedOption] = useState(null);
95
+ const [isFinishedInitialSpeaking, setIsFinishedInitialSpeaking] = useState(false);
96
+ useEffect(() => {
97
+ setSelectedOption(null);
98
+ setIsFinishedInitialSpeaking(false);
99
+ }, [currentQuestion]);
100
+ const definitionText = (() => {
101
+ if (!currentQuestion) return "";
102
+ if (currentQuestion.definitions?.length > 0) {
103
+ const raw = currentQuestion.definitions[0];
104
+ return typeof raw === "string" ? raw : "";
105
+ }
106
+ if (currentQuestion.translations?.length > 0) {
107
+ const raw = currentQuestion.translations[0];
108
+ return typeof raw === "string" ? raw : "";
109
+ }
110
+ return "";
111
+ })();
112
+ useEffect(() => {
113
+ if (isCreator && currentQuestion && definitionText !== "") {
114
+ speak(definitionText, voice, false, "#currentDefinition", voiceLanguageCode, false, voicePitch, () => setIsFinishedInitialSpeaking(true));
115
+ }
116
+ return () => {
117
+ // Cleanup: cancel/stop any in-flight speak
118
+ };
119
+ }, [speak, definitionText, voice, voiceLanguageCode, voicePitch, currentQuestion?.phrase, isCreator]);
120
+ const voiceMap = getVoiceImages(voices || []);
121
+ const getVoiceImage = () => {
122
+ try {
123
+ const exerciseVoice = voiceMap.get(voice) || {};
124
+ return exerciseVoice.speakingImage || null;
125
+ } catch (error) {
126
+ console.error("Error retrieving voice image:", error);
127
+ return null;
128
+ }
129
+ };
130
+ const handleOptionSelect = option => {
131
+ if (isAnswered) return;
132
+ setSelectedOption(option);
133
+ setCurrentAnswer(option.text);
134
+ };
135
+ return /*#__PURE__*/_jsxs(_Fragment, {
136
+ children: [/*#__PURE__*/_jsxs(Grid, {
137
+ container: true,
138
+ className: classes.root,
139
+ direction: "column",
140
+ justifyContent: "center",
141
+ alignItems: "center",
142
+ spacing: 2,
143
+ sx: {
144
+ maxWidth: "lg",
145
+ mx: "auto"
146
+ },
147
+ component: "main",
148
+ children: [/*#__PURE__*/_jsx(Grid, {
149
+ children: /*#__PURE__*/_jsx(Typography, {
150
+ lang: languageTag,
151
+ variant: "h6",
152
+ component: "h2",
153
+ color: "inherit",
154
+ children: t("select_the_correct_phrase", {
155
+ lng: languageTag
156
+ })
157
+ })
158
+ }), currentQuestion?.image && /*#__PURE__*/_jsx(Grid, {
159
+ children: /*#__PURE__*/_jsx("img", {
160
+ src: currentQuestion.image,
161
+ alt: "",
162
+ className: classes.image
163
+ })
164
+ }), isCreator && /*#__PURE__*/_jsx(Grid, {
165
+ children: /*#__PURE__*/_jsxs(Box, {
166
+ sx: {
167
+ display: "flex",
168
+ justifyContent: "center",
169
+ flexDirection: "row"
170
+ },
171
+ children: [/*#__PURE__*/_jsx(Box, {
172
+ sx: {
173
+ display: "flex",
174
+ justifyContent: "center"
175
+ },
176
+ children: /*#__PURE__*/_jsx("img", {
177
+ src: getVoiceImage(),
178
+ className: classes.nualaSpeaking,
179
+ alt: ""
180
+ })
181
+ }), /*#__PURE__*/_jsxs(Box, {
182
+ sx: {
183
+ display: "grid",
184
+ justifyContent: "flex-end",
185
+ marginLeft: 1
186
+ },
187
+ children: [/*#__PURE__*/_jsx(ChatBubble, {
188
+ id: "currentDefinition",
189
+ handleSpeak: () => speak(definitionText, voice, true, null, voiceLanguageCode, false, voicePitch),
190
+ learnLang: learnLang,
191
+ voice: voice || null,
192
+ voicePitch: voicePitch || null,
193
+ forLang: forLang,
194
+ wordList: definitionText ? removeExtraWhiteSpaces(definitionText).split(" ") : [],
195
+ disableTranslation: false,
196
+ isMessage: false,
197
+ t: t
198
+ }), learnLang !== "irish" && /*#__PURE__*/_jsx(Box, {
199
+ children: /*#__PURE__*/_jsx(WaveFormLite, {
200
+ src: pollyResult ? pollyResult : "",
201
+ controls: false,
202
+ isSpeaking: isSpeaking,
203
+ muteAudio: true,
204
+ handleSpeak: () => speak(definitionText, voice, true, null, voiceLanguageCode, false, voicePitch),
205
+ theme: theme
206
+ })
207
+ })]
208
+ })]
209
+ })
210
+ }), isCreator ? /*#__PURE__*/_jsxs(Grid, {
211
+ sx: {
212
+ marginTop: 2,
213
+ textAlign: "center"
214
+ },
215
+ children: [!isAnswered && /*#__PURE__*/_jsxs(_Fragment, {
216
+ children: [!isTimeUp && /*#__PURE__*/_jsxs(Box, {
217
+ children: [/*#__PURE__*/_jsx(CountdownCircleTimer, {
218
+ isPlaying: !isPaused && isFinishedInitialSpeaking,
219
+ duration: time,
220
+ colors: theme.palette.primary.main,
221
+ size: 160,
222
+ onComplete: handleTimeUp,
223
+ children: ({
224
+ remainingTime
225
+ }) => /*#__PURE__*/_jsx(Typography, {
226
+ variant: "h2",
227
+ fontWeight: "fontWeightBold",
228
+ children: remainingTime
229
+ })
230
+ }), /*#__PURE__*/_jsx(Button, {
231
+ variant: "contained",
232
+ color: "primary",
233
+ sx: {
234
+ marginTop: 2,
235
+ paddingLeft: 1.5,
236
+ paddingRight: 1.5
237
+ },
238
+ onClick: () => setIsPaused(!isPaused),
239
+ startIcon: isPaused ? /*#__PURE__*/_jsx(PlayIcon, {}) : /*#__PURE__*/_jsx(PauseIcon, {}),
240
+ size: "small",
241
+ disabled: !isFinishedInitialSpeaking,
242
+ children: isPaused ? t("resume") : t("pause")
243
+ })]
244
+ }), isTimeUp && /*#__PURE__*/_jsxs(Box, {
245
+ sx: {
246
+ textAlign: "center"
247
+ },
248
+ children: [/*#__PURE__*/_jsx(Typography, {
249
+ variant: "h5",
250
+ fontWeight: "fontWeightBold",
251
+ textAlign: "center",
252
+ color: "success.main",
253
+ children: currentQuestion?.phrase
254
+ }), /*#__PURE__*/_jsx(Button, {
255
+ variant: "contained",
256
+ color: "primary",
257
+ sx: {
258
+ marginTop: 5
259
+ },
260
+ onClick: () => handleShowLeaderboard(),
261
+ children: t("next")
262
+ }), /*#__PURE__*/_jsx(GameQuestionResults, {
263
+ t: t,
264
+ gameMembersCorrectAnswerTotal: gameMembersCorrectAnswerTotal,
265
+ gameMembersTotal: gameMembersTotal
266
+ })]
267
+ }), !isTimeUp && /*#__PURE__*/_jsx(GameControls, {
268
+ t: t,
269
+ handleTimeUp: handleTimeUp,
270
+ gameMembersAnsweredTotal: gameMembersAnsweredTotal,
271
+ gameMembersTotal: gameMembersTotal
272
+ })]
273
+ }), isAnswered && /*#__PURE__*/_jsxs(Box, {
274
+ sx: {
275
+ marginTop: 2
276
+ },
277
+ children: [/*#__PURE__*/_jsx(Typography, {
278
+ variant: "h5",
279
+ fontWeight: "fontWeightBold",
280
+ textAlign: "center",
281
+ children: currentQuestion?.phrase || ""
282
+ }), /*#__PURE__*/_jsx(Button, {
283
+ variant: "contained",
284
+ color: "primary",
285
+ sx: {
286
+ marginTop: 5
287
+ },
288
+ onClick: () => handleShowLeaderboard(),
289
+ children: t("next")
290
+ }), /*#__PURE__*/_jsx(GameQuestionResults, {
291
+ t: t,
292
+ gameMembersCorrectAnswerTotal: gameMembersCorrectAnswerTotal,
293
+ gameMembersTotal: gameMembersTotal
294
+ })]
295
+ })]
296
+ }) : /*#__PURE__*/_jsx(Grid, {
297
+ sx: {
298
+ marginTop: 2,
299
+ width: "100%"
300
+ },
301
+ children: !isAnswered ? /*#__PURE__*/_jsxs(Box, {
302
+ component: "fieldset",
303
+ sx: {
304
+ borderWidth: 0
305
+ },
306
+ children: [/*#__PURE__*/_jsx(Typography, {
307
+ variant: "subtitle1",
308
+ color: "inherit",
309
+ gutterBottom: true,
310
+ component: "legend",
311
+ sx: {
312
+ textAlign: "center",
313
+ paddingBottom: 1
314
+ },
315
+ children: t("select_the_correct_answer")
316
+ }), /*#__PURE__*/_jsx(Grid, {
317
+ children: /*#__PURE__*/_jsx(Grid, {
318
+ container: true,
319
+ sx: {
320
+ width: "100%"
321
+ },
322
+ justifyContent: "center",
323
+ spacing: 1,
324
+ children: (wordOptions || []).map(option => /*#__PURE__*/_jsx(Grid, {
325
+ children: /*#__PURE__*/_jsx(DefaultButton, {
326
+ variant: selectedOption?.id === option.id ? "contained" : "outlined",
327
+ onClick: () => handleOptionSelect(option),
328
+ className: classes.wordButton,
329
+ children: option.text
330
+ })
331
+ }, option.id))
332
+ })
333
+ })]
334
+ }) : /*#__PURE__*/_jsx(Box, {
335
+ sx: {
336
+ marginTop: 2,
337
+ textAlign: "center"
338
+ },
339
+ children: /*#__PURE__*/_jsx(Typography, {
340
+ variant: "h5",
341
+ fontWeight: "fontWeightBold",
342
+ textAlign: "center",
343
+ children: selectedOption?.text || ""
344
+ })
345
+ })
346
+ })]
347
+ }), /*#__PURE__*/_jsx(AnswerResult, {
348
+ t: t,
349
+ open: isCorrectResultOpen,
350
+ handleNext: handleNextQuestion,
351
+ ...answerAttempt,
352
+ result: "correct",
353
+ languageInformation: languageInformation,
354
+ isLive: true,
355
+ exercise: exercise
356
+ }), /*#__PURE__*/_jsx(AnswerResult, {
357
+ t: t,
358
+ open: isIncorrectResultOpen,
359
+ handleNext: handleNextQuestion,
360
+ ...answerAttempt,
361
+ result: "incorrect",
362
+ languageInformation: languageInformation,
363
+ isLive: true,
364
+ exercise: exercise
365
+ }), !isCreator && /*#__PURE__*/_jsx(ExerciseBottomBar, {
366
+ t: t,
367
+ handleSubmit: () => handleContinue(false),
368
+ handleSkip: () => handleContinue(true),
369
+ isSubmitAnswerAllowed: true,
370
+ isSubmitAnswerDisabled: !selectedOption,
371
+ isLive: true,
372
+ isAnswered: isAnswered,
373
+ setIsAnswered: setIsAnswered,
374
+ isTimeUp: isTimeUp,
375
+ setCurrentAnswer: setCurrentAnswer,
376
+ exercise: exercise
377
+ })]
378
+ });
379
+ }
380
+ export default LiveMeaning;
@@ -48,6 +48,8 @@ function ExerciseBottomBar({
48
48
  } else if (isLive && !isAnswered && exercise === "translation") {
49
49
  setCurrentAnswer(translationText ? translationText : translationWords);
50
50
  setIsAnswered(true);
51
+ } else if (isLive && !isAnswered && exercise === "meaning") {
52
+ setIsAnswered(true);
51
53
  } else {
52
54
  handleSubmit();
53
55
  }
@@ -1637,6 +1637,7 @@ function Classroom({
1637
1637
  handleClose: handleCloseSelectExercise,
1638
1638
  getCourseSections: getCourseSections,
1639
1639
  getRoleplays: getRoleplays,
1640
+ getPhraseLists: getCourseSectionTopicPhraseLists,
1640
1641
  username: username,
1641
1642
  handleCreateGame: handleCreateLiveGame,
1642
1643
  t: t,
@@ -1724,7 +1725,8 @@ export default function ViewClassroom({
1724
1725
  makeChatGptApiRequest,
1725
1726
  siteLanguage
1726
1727
  }) {
1727
- console.log("ViewClassroom render");
1728
+ // console.log("ViewClassroom render");
1729
+
1728
1730
  const {
1729
1731
  classes
1730
1732
  } = useStyles();
@@ -306,7 +306,7 @@ function ExerciseListItem({
306
306
  return /*#__PURE__*/_jsx(ListItemIcon, {});
307
307
  }
308
308
  let icon = null;
309
- let completions = exercise === "roleplays" ? data?.completions.map(completion => completion.exercise.startsWith("roleplay") && completion.roleplayId === roleplay.roleplayId) : phraseListId ? data?.completions.map(completion => completion.exercise === exercise && completion.phraseListId === phraseListId) : data?.completions.map(completion => completion.exercise === exercise);
309
+ let completions = exercise === "roleplays" ? data?.completions.map(completion => completion.exercise.startsWith("roleplay") && completion.roleplayId === roleplay.roleplayId) : phraseListId ? data?.completions.map(completion => completion.exercise === exercise && (completion.phraseListId === phraseListId || !completion.phraseListId && phraseListId?.startsWith('leg-'))) : data?.completions.map(completion => completion.exercise === exercise);
310
310
  if (exercise === "roleplays" && roleplay?.roleplayId && completions.length > 0) {
311
311
  icon = /*#__PURE__*/_jsx(Badge, {
312
312
  badgeContent: completions.length,
@@ -564,7 +564,7 @@ function MemberListItem({
564
564
  }), currentView === "topic" && phraseLists.map((phraseList, i) => {
565
565
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
566
566
  const phraseListExercises = ["translation", "listening", "pronunciation"];
567
- const completedCount = phraseListExercises.filter(exercise => topicData?.completions?.some(c => c.exercise === exercise && c.phraseListId === phraseList.phraseListId)).length;
567
+ const completedCount = phraseListExercises.filter(exercise => topicData?.completions?.some(c => c.exercise === exercise && (c.phraseListId === phraseList.phraseListId || !c.phraseListId && phraseList.phraseListId?.startsWith('leg-')))).length;
568
568
  return /*#__PURE__*/_jsx(MenuItem, {
569
569
  children: /*#__PURE__*/_jsx(ListItem, {
570
570
  sx: {
@@ -356,7 +356,7 @@ export function ExerciseCellData(props) {
356
356
  } else if (exercise === "bots") {
357
357
  completions = data?.completions.filter(completion => completion.exercise === "bot" && completion.botId === botId);
358
358
  } else if (phraseListId) {
359
- completions = data?.completions.filter(completion => completion.exercise === exercise && completion.phraseListId === phraseListId);
359
+ completions = data?.completions.filter(completion => completion.exercise === exercise && (completion.phraseListId === phraseListId || !completion.phraseListId && phraseListId?.startsWith('leg-')));
360
360
  } else {
361
361
  completions = data?.completions.filter(completion => completion.exercise === exercise);
362
362
  }
@@ -474,7 +474,7 @@ export function PhraseListCellData({
474
474
  index: index
475
475
  });
476
476
  const phraseListExercises = ["translation", "listening", "pronunciation"];
477
- const completedCount = phraseListExercises.filter(exercise => data?.completions?.some(c => c.exercise === exercise && c.phraseListId === phraseListId)).length;
477
+ const completedCount = phraseListExercises.filter(exercise => data?.completions?.some(c => c.exercise === exercise && (c.phraseListId === phraseListId || !c.phraseListId && phraseListId?.startsWith('leg-')))).length;
478
478
  const total = phraseListExercises.length;
479
479
  let color;
480
480
  if (completedCount === total) color = green[400];else if (completedCount > 0) color = yellow[400];else color = red[400];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1357",
3
+ "version": "0.1.1359",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [