@nualang/nualang-ui-components 0.1.1432 → 0.1.1433

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.
@@ -1,16 +1,30 @@
1
- import { useState, useEffect, Fragment } from "react";
2
- import { Box, ListItemText, ListItemButton, Avatar, Checkbox, ListItemIcon, Badge, IconButton, Chip, Typography } from "@mui/material";
1
+ import { useState } from "react";
2
+ import { Box, ListItemText, ListItemButton, Avatar, Checkbox, ListItemIcon, Badge, IconButton, Chip, Typography, Collapse, Tooltip } from "@mui/material";
3
3
  import ClearIcon from "@mui/icons-material/Clear";
4
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
5
+ import ExpandLessIcon from "@mui/icons-material/ExpandLess";
6
+ import ForumIcon from "@mui/icons-material/Forum";
7
+ import HeadsetIcon from "@mui/icons-material/Headset";
4
8
  import { Done, Timelapse } from "@mui/icons-material";
5
9
  import { useNavigate, useParams } from "react-router";
6
10
  import HtmlTooltipComponent from "../../Misc/HtmlTooltipComponent/HtmlTooltipComponent";
11
+ import { getBotContentTemplateType } from "../../utils";
7
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
8
13
  const sxGreenColor = theme => ({
9
- color: theme.palette.success ? theme.palette.success.dark : ""
14
+ color: theme.palette.success ? theme.palette.success.dark : "",
15
+ marginTop: theme.spacing(2),
16
+ marginRight: theme.spacing(2),
17
+ marginBottom: theme.spacing(2)
10
18
  });
11
19
  const sxInfoColor = theme => ({
12
- color: theme.palette.info ? theme.palette.info.main : ""
20
+ color: theme.palette.info ? theme.palette.info.main : "",
21
+ marginTop: theme.spacing(2),
22
+ marginRight: theme.spacing(2),
23
+ marginBottom: theme.spacing(2)
13
24
  });
