@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.
@@ -1,196 +1,180 @@
1
- import { useState, useEffect } from "react";
2
- import { Box, ListItemText, ListItemButton, Avatar, ListItemIcon, Tooltip, Collapse, Checkbox } from "@mui/material";
3
- import PlayCircleOutlinedIcon from "@mui/icons-material/PlayCircleOutlined";
1
+ import { Box, ListItemText, ListItemButton, Avatar, Radio, Tooltip, Typography } from "@mui/material";
2
+ import { alpha } from "@mui/material/styles";
3
+ import ImageSearchIcon from "@mui/icons-material/ImageSearch";
4
4
  import MessageIcon from "@mui/icons-material/Message";
5
5
  import RateReviewIcon from "@mui/icons-material/RateReview";
6
- import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
7
- import ExpandLessIcon from "@mui/icons-material/ExpandLess";
8
- import AutoStoriesIcon from "@mui/icons-material/AutoStories";
9
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
- function Roleplay({
6
+
7
+ // Theme-aware selected-row tint (keeps the primary hue in light and dark).
8
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
9
+ export const selectedRowBg = theme => alpha(theme.palette.primary.main, theme.palette.mode === "dark" ? 0.24 : 0.08);
10
+ export const getRoleplayMessageCount = roleplay => {
11
+ const questions = roleplay?.script?.filter(item => Object.hasOwn(item, "question")).length ?? 0;
12
+ return (roleplay?.script?.length ?? 0) - questions;
13
+ };
14
+ export const getRoleplayInteractionCount = roleplay => roleplay?.script?.filter(item => Object.hasOwn(item, "question")).length ?? 0;
15
+ export const isRoleplayPlayable = roleplay => {
16
+ const hasText = roleplay?.script?.length > 0 && roleplay.script.some(line => line.text !== undefined);
17
+ if (!hasText) return false;
18
+ const questions = roleplay.script.filter(item => Object.hasOwn(item, "question")).length;
19
+ const interactions = roleplay.script.length - questions;
20
+ return interactions > 0 && questions > 0;
21
+ };
22
+
23
+ // Whether a topic has a scenario worth showing in scenarios mode. Empty topics
24
+ // are dropped; topics whose roleplay scripts haven't loaded yet are kept (so we
25
+ // never hide a real scenario just because its script isn't in the payload); once
26
+ // scripts are present, only topics with a playable scenario are kept.
27
+ export const topicHasScenario = (roleplays = []) => {
28
+ if (!roleplays.length) return false;
29
+ const anyScriptLoaded = roleplays.some(rp => Array.isArray(rp?.script));
30
+ if (!anyScriptLoaded) return true;
31
+ return roleplays.some(isRoleplayPlayable);
32
+ };
33
+
34
+ // Message + interaction (question) counts with their icons — reused on the row
35
+ // and in the dialog footer.
36
+ export function ScenarioCounts({
11
37
  roleplay,
12
- handleCreateGame,
13
- courseSectionTopicId,
14
- isRoleplaySelected,
15
- setIsRoleplaySelected,
16
- t,
17
- isExerciseSelected,
18
- handleSelectExercise = null,
19
- topicName
38
+ t
20
39
  }) {
21
- const [open, setOpen] = useState(false);
22
- const games = ["roleplay-story", "roleplay-fill-in-the-blanks", "roleplay-act-it-out", "roleplay-act-it-out-listening", "roleplay-act-it-out-speaking", "roleplay-act-it-out-listening-speaking"];
23
- const handleRoleplaySelected = exerciseName => () => {
24
- setIsRoleplaySelected(true);
25
- handleCreateGame({
26
- courseSectionTopicId: courseSectionTopicId,
27
- exercise: exerciseName,
28
- timer: 30,
29
- answerOption: 0,
30
- roleplayId: roleplay.roleplayId,
31
- topicName: topicName ?? ""
32
- });
33
- };
34
- const isRoleplayEmpty = roleplay?.script?.length === 0 || !roleplay?.script?.some(line => line.text !== undefined);
35
- const numberOfRoleplayQuestions = roleplay?.script?.filter(item => Object.hasOwn(item, "question")).length;
36
- const numberOfRoleplayInteractions = roleplay?.script?.length - numberOfRoleplayQuestions;
37
- const roleplayHasInteractions = numberOfRoleplayInteractions > 0;
38
- const roleplayHasQuestions = numberOfRoleplayQuestions > 0;
39
- const isRoleplayValid = roleplayHasInteractions && roleplayHasQuestions;
40
- return /*#__PURE__*/_jsxs("div", {
41
- children: [/*#__PURE__*/_jsx(Tooltip, {
42
- title: roleplay?.script?.length === 0 ? t("roleplay_no_interactions") : !isRoleplayValid ? t("roleplay_not_valid") : "",
43
- placement: "right",
44
- children: /*#__PURE__*/_jsx("div", {
45
- children: /*#__PURE__*/_jsxs(ListItemButton, {
46
- onClick: () => setOpen(!open),
47
- disabled: isRoleplaySelected || isRoleplayEmpty || isExerciseSelected || !isRoleplayValid,
40
+ const messages = getRoleplayMessageCount(roleplay);
41
+ const interactions = getRoleplayInteractionCount(roleplay);
42
+ return /*#__PURE__*/_jsxs(_Fragment, {
43
+ children: [messages > 0 && /*#__PURE__*/_jsxs(Box, {
44
+ sx: {
45
+ display: "flex",
46
+ alignItems: "center",
47
+ gap: 0.5
48
+ },
49
+ children: [/*#__PURE__*/_jsx(Typography, {
50
+ variant: "body2",
51
+ color: "textSecondary",
52
+ children: messages
53
+ }), /*#__PURE__*/_jsx(Tooltip, {
54
+ title: t("number_of_messages"),
55
+ placement: "top",
56
+ children: /*#__PURE__*/_jsx(MessageIcon, {
57
+ sx: {
58
+ fontSize: 18,
59
+ color: "text.secondary"
60
+ }
61
+ })
62
+ })]
63
+ }), interactions > 0 && /*#__PURE__*/_jsxs(Box, {
64
+ sx: {
65
+ display: "flex",
66
+ alignItems: "center",
67
+ gap: 0.5
68
+ },
69
+ children: [/*#__PURE__*/_jsx(Typography, {
70
+ variant: "body2",
71
+ color: "textSecondary",
72
+ children: interactions
73
+ }), /*#__PURE__*/_jsx(Tooltip, {
74
+ title: t("number_of_interactions"),
75
+ placement: "top",
76
+ children: /*#__PURE__*/_jsx(RateReviewIcon, {
48
77
  sx: {
49
- display: "flex",
50
- alignItems: "center",
51
- justifyContent: "space-between",
52
- flexDirection: {
53
- xs: "column",
54
- sm: "row"
55
- }
56
- },
57
- children: [/*#__PURE__*/_jsxs(Box, {
58
- sx: {
59
- display: "flex",
60
- alignItems: "center",
61
- width: {
62
- xs: "100%",
63
- sm: "85%"
64
- }
65
- },
66
- children: [/*#__PURE__*/_jsx(Avatar, {
67
- src: roleplay.picture
68
- }), /*#__PURE__*/_jsx(ListItemText, {
69
- primary: roleplay.roleplayName,
70
- secondary: roleplay.description,
71
- sx: {
72
- marginLeft: 2
73
- }
74
- })]
75
- }), roleplayHasInteractions && /*#__PURE__*/_jsxs(Box, {
76
- sx: {
77
- display: "flex",
78
- alignItems: "center"
79
- },
80
- children: [/*#__PURE__*/_jsx(ListItemText, {
81
- secondary: numberOfRoleplayInteractions,
82
- sx: {
83
- marginRight: 0.5
84
- }
85
- }), /*#__PURE__*/_jsx(ListItemIcon, {
86
- sx: {
87
- marginRight: {
88
- xs: 0,
89
- sm: -3
90
- }
91
- },
92
- children: /*#__PURE__*/_jsx(Tooltip, {
93
- title: t("number_of_interactions"),
94
- placement: "top",
95
- children: /*#__PURE__*/_jsx(MessageIcon, {
96
- sx: {
97
- width: 18
98
- }
99
- })
100
- })
101
- })]
102
- }), roleplayHasQuestions && /*#__PURE__*/_jsxs(Box, {
103
- sx: {
104
- display: "flex",
105
- alignItems: "center"
106
- },
107
- children: [/*#__PURE__*/_jsx(ListItemText, {
108
- secondary: numberOfRoleplayQuestions,
109
- sx: {
110
- marginRight: 0.5
111
- }
112
- }), /*#__PURE__*/_jsx(ListItemIcon, {
113
- children: /*#__PURE__*/_jsx(Tooltip, {
114
- title: t("number_of_questions"),
115
- placement: "top",
116
- children: /*#__PURE__*/_jsx(RateReviewIcon, {
117
- sx: {
118
- width: 18
119
- }
120
- })
121
- })
122
- })]
123
- }), /*#__PURE__*/_jsx(ListItemIcon, {
124
- onClick: () => setOpen(!open),
125
- children: open ? /*#__PURE__*/_jsx(ExpandLessIcon, {
126
- sx: {
127
- color: "#757575"
128
- }
129
- }) : /*#__PURE__*/_jsx(ExpandMoreIcon, {
130
- sx: {
131
- color: "#757575"
132
- }
133
- })
134
- })]
78
+ fontSize: 18,
79
+ color: "text.secondary"
80
+ }
135
81
  })
82
+ })]
83
+ })]
84
+ });
85
+ }
86
+ function Roleplay({
87
+ roleplay,
88
+ selected,
89
+ onSelect,
90
+ t
91
+ }) {
92
+ return /*#__PURE__*/_jsxs(ListItemButton, {
93
+ onClick: onSelect,
94
+ role: "radio",
95
+ "aria-checked": selected,
96
+ sx: {
97
+ display: "flex",
98
+ alignItems: "center",
99
+ px: 2,
100
+ py: 1,
101
+ borderRadius: 1,
102
+ ...(selected ? {
103
+ backgroundColor: selectedRowBg
104
+ } : {})
105
+ },
106
+ children: [roleplay.picture ? /*#__PURE__*/_jsx(Avatar, {
107
+ src: roleplay.picture,
108
+ alt: roleplay.roleplayName,
109
+ sx: {
110
+ flex: "none"
111
+ }
112
+ }) : /*#__PURE__*/_jsx(Avatar, {
113
+ alt: roleplay.roleplayName,
114
+ sx: {
115
+ flex: "none"
116
+ },
117
+ children: /*#__PURE__*/_jsx(ImageSearchIcon, {})
118
+ }), /*#__PURE__*/_jsx(ListItemText, {
119
+ primary: roleplay.roleplayName,
120
+ secondary: roleplay.description,
121
+ sx: {
122
+ ml: 2,
123
+ flex: 1,
124
+ minWidth: 0
125
+ }
126
+ }), /*#__PURE__*/_jsx(Box, {
127
+ sx: {
128
+ display: "flex",
129
+ alignItems: "center",
130
+ gap: 1.5,
131
+ mr: 1
132
+ },
133
+ children: /*#__PURE__*/_jsx(ScenarioCounts, {
134
+ roleplay: roleplay,
135
+ t: t
136
136
  })
137
- }), /*#__PURE__*/_jsx(Collapse, {
138
- in: open,
139
- timeout: "auto",
140
- unmountOnExit: true,
141
- children: /*#__PURE__*/_jsx(Box, {
137
+ }), /*#__PURE__*/_jsx(Box, {
138
+ sx: {
139
+ minWidth: 56,
140
+ display: "flex",
141
+ justifyContent: "flex-end",
142
+ flex: "none"
143
+ },
144
+ children: /*#__PURE__*/_jsx(Radio, {
145
+ checked: selected,
146
+ tabIndex: -1,
147
+ disableRipple: true,
148
+ color: "primary",
142
149
  sx: {
143
- ml: 3.5
150
+ p: 0
144
151
  },
145
- children: /*#__PURE__*/_jsxs(ListItemButton, {
146
- onClick: handleRoleplaySelected("roleplay-story"),
147
- disabled: isRoleplaySelected || isExerciseSelected || !isRoleplayValid,
148
- sx: {
149
- display: "flex",
150
- alignItems: "center",
151
- justifyContent: "space-between",
152
- flexDirection: {
153
- xs: "column",
154
- sm: "row"
155
- }
156
- },
157
- children: [/*#__PURE__*/_jsx(ListItemIcon, {
158
- children: /*#__PURE__*/_jsx(AutoStoriesIcon, {})
159
- }), /*#__PURE__*/_jsx(ListItemText, {
160
- primary: t("roleplay-story"),
161
- secondary: t("story_description")
162
- }), /*#__PURE__*/_jsx(ListItemIcon, {
163
- children: /*#__PURE__*/_jsx(PlayCircleOutlinedIcon, {
164
- color: "primary"
165
- })
166
- })]
167
- })
152
+ inputProps: {
153
+ "aria-label": roleplay.roleplayName
154
+ }
168
155
  })
169
156
  })]
170
157
  });
171
158
  }
172
159
  function RoleplaySelection({
173
160
  roleplays = [],
174
- handleCreateGame,
175
161
  courseSectionTopicId,
176
162
  t,
177
- isExerciseSelected,
178
- handleSelectExercise,
179
- useCase,
163
+ selectedScenarioId,
164
+ onSelectScenario,
180
165
  topicName
181
166
  }) {
182
- const [isRoleplaySelected, setIsRoleplaySelected] = useState(false);
183
- return roleplays.map((roleplay, index) => /*#__PURE__*/_jsx(Roleplay, {
167
+ // Only surface playable scenarios (never a disabled row).
168
+ const scenarios = roleplays.filter(isRoleplayPlayable);
169
+ return scenarios.map((roleplay, index) => /*#__PURE__*/_jsx(Roleplay, {
184
170
  roleplay: roleplay,
185
- handleCreateGame: handleCreateGame,
186
- isRoleplaySelected: isRoleplaySelected,
187
- setIsRoleplaySelected: setIsRoleplaySelected,
188
- courseSectionTopicId: courseSectionTopicId,
189
- t: t,
190
- isExerciseSelected: isExerciseSelected,
191
- handleSelectExercise: handleSelectExercise,
192
- useCase: useCase,
193
- topicName: topicName
194
- }, index));
171
+ selected: selectedScenarioId === roleplay.roleplayId,
172
+ onSelect: () => onSelectScenario({
173
+ ...roleplay,
174
+ courseSectionTopicId,
175
+ topicName: topicName ?? ""
176
+ }),
177
+ t: t
178
+ }, roleplay.roleplayId || index));
195
179
  }
196
180
  export default RoleplaySelection;
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useMemo } from "react";
1
+ import { useState, useEffect, useMemo, useRef } from "react";
2
2
  import { Dialog, Box, Slide, Typography, AppBar, Toolbar, IconButton, Tooltip, Badge } from "@mui/material";
3
3
  import LightbulbIcon from "@mui/icons-material/Lightbulb";
4
4
  import { makeStyles } from "tss-react/mui";
@@ -6,8 +6,8 @@ import { useTheme } from "@mui/material/styles";
6
6
  import { styled } from "@mui/system";
7
7
  import Close from "@mui/icons-material/Close";
8
8
  import PersonAddIcon from "@mui/icons-material/PersonAdd";
9
- import People from "@mui/icons-material/People";
10
9
  import SettingsIcon from "@mui/icons-material/Settings";
10
+ import People from "@mui/icons-material/People";
11
11
  import { Joyride, STATUS } from "react-joyride";
12
12
  import ExerciseProgressBar from "../../Misc/ExerciseProgressBar/ExerciseProgressBar";
13
13
  import VoiceSpeedSelector from "../../Misc/VoiceSpeed/VoiceSpeed";
@@ -23,9 +23,7 @@ import GameMembers from "../GameMembers/GameMembers";
23
23
  import GameSettings from "../GameSettings/GameSettings";
24
24
  import FinalScoreDrawer from "../Misc/FinalScoreDrawer/FinalScoreDrawer";
25
25
  import RewardEarned from "../../Dialogs/RewardEarned/RewardEarned";
26
- import LiveGameTutorial from "../../Dialogs/LiveGameTutorial/LiveGameTutorial";
27
26
  import { startJoyrideWhenReady } from "../../utils/joyride";
28
- import HelpIcon from "@mui/icons-material/Help";
29
27
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
30
28
  const useStyles = makeStyles()(theme => ({
31
29
  appBar: {
@@ -152,7 +150,6 @@ export default function Game({
152
150
  const [isInviteDrawerOpen, setIsInviteDrawerOpen] = useState(false);
153
151
  const [isMembersDrawerOpen, setIsMembersDrawerOpen] = useState(false);
154
152
  const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState(false);
155
- const [isLiveGameTutorialOpen, setIsLiveGameTutorialOpen] = useState(false);
156
153
  const activeMembers = useMemo(() => membersInRoom.filter(member => member.isActive === true), [membersInRoom]);
157
154
  const inactiveMembers = useMemo(() => [...membersNotInRoom, ...membersInRoom.filter(member => member.isActive === false)], [membersInRoom, membersNotInRoom]);
158
155
  const handleOpenInviteDrawer = () => {
@@ -170,9 +167,6 @@ export default function Game({
170
167
  const handleOpenSettingsDrawer = () => {
171
168
  setIsSettingsDrawerOpen(true);
172
169
  };
173
- const handleOpenLiveGameTutorial = () => {
174
- setIsLiveGameTutorialOpen(true);
175
- };
176
170
  const handleCloseSettingsDrawer = () => {
177
171
  setIsSettingsDrawerOpen(false);
178
172
  };
@@ -202,16 +196,6 @@ export default function Game({
202
196
  }),
203
197
  spotlightPadding: 8,
204
198
  target: "#inviteStudents"
205
- }, {
206
- content: /*#__PURE__*/_jsxs("div", {
207
- children: [/*#__PURE__*/_jsx("h2", {
208
- children: t("nualang_live_help")
209
- }), /*#__PURE__*/_jsx("br", {}), /*#__PURE__*/_jsx("p", {
210
- children: t("nualang_live_help_desc")
211
- })]
212
- }),
213
- spotlightPadding: 8,
214
- target: "#appBarGameHelp"
215
199
  }, {
216
200
  content: /*#__PURE__*/_jsxs("div", {
217
201
  children: [/*#__PURE__*/_jsx("h2", {
@@ -252,6 +236,21 @@ export default function Game({
252
236
  })));
253
237
  }
254
238
  }, [open, isCreator]);
239
+
240
+ // Show the settings drawer to the creator in the waiting room so they review
241
+ // (and adjust) the game settings before starting. Only auto-opens once per
242
+ // opening; if the teacher closes it, it stays closed.
243
+ const hasAutoOpenedSettingsRef = useRef(false);
244
+ useEffect(() => {
245
+ if (!open) {
246
+ hasAutoOpenedSettingsRef.current = false;
247
+ return;
248
+ }
249
+ if (isCreator && gameStatus !== "LIVE" && !hasAutoOpenedSettingsRef.current) {
250
+ hasAutoOpenedSettingsRef.current = true;
251
+ setIsSettingsDrawerOpen(true);
252
+ }
253
+ }, [open, isCreator, gameStatus]);
255
254
  const handleJoyrideCallback = data => {
256
255
  const {
257
256
  status,
@@ -279,7 +278,6 @@ export default function Game({
279
278
  }
280
279
  };
281
280
  const nextScriptLine = roleplayMessages?.length && selectedRoleplay?.script?.[roleplayMessages.length] ? selectedRoleplay.script[roleplayMessages.length] : "";
282
- const videoUrl = topicInfo.videoUrl ?? null;
283
281
  return /*#__PURE__*/_jsxs(Dialog, {
284
282
  fullScreen: true,
285
283
  open: open,
@@ -342,23 +340,6 @@ export default function Game({
342
340
  children: /*#__PURE__*/_jsx(People, {})
343
341
  })
344
342
  })
345
- }), /*#__PURE__*/_jsx(Tooltip, {
346
- title: t("instructions"),
347
- children: /*#__PURE__*/_jsx(IconButton, {
348
- onClick: () => {
349
- handleOpenLiveGameTutorial();
350
- },
351
- id: "appBarGameHelp",
352
- color: "inherit",
353
- "data-cy": "game-help",
354
- "aria-label": "help",
355
- size: "large",
356
- children: /*#__PURE__*/_jsx(HelpIcon, {
357
- sx: {
358
- color: "white"
359
- }
360
- })
361
- })
362
343
  }), /*#__PURE__*/_jsx(Tooltip, {
363
344
  title: t("game_settings"),
364
345
  children: /*#__PURE__*/_jsx(IconButton, {
@@ -458,9 +439,6 @@ export default function Game({
458
439
  gameAward: gameAward,
459
440
  userImage: userImage,
460
441
  exerciseName: exerciseName,
461
- isLiveGameTutorialOpen: isLiveGameTutorialOpen,
462
- setIsLiveGameTutorialOpen: setIsLiveGameTutorialOpen,
463
- videoUrl: videoUrl,
464
442
  ...otherProps
465
443
  }), gameView === GameViewTypes.GameStarting && /*#__PURE__*/_jsx(Typography, {
466
444
  variant: "h1",
@@ -613,11 +591,6 @@ export default function Game({
613
591
  classroomURL: classroomURL,
614
592
  isOpen: isInviteDrawerOpen,
615
593
  handleClose: handleCloseInviteDrawer
616
- }), /*#__PURE__*/_jsx(LiveGameTutorial, {
617
- open: isLiveGameTutorialOpen,
618
- handleClose: () => setIsLiveGameTutorialOpen(false),
619
- exerciseName: exerciseName,
620
- t: t
621
594
  }), /*#__PURE__*/_jsx(GameMembers, {
622
595
  t: t,
623
596
  isOpen: isMembersDrawerOpen,
@@ -641,7 +614,8 @@ export default function Game({
641
614
  handleConfirm: handleConfirm,
642
615
  numberOfQuestions: numberOfQuestions,
643
616
  exercise: exercise,
644
- roleplay: selectedRoleplay
617
+ roleplay: selectedRoleplay,
618
+ phraseListCount: (otherProps.sources || []).length
645
619
  }), /*#__PURE__*/_jsx(Joyride, {
646
620
  onEvent: handleJoyrideCallback,
647
621
  continuous: true,
@@ -64,6 +64,10 @@ export default function GameMembers({
64
64
  children: [/*#__PURE__*/_jsx(Typography, {
65
65
  variant: "h6",
66
66
  gutterBottom: true,
67
+ sx: {
68
+ fontSize: "24px",
69
+ fontWeight: 700
70
+ },
67
71
  children: t("members")
68
72
  }), /*#__PURE__*/_jsx(TextField, {
69
73
  id: "search",