25
+ const botModes = ["bot", "bot-listen-and-speak"];
26
+ const getModeIcon = mode => mode === "bot" ? /*#__PURE__*/_jsx(ForumIcon, {}) : /*#__PURE__*/_jsx(HeadsetIcon, {});
27
+ const getModeLabel = (mode, t) => mode === "bot" ? t("choose_mode_text_option") : t("audio_only_mode");
14
28
  export function ProgressBadge({
15
29
  completions
16
30
  }) {
@@ -55,7 +69,7 @@ function Bot({
55
69
  } = params;
56
70
  const [courseId, sectionId, topicId] = courseSectionTopicId ? courseSectionTopicId.split("|") : "";
57
71
  const [lastClickedAssignmentId, lastClickedBotId] = lastClickedExerciseId?.split("|").slice(-2) || [];
58
- const addSearchParams = () => {
72
+ const addSearchParams = mode => {
59
73
  if (!classroomId || !courseId || !sectionId || !topicId || !bot?.botId) {
60
74
  console.error("Missing required parameters for navigation");
61
75
  return;
@@ -64,55 +78,103 @@ function Bot({
64
78
  courseSectionTopicId: `${courseSectionTopicId}|${assignment.assignmentId}|${bot.botId}`
65
79
  };
66
80
  localStorage.setItem("lastClickedExercise", JSON.stringify(lastClickedExercise));
67
- navigate(`/classrooms/${classroomId}/${courseId}/${sectionId}/${topicId}/bots/${bot?.botId}?assignmentId=${assignment.assignmentId}`);
81
+ const modeParam = mode ? `&mode=${encodeURIComponent(mode)}` : "";
82
+ navigate(`/classrooms/${classroomId}/${courseId}/${sectionId}/${topicId}/bots/${bot?.botId}?assignmentId=${assignment.assignmentId}${modeParam}`);
68
83
  };
69
84
  const [open, setOpen] = useState(lastClickedBotId === bot.botId && lastClickedAssignmentId === assignment.assignmentId);
70
- const [isSelected, setIsSelected] = useState(false);
71
- useEffect(() => {
72
- // Check if this exercise is selected
73
- const exerciseSelected = selectedExercises?.some(exercise => exercise.botId === bot.botId);
74
- setIsSelected(exerciseSelected);
75
- }, [selectedExercises, bot.botId]);
76
- const handleClick = () => {
77
- setIsSelected(!isSelected);
78
- const exercise = {
79
- botId: bot.botId,
80
- courseSectionTopicId
81
- };
82
- handleSelectExercise([exercise]);
85
+ const botTemplateType = getBotContentTemplateType(bot.selectedTemplate);
86
+ const isPictureOrEmojiQuizBot = botTemplateType === "picture-quiz" || botTemplateType === "emoji-quiz";
87
+ // Picture/emoji quiz bots have no spoken content to listen to, so Listen &
88
+ // Speak is never a valid mode for them - only Chat can be assigned/played.
89
+ const availableModes = isPictureOrEmojiQuizBot ? ["bot"] : botModes;
90
+ const allModesValue = isPictureOrEmojiQuizBot ? "bot" : "all";
91
+ const selectedExercise = selectedExercises?.find(exercise => exercise.botId === bot.botId && exercise.courseSectionTopicId === courseSectionTopicId);
92
+ const isBotSelectedForAssignment = !!selectedExercise;
93
+ const effectiveMode = selectedExercise?.mode || "all";
94
+ const isAllModesSelected = isBotSelectedForAssignment && effectiveMode === allModesValue;
95
+ const isIndeterminate = isBotSelectedForAssignment && effectiveMode !== allModesValue;
96
+ const isModeSelected = mode => isBotSelectedForAssignment && (effectiveMode === "all" || effectiveMode === mode);
97
+ const handleToggleMode = mode => {
98
+ const otherMode = mode === "bot" ? "bot-listen-and-speak" : "bot";
99
+ const modeChecked = isModeSelected(mode);
100
+ const otherChecked = availableModes.includes(otherMode) && isModeSelected(otherMode);
101
+ if (modeChecked) {
102
+ if (otherChecked) {
103
+ handleSelectExercise([{
104
+ botId: bot.botId,
105
+ courseSectionTopicId,
106
+ mode: otherMode,
107
+ updateModeOnly: true
108
+ }]);
109
+ } else {
110
+ handleSelectExercise([{
111
+ botId: bot.botId,
112
+ courseSectionTopicId
113
+ }]);
114
+ }
115
+ } else if (otherChecked) {
116
+ handleSelectExercise([{
117
+ botId: bot.botId,
118
+ courseSectionTopicId,
119
+ mode: "all",
120
+ updateModeOnly: true
121
+ }]);
122
+ } else {
123
+ handleSelectExercise([{
124
+ botId: bot.botId,
125
+ courseSectionTopicId,
126
+ mode
127
+ }]);
128
+ }
129
+ };
130
+ const handleSelectAllModes = () => {
131
+ if (isAllModesSelected) {
132
+ handleSelectExercise([{
133
+ botId: bot.botId,
134
+ courseSectionTopicId
135
+ }]);
136
+ } else if (isBotSelectedForAssignment) {
137
+ handleSelectExercise([{
138
+ botId: bot.botId,
139
+ courseSectionTopicId,
140
+ mode: allModesValue,
141
+ updateModeOnly: true
142
+ }]);
143
+ } else {
144
+ handleSelectExercise([{
145
+ botId: bot.botId,
146
+ courseSectionTopicId,
147
+ mode: allModesValue
148
+ }]);
149
+ }
83
150
  };
84
- const getBotCompletion = () => {
151
+ const getBotCompletion = mode => {
85
152
  if (!Array.isArray(memberCourseCompletions) || memberCourseCompletions.length === 0) {
86
153
  return 0;
87
154
  }
88
- const botCompletion = memberCourseCompletions.filter(completion => completion.botId === bot.botId);
155
+ const botCompletion = memberCourseCompletions.filter(completion => completion.botId === bot.botId && (!mode || completion.exercise === mode));
89
156
  return botCompletion.length;
90
157
  };
91
- const getBotProgress = () => {
92
- const progress = topicProgress?.filter(progress => progress.botId === bot.botId && progress.existingMessages && progress.existingMessages !== null);
158
+ const getBotProgress = mode => {
159
+ const progress = topicProgress?.filter(progress => progress.botId === bot.botId && (!mode || progress.exercise === mode) && progress.existingMessages && progress.existingMessages !== null);
93
160
  return progress?.length || 0;
94
161
  };
95
- return /*#__PURE__*/_jsx("div", {
96
- children: /*#__PURE__*/_jsx("div", {
162
+ return /*#__PURE__*/_jsxs("div", {
163
+ children: [/*#__PURE__*/_jsx("div", {
97
164
  children: /*#__PURE__*/_jsxs(ListItemButton, {
98
- onClick: () => {
99
- if (useCase === "assignment-start") {
100
- addSearchParams();
101
- } else if (handleSelectExercise) {
102
- handleClick();
103
- }
104
- },
165
+ onClick: () => setOpen(!open),
105
166
  sx: {
106
167
  display: "flex",
107
168
  alignItems: "center",
108
169
  justifyContent: "space-between",
109
- flexDirection: {
110
- xs: "column",
111
- sm: "row"
112
- },
170
+ flexDirection: "row",
113
171
  marginLeft: {
114
172
  sm: "0px",
115
173
  md: "60px"
174
+ },
175
+ gap: {
176
+ xs: 1,
177
+ sm: 2
116
178
  }
117
179
  },
118
180
  children: [/*#__PURE__*/_jsxs(Box, {
@@ -171,39 +233,131 @@ function Bot({
171
233
  }),
172
234
  secondary: bot.description
173
235
  })]
174
- }), useCase === "assignment-select" && /*#__PURE__*/_jsx(Checkbox, {
236
+ }), /*#__PURE__*/_jsxs(Box, {
175
237
  sx: {
176
- marginRight: "15px"
238
+ display: "flex",
239
+ alignItems: "center",
240
+ gap: 1
177
241
  },
178
- checked: isSelected,
179
- onClick: e => {
180
- e.stopPropagation();
181
- handleClick();
242
+ children: [/*#__PURE__*/_jsx(IconButton, {
243
+ onClick: e => {
244
+ e.stopPropagation();
245
+ setOpen(!open);
246
+ },
247
+ sx: {
248
+ ml: 2
249
+ },
250
+ children: open ? /*#__PURE__*/_jsx(ExpandLessIcon, {
251
+ sx: {
252
+ color: "#757575"
253
+ }
254
+ }) : /*#__PURE__*/_jsx(ExpandMoreIcon, {
255
+ sx: {
256
+ color: "#757575"
257
+ }
258
+ })
259
+ }), useCase === "assignment-select" && /*#__PURE__*/_jsx(Tooltip, {
260
+ title: t("select_all"),
261
+ placement: "top",
262
+ children: /*#__PURE__*/_jsx(Checkbox, {
263
+ checked: isAllModesSelected,
264
+ indeterminate: isIndeterminate,
265
+ onChange: handleSelectAllModes,
266
+ onClick: event => {
267
+ event.stopPropagation();
268
+ },
269
+ sx: {
270
+ mr: 2
271
+ }
272
+ })
273
+ })]
274
+ })]
275
+ })
276
+ }), /*#__PURE__*/_jsx(Collapse, {
277
+ in: open,
278
+ timeout: "auto",
279
+ unmountOnExit: true,
280
+ children: /*#__PURE__*/_jsxs(Box, {
281
+ sx: {
282
+ ml: {
283
+ sm: 0,
284
+ md: 3.5
182
285
  }
183
- }), useCase === "assignment-view" && isPreview && /*#__PURE__*/_jsx(ListItemIcon, {
184
- children: /*#__PURE__*/_jsx(IconButton, {
286
+ },
287
+ children: [useCase === "assignment-select" && availableModes.map(mode => /*#__PURE__*/_jsxs(ListItemButton, {
288
+ onClick: () => handleToggleMode(mode),
289
+ sx: {
290
+ display: "flex",
291
+ alignItems: "center",
292
+ justifyContent: "space-between",
293
+ flexDirection: "row",
294
+ marginLeft: {
295
+ sm: "0px",
296
+ md: "95px"
297
+ }
298
+ },
299
+ children: [/*#__PURE__*/_jsx(ListItemIcon, {
300
+ sx: {
301
+ display: {
302
+ xs: "none",
303
+ sm: "flex"
304
+ }
305
+ },
306
+ children: getModeIcon(mode)
307
+ }), /*#__PURE__*/_jsx(ListItemText, {
308
+ primary: getModeLabel(mode, t),
309
+ secondary: t(mode === "bot" ? "choose_mode_text_option_desc" : "audio_only_subtitle")
310
+ }), /*#__PURE__*/_jsx(ListItemIcon, {
311
+ children: /*#__PURE__*/_jsx(Checkbox, {
312
+ checked: isModeSelected(mode)
313
+ })
314
+ })]
315
+ }, mode)), (useCase === "assignment-view" || useCase === "assignment-start") && availableModes.map(mode => isModeSelected(mode) && /*#__PURE__*/_jsxs(ListItemButton, {
316
+ "data-cy": `assignment-bot-mode-${mode}`,
317
+ onClick: () => {
318
+ if (useCase === "assignment-start") addSearchParams(mode);
319
+ },
320
+ sx: {
321
+ display: "flex",
322
+ alignItems: "center",
323
+ justifyContent: "space-between",
324
+ flexDirection: "row",
325
+ marginLeft: {
326
+ sm: "0px",
327
+ md: "95px"
328
+ }
329
+ },
330
+ children: [/*#__PURE__*/_jsx(ListItemIcon, {
331
+ sx: {
332
+ display: {
333
+ xs: "none",
334
+ sm: "flex"
335
+ }
336
+ },
337
+ children: getModeIcon(mode)
338
+ }), /*#__PURE__*/_jsx(ListItemText, {
339
+ primary: getModeLabel(mode, t),
340
+ secondary: t(mode === "bot" ? "choose_mode_text_option_desc" : "audio_only_subtitle")
341
+ }), useCase === "assignment-start" && /*#__PURE__*/_jsxs(ListItemIcon, {
342
+ children: [/*#__PURE__*/_jsx(InProgressBadge, {
343
+ progressCount: getBotProgress(mode)
344
+ }), /*#__PURE__*/_jsx(ProgressBadge, {
345
+ completions: getBotCompletion(mode)
346
+ })]
347
+ }), useCase === "assignment-view" && isPreview && /*#__PURE__*/_jsx(IconButton, {
185
348
  edge: "end",
186
- "aria-label": "remove-bot",
349
+ "aria-label": "remove-bot-mode",
187
350
  onClick: e => {
188
351
  e.stopPropagation();
189
- const exerciseToRemove = selectedExercises.find(ex => ex.botId === bot.botId && ex.courseSectionTopicId === courseSectionTopicId);
190
- if (exerciseToRemove) {
191
- handleRemoveExercise(exerciseToRemove);
192
- }
352
+ handleToggleMode(mode);
193
353
  },
194
354
  children: /*#__PURE__*/_jsx(ClearIcon, {
195
355
  fontSize: "small"
196
356
  })
197
- })
198
- }), useCase === "assignment-start" && /*#__PURE__*/_jsxs(ListItemIcon, {
199
- children: [/*#__PURE__*/_jsx(InProgressBadge, {
200
- progressCount: getBotProgress()
201
- }), /*#__PURE__*/_jsx(ProgressBadge, {
202
- completions: getBotCompletion()
203
357
  })]
204
- })]
358
+ }, mode))]
205
359
  })
206
- })
360
+ })]
207
361
  });
208
362
  }
209
363
  function AssignmentBotSelection({
@@ -17,7 +17,7 @@ import { progress as progressQuerys } from "@nualang/nualang-api-and-queries/Que
17
17
  import { useNavigate, useParams } from "react-router";
18
18
  import { ProgressBadge, InProgressBadge } from "../../Lists/Exercises/Exercises";
19
19
  import ColorLinearProgress from "../../Misc/ColorLinearProgress/ColorLinearProgress";
20
- import { calcCompletions } from "../../utils";
20
+ import { calcCompletions, getBotContentTemplateType } from "../../utils";
21
21
  import AssignmentBotSelection from "../AssignmentBotSelection/AssignmentBotSelection";
22
22
  import AssignmentPhraseListSelection from "../AssignmentPhraseListSelection/AssignmentPhraseListSelection";
23
23
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -508,9 +508,12 @@ function Topic({
508
508
  });
509
509
  bots?.forEach(bot => {
510
510
  if (!selectedExercises?.some(e => e.botId === bot.botId)) {
511
+ const botTemplateType = getBotContentTemplateType(bot.selectedTemplate);
512
+ const isPictureOrEmojiQuizBot = botTemplateType === "picture-quiz" || botTemplateType === "emoji-quiz";
511
513
  exercisesToAdd.push({
512
514
  botId: bot.botId,
513
- courseSectionTopicId: courseSectionTopicId
515
+ courseSectionTopicId: courseSectionTopicId,
516
+ mode: isPictureOrEmojiQuizBot ? "bot" : "all"
514
517
  });
515
518
  }
516
519
  });
@@ -50,11 +50,22 @@ function AssignmentSelectExercise({
50
50
  }
51
51
  } else if (isBot) {
52
52
  // For bots
53
- const exists = updatedExercises.some(e => e.botId === exercise.botId && e.courseSectionTopicId === exercise.courseSectionTopicId);
54
- if (exists) {
55
- updatedExercises = updatedExercises.filter(e => e.botId !== exercise.botId || e.courseSectionTopicId !== exercise.courseSectionTopicId);
53
+ const existingIndex = updatedExercises.findIndex(e => e.botId === exercise.botId && e.courseSectionTopicId === exercise.courseSectionTopicId);
54
+ if (existingIndex > -1) {
55
+ if (exercise.updateModeOnly) {
56
+ updatedExercises[existingIndex] = {
57
+ ...updatedExercises[existingIndex],
58
+ mode: exercise.mode
59
+ };
60
+ } else {
61
+ updatedExercises = updatedExercises.filter((_, index) => index !== existingIndex);
62
+ }
56
63
  } else {
57
- updatedExercises.push(exercise);
64
+ const newExercise = {
65
+ ...exercise
66
+ };
67
+ delete newExercise.updateModeOnly;
68
+ updatedExercises.push(newExercise);
58
69
  }
59
70
  } else {
60
71
  // Unknown type — just push without unsafe undefined matching