@nualang/nualang-ui-components 0.1.1410 → 0.1.1411

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.
Files changed (30) hide show
  1. package/dist/Assignments/AssignmentCard/AssignmentCard.js +1 -1
  2. package/dist/Cards/FeedbackCard/FeedbackCard.js +1 -1
  3. package/dist/Dialogs/BotPreview/BotPreview.js +1 -1
  4. package/dist/Dialogs/InviteDialog/InviteDialog.js +2 -2
  5. package/dist/Editors/Bot/Bot.js +16 -21
  6. package/dist/Editors/Bot/Editor/Editor.js +2 -2
  7. package/dist/Editors/Roleplay/Roleplay.js +16 -11
  8. package/dist/Exercises/Bot/Bot.js +12 -8
  9. package/dist/Exercises/ListenAndSpeak/ListenAndSpeak.js +12 -20
  10. package/dist/Exercises/Listener/Listener.js +15 -22
  11. package/dist/Exercises/Meaning/Meaning.js +12 -8
  12. package/dist/Exercises/MeaningReadWrite/MeaningReadWriteGame.js +17 -20
  13. package/dist/Exercises/Pronouncer/Pronouncer.js +13 -8
  14. package/dist/Exercises/Roleplay/OriginalRoleplay.js +6 -3
  15. package/dist/Exercises/Roleplay/Roleplay.js +1 -0
  16. package/dist/Exercises/Translator/Translator.js +14 -8
  17. package/dist/Forms/FeedbackForm/FeedbackForm.js +2 -2
  18. package/dist/Forms/ImageSelector/ImageSelector.js +3 -3
  19. package/dist/Forms/PdfUploader/PdfUploader.js +2 -2
  20. package/dist/Forms/Settings/Settings.js +1 -1
  21. package/dist/Live/Game/Game.js +12 -8
  22. package/dist/Live/GameMembers/GameMembers.js +3 -3
  23. package/dist/Live/GameSettings/GameSettings.js +1 -1
  24. package/dist/Live/HowToJoin/HowToJoin.js +28 -11
  25. package/dist/Live/LiveListener/LiveListener.js +1 -4
  26. package/dist/Misc/ImageLibrary/ImageLibrary.js +3 -3
  27. package/dist/Navigation/ResponsiveTabs/ResponsiveTabs.js +29 -15
  28. package/dist/hooks/useConfirm.js +1 -1
  29. package/dist/utils/joyride.js +127 -0
  30. package/package.json +1 -1
@@ -167,7 +167,7 @@ export default function AssignmentCard({
167
167
  borderRadius: 1,
168
168
  fontWeight: "bold"
169
169
  },
170
- children: t("NEW")
170
+ children: t("new")
171
171
  })]
172
172
  })
173
173
  }), /*#__PURE__*/_jsx(Grid, {
@@ -514,7 +514,7 @@ export default function FeedbackCard({
514
514
  setHasBeenEdited(true);
515
515
  setIsSubmitted(false);
516
516
  },
517
- placeholder: t("Write your feedback here")
517
+ placeholder: t("write_your_feedback_here")
518
518
  })
519
519
  })]
520
520
  })
@@ -237,7 +237,7 @@ function BotPreview({
237
237
  textAlign: "center",
238
238
  mb: 1
239
239
  },
240
- children: t("Preview")
240
+ children: t("preview")
241
241
  }), bot && bot.picture && /*#__PURE__*/_jsx(Box, {
242
242
  sx: {
243
243
  textAlign: "center"
@@ -200,11 +200,11 @@ function InviteDialog({
200
200
  return /*#__PURE__*/_jsx(ResponsiveDialog, {
201
201
  open: open,
202
202
  handleClose: handleClose,
203
- dialogTitle: t("Invite Students"),
203
+ dialogTitle: t("invite_students"),
204
204
  handleSubmit: isWaysideClassroom ? () => {
205
205
  window.open(learningSiteURL, "wayside-window");
206
206
  } : null,
207
- submitText: isWaysideClassroom ? t("Visit Learning Site") : null,
207
+ submitText: isWaysideClassroom ? t("visit_learning_site") : null,
208
208
  children: isWaysideClassroom ? /*#__PURE__*/_jsx(_Fragment, {
209
209
  children: /*#__PURE__*/_jsxs(DialogContentText, {
210
210
  sx: {
@@ -26,6 +26,7 @@ import Editor from "./Editor/Editor";
26
26
 
27
27
  // utils
28
28
  import { jsonToRivescript, rivescriptToJson } from "../../utils/index";
29
+ import { startJoyrideWhenReady } from "../../utils/joyride";
29
30
  import useDebounce from "../../hooks/useDebounce";
30
31
  import useRecognition from "../../hooks/useRecognition";
31
32
  import { botErrorMessage } from "../../utils/constants";
@@ -217,16 +218,6 @@ function Bot({
217
218
  }),
218
219
  spotlightPadding: 8,
219
220
  target: "#editorSidebar"
220
- }, {
221
- content: /*#__PURE__*/_jsxs("div", {
222
- children: [/*#__PURE__*/_jsx("h2", {
223
- children: t("bot_editor_flow_chart")
224
- }), /*#__PURE__*/_jsx("br", {}), /*#__PURE__*/_jsx("p", {
225
- children: t("bot_editor_flow_chart_desc")
226
- })]
227
- }),
228
- spotlightPadding: 8,
229
- target: "#editorFlowChart"
230
221
  }, {
231
222
  content: /*#__PURE__*/_jsx("h2", {
232
223
  children: t("bot_editor_share")
@@ -260,10 +251,11 @@ function Bot({
260
251
  };
261
252
  useEffect(() => {
262
253
  if (open && !localStorage.getItem("bot-editor-tour-completed")) {
263
- setJoyrideState(prevState => ({
254
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
264
255
  ...prevState,
265
- run: true
266
- }));
256
+ run: true,
257
+ steps
258
+ })));
267
259
  }
268
260
  }, [open]);
269
261
  useEffect(() => {
@@ -303,10 +295,11 @@ function Bot({
303
295
  }));
304
296
  }
305
297
  if (tabValue === 0 && !localStorage.getItem(`bot-editor-tour-completed`)) {
306
- setJoyrideState(prevState => ({
298
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
307
299
  ...prevState,
308
- run: true
309
- }));
300
+ run: true,
301
+ steps
302
+ })));
310
303
  }
311
304
  }, [tabValue]);
312
305
  const handleChangeTab = (event, newValue) => {
@@ -670,13 +663,14 @@ function Bot({
670
663
  }), /*#__PURE__*/_jsx("div", {
671
664
  className: "grow-1"
672
665
  }), /*#__PURE__*/_jsx(Tooltip, {
673
- title: t("Rewatch tutorial"),
666
+ title: t("rewatch_tutorial"),
674
667
  children: /*#__PURE__*/_jsx(IconButton, {
675
668
  color: "inherit",
676
- onClick: () => setJoyrideState({
677
- ...joyrideState,
678
- run: true
679
- }),
669
+ onClick: () => startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
670
+ ...prevState,
671
+ run: true,
672
+ steps
673
+ }))),
680
674
  size: "large",
681
675
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
682
676
  })
@@ -825,6 +819,7 @@ function Bot({
825
819
  close: t("close"),
826
820
  last: t("last"),
827
821
  next: t("next"),
822
+ nextWithProgress: t("next_with_progress"),
828
823
  skip: t("skip")
829
824
  },
830
825
  options: {
@@ -475,7 +475,7 @@ function Editor({
475
475
  width: "100%"
476
476
  },
477
477
  children: [/*#__PURE__*/_jsx(Tooltip, {
478
- title: isDisabled ? t("Please select a textfield") : t("formatting_options"),
478
+ title: isDisabled ? t("please_select_a_textfield") : t("formatting_options"),
479
479
  children: /*#__PURE__*/_jsx("span", {
480
480
  children: /*#__PURE__*/_jsx(IconButton, {
481
481
  color: "inherit",
@@ -489,7 +489,7 @@ function Editor({
489
489
  })
490
490
  })
491
491
  }), /*#__PURE__*/_jsx(Tooltip, {
492
- title: isDisabled ? t("Please select a textfield") : t("insert_link"),
492
+ title: isDisabled ? t("please_select_a_textfield") : t("insert_link"),
493
493
  children: /*#__PURE__*/_jsx("span", {
494
494
  children: /*#__PURE__*/_jsx(IconButton, {
495
495
  color: "inherit",
@@ -26,6 +26,7 @@ import useExerciseState from "../../hooks/useExerciseState";
26
26
  import useConfirm from "../../hooks/useConfirm";
27
27
  import { handleScriptDownload } from "../../hooks/handleScriptDownload";
28
28
  import { formatMarkdownNewlines } from "../../utils";
29
+ import { startJoyrideWhenReady } from "../../utils/joyride";
29
30
  import DefaultButton from "../../Misc/DefaultColourButton/DefaultColourButton";
30
31
  import { removeExtraWhiteSpaces } from "../../utils/index";
31
32
  import GenerateQuestion from "../../Dialogs/GenerateQuestion/GenerateQuestion";
@@ -388,18 +389,20 @@ function Roleplay({
388
389
  }));
389
390
  }
390
391
  if (tabValue === 0 && !localStorage.getItem(`roleplay-editor-tour-completed`)) {
391
- setJoyrideState(prevState => ({
392
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
392
393
  ...prevState,
393
- run: true
394
- }));
394
+ run: true,
395
+ steps
396
+ })));
395
397
  }
396
398
  }, [tabValue]);
397
399
  useEffect(() => {
398
400
  if (open && !localStorage.getItem(`roleplay-editor-tour-completed`)) {
399
- setJoyrideState(prevState => ({
401
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
400
402
  ...prevState,
401
- run: true
402
- }));
403
+ run: true,
404
+ steps
405
+ })));
403
406
  }
404
407
  }, [open]);
405
408
 
@@ -719,13 +722,14 @@ function Roleplay({
719
722
  },
720
723
  children: roleplay.roleplayName
721
724
  }), /*#__PURE__*/_jsx(Tooltip, {
722
- title: t("Rewatch tutorial"),
725
+ title: t("rewatch_tutorial"),
723
726
  children: /*#__PURE__*/_jsx(IconButton, {
724
727
  color: "inherit",
725
- onClick: () => setJoyrideState({
726
- ...joyrideState,
727
- run: true
728
- }),
728
+ onClick: () => startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
729
+ ...prevState,
730
+ run: true,
731
+ steps
732
+ }))),
729
733
  size: "large",
730
734
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
731
735
  })
@@ -937,6 +941,7 @@ function Roleplay({
937
941
  close: t("close"),
938
942
  last: t("last"),
939
943
  next: t("next"),
944
+ nextWithProgress: t("next_with_progress"),
940
945
  skip: t("skip")
941
946
  },
942
947
  options: {
@@ -21,6 +21,7 @@ import useExerciseState from "../../hooks/useExerciseState";
21
21
  import VoiceSpeedSelector from "../../Misc/VoiceSpeed/VoiceSpeed";
22
22
  import ReportProblem from "../../Misc/ReportProblem/ReportProblem";
23
23
  import NualaSignInImage from "../../img/nuala_sign_in.svg";
24
+ import { startJoyrideWhenReady } from "../../utils/joyride";
24
25
  import { useTheme } from "@mui/material/styles";
25
26
  import config from "../../config";
26
27
  import { useNavigate } from "react-router";
@@ -393,10 +394,11 @@ function Bot({
393
394
  }, [currentPhrase, bot, isSpeaking]);
394
395
  useEffect(() => {
395
396
  if (open && !run && !localStorage.getItem(`bot-exercise-tour-completed`)) {
396
- setJoyrideState(prevState => ({
397
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
397
398
  ...prevState,
398
- run: true
399
- }));
399
+ run: true,
400
+ steps
401
+ })));
400
402
  } else {
401
403
  if (open && !joyrideRewatched && !run && bot.studentInstructions && bot.studentInstructions !== "") {
402
404
  handleClickOpenInstructions();
@@ -424,15 +426,16 @@ function Bot({
424
426
 
425
427
  const topBarActions = /*#__PURE__*/_jsxs(_Fragment, {
426
428
  children: [/*#__PURE__*/_jsx(Tooltip, {
427
- title: t("Rewatch tutorial"),
429
+ title: t("rewatch_tutorial"),
428
430
  children: /*#__PURE__*/_jsx(IconButton, {
429
431
  color: "inherit",
430
432
  onClick: () => {
431
433
  setJoyrideRewatched(true);
432
- setJoyrideState({
433
- ...joyrideState,
434
- run: true
435
- });
434
+ startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
435
+ ...prevState,
436
+ run: true,
437
+ steps
438
+ })));
436
439
  },
437
440
  size: "large",
438
441
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
@@ -698,6 +701,7 @@ function Bot({
698
701
  close: t("close"),
699
702
  last: t("last"),
700
703
  next: t("next"),
704
+ nextWithProgress: t("next_with_progress"),
701
705
  skip: t("skip")
702
706
  },
703
707
  options: {
@@ -22,12 +22,11 @@ import ExerciseBottomBar from "../../Misc/ExerciseBottomBar/ExerciseBottomBar";
22
22
  import useExerciseState from "../../hooks/useExerciseState";
23
23
  import useExerciseDocumentTitle from "../../hooks/useExerciseDocumentTitle";
24
24
  import useRecognition from "../../hooks/useRecognition";
25
- import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
26
25
  import VoiceSpeedSelector from "../../Misc/VoiceSpeed/VoiceSpeed";
27
26
  import ReportProblem from "../../Misc/ReportProblem/ReportProblem";
28
- import ChatBubble from "../../Misc/ChatBubble/ChatBubble";
29
27
  import WaveFormLite from "../../Misc/WaveFormLite/WaveFormLite";
30
28
  import { removeExtraWhiteSpaces, removeAllSymbols } from "../../utils";
29
+ import { startJoyrideWhenReady } from "../../utils/joyride";
31
30
  import getVoiceImages from "../../utils/voiceImages";
32
31
  import { useNavigate } from "react-router";
33
32
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -340,16 +339,6 @@ function ListenAndSpeak({
340
339
  }),
341
340
  placement: "center",
342
341
  target: "body"
343
- }, {
344
- content: /*#__PURE__*/_jsxs("div", {
345
- children: [/*#__PURE__*/_jsx("h2", {
346
- children: t("pronunciation_exercise_speak")
347
- }), /*#__PURE__*/_jsx("br", {}), /*#__PURE__*/_jsx("p", {
348
- children: t("listen_and_speak_exercise_speak_desc")
349
- })]
350
- }),
351
- spotlightPadding: 8,
352
- target: "#speakButton"
353
342
  }, {
354
343
  content: /*#__PURE__*/_jsx("div", {
355
344
  children: /*#__PURE__*/_jsx("h2", {
@@ -429,10 +418,11 @@ function ListenAndSpeak({
429
418
  }, [isSpeaking, isSocketConnected]);
430
419
  useEffect(() => {
431
420
  if (open && !localStorage.getItem(`meaning-listen-and-speak-exercise-tour-completed`)) {
432
- setJoyrideState(prevState => ({
421
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
433
422
  ...prevState,
434
- run: true
435
- }));
423
+ run: true,
424
+ steps
425
+ })));
436
426
  } else if (open && !run && !joyrideRewatched && localStorage.getItem(`meaning-listen-and-speak-exercise-tour-completed`)) {
437
427
  handlePronunciationTourFinished();
438
428
  }
@@ -524,15 +514,16 @@ function ListenAndSpeak({
524
514
  children: t("meaning")
525
515
  })
526
516
  }), /*#__PURE__*/_jsx(Tooltip, {
527
- title: t("Rewatch tutorial"),
517
+ title: t("rewatch_tutorial"),
528
518
  children: /*#__PURE__*/_jsx(IconButton, {
529
519
  color: "inherit",
530
520
  onClick: () => {
531
521
  setJoyrideRewatched(true);
532
- setJoyrideState({
533
- ...joyrideState,
534
- run: true
535
- });
522
+ startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
523
+ ...prevState,
524
+ run: true,
525
+ steps
526
+ })));
536
527
  },
537
528
  size: "large",
538
529
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
@@ -950,6 +941,7 @@ function ListenAndSpeak({
950
941
  close: t("close"),
951
942
  last: t("last"),
952
943
  next: t("next"),
944
+ nextWithProgress: t("next_with_progress"),
953
945
  skip: t("skip")
954
946
  },
955
947
  options: {
@@ -30,6 +30,7 @@ import { Sortable, SortableGridDndContextWrapper } from "../../utils/dragAndDrop
30
30
 
31
31
  // utils
32
32
  import { capitalize, removeExtraWhiteSpaces } from "../../utils/index";
33
+ import { startJoyrideWhenReady } from "../../utils/joyride";
33
34
  import useWordBankFocus from "../../hooks/useWordBankFocus";
34
35
  import getVoiceImages from "../../utils/voiceImages";
35
36
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -379,16 +380,6 @@ function Listener({
379
380
  }),
380
381
  spotlightPadding: 8,
381
382
  target: "#speakButton"
382
- }, {
383
- content: /*#__PURE__*/_jsxs("div", {
384
- children: [/*#__PURE__*/_jsx("h2", {
385
- children: t("listening_exercise_voice_options")
386
- }), /*#__PURE__*/_jsx("br", {}), /*#__PURE__*/_jsx("p", {
387
- children: t("listening_exercise_voice_options_desc")
388
- })]
389
- }),
390
- spotlightPadding: 8,
391
- target: "#voiceOptions"
392
383
  }, {
393
384
  content: /*#__PURE__*/_jsxs("div", {
394
385
  children: [/*#__PURE__*/_jsx("h2", {
@@ -480,10 +471,11 @@ function Listener({
480
471
  }, [useWordBank]);
481
472
  useEffect(() => {
482
473
  if (open && !run && !localStorage.getItem(`listening-exercise-tour-completed`)) {
483
- setJoyrideState(prevState => ({
474
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
484
475
  ...prevState,
485
- run: true
486
- }));
476
+ run: true,
477
+ steps
478
+ })));
487
479
  } else if (open && !run && !joyrideRewatched && localStorage.getItem(`listening-exercise-tour-completed`)) {
488
480
  handleListeningTourFinished();
489
481
  setTimeout(() => setWordBankTooltipOpen(true), 1000);
@@ -586,15 +578,16 @@ function Listener({
586
578
  },
587
579
  children: t("listening")
588
580
  }), /*#__PURE__*/_jsx(Tooltip, {
589
- title: t("Rewatch tutorial"),
581
+ title: t("rewatch_tutorial"),
590
582
  children: /*#__PURE__*/_jsx(IconButton, {
591
583
  color: "inherit",
592
584
  onClick: () => {
593
585
  setJoyrideRewatched(true);
594
- setJoyrideState({
595
- ...joyrideState,
596
- run: true
597
- });
586
+ startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
587
+ ...prevState,
588
+ run: true,
589
+ steps
590
+ })));
598
591
  },
599
592
  size: "large",
600
593
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
@@ -735,6 +728,7 @@ function Listener({
735
728
  marginLeft: 1
736
729
  },
737
730
  children: learnLang !== "irish" && /*#__PURE__*/_jsx(Box, {
731
+ id: "speakButton",
738
732
  sx: theme => ({
739
733
  [theme.breakpoints.down("md")]: {
740
734
  minWidth: 185
@@ -886,6 +880,7 @@ function Listener({
886
880
  })
887
881
  })
888
882
  }), isWordBankEnabled && /*#__PURE__*/_jsx(Grid, {
883
+ id: "useWordBank",
889
884
  children: /*#__PURE__*/_jsx(Tooltip, {
890
885
  title: t("use_wordbank_if_stuck"),
891
886
  enterDelay: 500,
@@ -909,10 +904,7 @@ function Listener({
909
904
  run: false
910
905
  }));
911
906
  },
912
- startIcon: /*#__PURE__*/_jsx(Bank, {})
913
- // Commented out to fix lint warning - jsx-no-duplicate-props
914
- // id="useWordBank"
915
- ,
907
+ startIcon: /*#__PURE__*/_jsx(Bank, {}),
916
908
  children: t("use_word_bank")
917
909
  })
918
910
  })
@@ -1010,6 +1002,7 @@ function Listener({
1010
1002
  close: t("close"),
1011
1003
  last: t("last"),
1012
1004
  next: t("next"),
1005
+ nextWithProgress: t("next_with_progress"),
1013
1006
  skip: t("skip")
1014
1007
  },
1015
1008
  options: {
@@ -26,6 +26,7 @@ import { useTheme } from "@mui/material/styles";
26
26
  import getVoiceImages from "../../utils/voiceImages";
27
27
  import { useNavigate } from "react-router";
28
28
  import { removeExtraWhiteSpaces, shuffle } from "../../utils/index";
29
+ import { startJoyrideWhenReady } from "../../utils/joyride";
29
30
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
30
31
  const useStyles = makeStyles()(theme => ({
31
32
  appBar: {
@@ -335,10 +336,11 @@ function Meaning({
335
336
  };
336
337
  useEffect(() => {
337
338
  if (open && !run && !localStorage.getItem("meaning-exercise-tour-completed")) {
338
- setJoyrideState(prev => ({
339
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prev => ({
339
340
  ...prev,
340
- run: true
341
- }));
341
+ run: true,
342
+ steps
343
+ })));
342
344
  } else if (open && !run && !joyrideRewatched && localStorage.getItem("meaning-exercise-tour-completed")) {
343
345
  handleTranslationTourFinished();
344
346
  }
@@ -454,15 +456,16 @@ function Meaning({
454
456
  },
455
457
  children: t("meaning")
456
458
  }), /*#__PURE__*/_jsx(Tooltip, {
457
- title: t("Rewatch tutorial"),
459
+ title: t("rewatch_tutorial"),
458
460
  children: /*#__PURE__*/_jsx(IconButton, {
459
461
  color: "inherit",
460
462
  onClick: () => {
461
463
  setJoyrideRewatched(true);
462
- setJoyrideState({
463
- ...joyrideState,
464
- run: true
465
- });
464
+ startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
465
+ ...prevState,
466
+ run: true,
467
+ steps
468
+ })));
466
469
  },
467
470
  size: "large",
468
471
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
@@ -787,6 +790,7 @@ function Meaning({
787
790
  close: t("close"),
788
791
  last: t("last"),
789
792
  next: t("next"),
793
+ nextWithProgress: t("next_with_progress"),
790
794
  skip: t("skip")
791
795
  },
792
796
  options: {
@@ -5,6 +5,7 @@ import { useTheme } from "@mui/material/styles";
5
5
  import { makeStyles } from "tss-react/mui";
6
6
  import useExerciseState from "../../hooks/useExerciseState";
7
7
  import { removeExtraWhiteSpaces } from "../../utils/index";
8
+ import { startJoyrideWhenReady } from "../../utils/joyride";
8
9
  import { Grid, Slide, IconButton, Dialog, AppBar, Toolbar, Tooltip, Typography, Box } from "@mui/material";
9
10
  import { styled } from "@mui/system";
10
11
  import LightbulbIcon from "@mui/icons-material/Lightbulb";
@@ -268,19 +269,12 @@ export default function MeaningReadWrite({
268
269
  }),
269
270
  spotlightPadding: 8,
270
271
  target: "#skipButton"
271
- }, {
272
- content: /*#__PURE__*/_jsx("h2", {
273
- children: t("change_voice_speed")
274
- }),
275
- spotlightPadding: 8,
276
- target: "#change-voice-speed"
277
- }, {
278
- content: /*#__PURE__*/_jsx("h2", {
279
- children: t("exercise_mute")
280
- }),
281
- spotlightPadding: 8,
282
- target: "#mute"
283
- }, {
272
+ },
273
+ // No change_voice_speed or exercise_mute step: this exercise has neither control.
274
+ // It speaks only through the answer result dialogs, and that path calls the raw
275
+ // handleSpeak prop rather than the muteSound-aware speak() from useExerciseState,
276
+ // so a mute button here would not silence anything the learner hears.
277
+ {
284
278
  content: /*#__PURE__*/_jsx("h2", {
285
279
  children: t("exercise_restart")
286
280
  }),
@@ -298,10 +292,11 @@ export default function MeaningReadWrite({
298
292
  } = joyrideState;
299
293
  useEffect(() => {
300
294
  if (open && !run && !localStorage.getItem("meaning-read-write-exercise-tour-completed")) {
301
- setJoyrideState(prev => ({
295
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prev => ({
302
296
  ...prev,
303
- run: true
304
- }));
297
+ run: true,
298
+ steps
299
+ })));
305
300
  } else if (open && !run && !joyrideRewatched && localStorage.getItem("meaning-read-write-exercise-tour-completed")) {
306
301
  handleTranslationTourFinished();
307
302
  }
@@ -429,10 +424,11 @@ export default function MeaningReadWrite({
429
424
  color: "inherit",
430
425
  onClick: () => {
431
426
  setJoyrideRewatched(true);
432
- setJoyrideState({
433
- ...joyrideState,
434
- run: true
435
- });
427
+ startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
428
+ ...prevState,
429
+ run: true,
430
+ steps
431
+ })));
436
432
  },
437
433
  size: "large",
438
434
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
@@ -714,6 +710,7 @@ export default function MeaningReadWrite({
714
710
  close: t("close"),
715
711
  last: t("last"),
716
712
  next: t("next"),
713
+ nextWithProgress: t("next_with_progress"),
717
714
  skip: t("skip")
718
715
  },
719
716
  options: {
@@ -28,6 +28,7 @@ import ReportProblem from "../../Misc/ReportProblem/ReportProblem";
28
28
  import ChatBubble from "../../Misc/ChatBubble/ChatBubble";
29
29
  import WaveFormLite from "../../Misc/WaveFormLite/WaveFormLite";
30
30
  import { removeExtraWhiteSpaces, removeAllSymbols } from "../../utils";
31
+ import { startJoyrideWhenReady } from "../../utils/joyride";
31
32
  import getVoiceImages from "../../utils/voiceImages";
32
33
  import { useNavigate } from "react-router";
33
34
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -429,10 +430,11 @@ function Pronouncer({
429
430
  }, [isSpeaking, isSocketConnected]);
430
431
  useEffect(() => {
431
432
  if (open && !localStorage.getItem(`pronunciation-exercise-tour-completed`)) {
432
- setJoyrideState(prevState => ({
433
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
433
434
  ...prevState,
434
- run: true
435
- }));
435
+ run: true,
436
+ steps
437
+ })));
436
438
  } else if (open && !run && !joyrideRewatched && localStorage.getItem(`pronunciation-exercise-tour-completed`)) {
437
439
  handlePronunciationTourFinished();
438
440
  }
@@ -508,15 +510,16 @@ function Pronouncer({
508
510
  },
509
511
  children: t("pronunciation")
510
512
  }), /*#__PURE__*/_jsx(Tooltip, {
511
- title: t("Rewatch tutorial"),
513
+ title: t("rewatch_tutorial"),
512
514
  children: /*#__PURE__*/_jsx(IconButton, {
513
515
  color: "inherit",
514
516
  onClick: () => {
515
517
  setJoyrideRewatched(true);
516
- setJoyrideState({
517
- ...joyrideState,
518
- run: true
519
- });
518
+ startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
519
+ ...prevState,
520
+ run: true,
521
+ steps
522
+ })));
520
523
  },
521
524
  size: "large",
522
525
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
@@ -723,6 +726,7 @@ function Pronouncer({
723
726
  muteSound: muteSound,
724
727
  t: t
725
728
  }), learnLang !== "irish" && /*#__PURE__*/_jsx(Box, {
729
+ id: "speakButton",
726
730
  children: /*#__PURE__*/_jsx(WaveFormLite, {
727
731
  src: pollyResult ? pollyResult : "",
728
732
  controls: false,
@@ -959,6 +963,7 @@ function Pronouncer({
959
963
  close: t("close"),
960
964
  last: t("last"),
961
965
  next: t("next"),
966
+ nextWithProgress: t("next_with_progress"),
962
967
  skip: t("skip")
963
968
  },
964
969
  options: {
@@ -28,6 +28,7 @@ import { formatMarkdownNewlines, randomId } from "../../utils/index";
28
28
  import VoiceSpeedSelector from "../../Misc/VoiceSpeed/VoiceSpeed";
29
29
  import ReportProblem from "../../Misc/ReportProblem/ReportProblem";
30
30
  import { removeExtraWhiteSpaces } from "../../utils";
31
+ import { startJoyrideWhenReady } from "../../utils/joyride";
31
32
  import DefaultButton from "../../Misc/DefaultColourButton/DefaultColourButton";
32
33
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
33
34
  const useStyles = makeStyles()((theme, {
@@ -579,10 +580,11 @@ function Roleplay({
579
580
  };
580
581
  useEffect(() => {
581
582
  if (open && !localStorage.getItem(`roleplay-exercise-tour-completed`)) {
582
- setJoyrideState(prevState => ({
583
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
583
584
  ...prevState,
584
- run: true
585
- }));
585
+ run: true,
586
+ steps
587
+ })));
586
588
  }
587
589
  }, [open]);
588
590
  useEffect(() => {
@@ -922,6 +924,7 @@ function Roleplay({
922
924
  close: t("close"),
923
925
  last: t("last"),
924
926
  next: t("next"),
927
+ nextWithProgress: t("next_with_progress"),
925
928
  skip: t("skip")
926
929
  },
927
930
  options: {
@@ -380,6 +380,7 @@ function Roleplay({
380
380
  close: t("close"),
381
381
  last: t("last"),
382
382
  next: t("next"),
383
+ nextWithProgress: t("next_with_progress"),
383
384
  skip: t("skip")
384
385
  },
385
386
  options: {
@@ -33,6 +33,7 @@ import { useNavigate } from "react-router";
33
33
 
34
34
  // utils
35
35
  import { capitalize, removeExtraWhiteSpaces, removeAllSymbols } from "../../utils/index";
36
+ import { startJoyrideWhenReady } from "../../utils/joyride";
36
37
  import useWordBankFocus from "../../hooks/useWordBankFocus";
37
38
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
38
39
  const useStyles = makeStyles()(theme => ({
@@ -475,10 +476,11 @@ function Translator({
475
476
  }, [useWordBank]);
476
477
  useEffect(() => {
477
478
  if (open && !run && !localStorage.getItem(`translation-exercise-tour-completed`)) {
478
- setJoyrideState(prevState => ({
479
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
479
480
  ...prevState,
480
- run: true
481
- }));
481
+ run: true,
482
+ steps
483
+ })));
482
484
  } else if (open && !run && !joyrideRewatched && localStorage.getItem(`translation-exercise-tour-completed`)) {
483
485
  handleTranslationTourFinished();
484
486
  setTimeout(() => setWordBankTooltipOpen(true), 1000);
@@ -586,15 +588,16 @@ function Translator({
586
588
  },
587
589
  children: t("translation")
588
590
  }), /*#__PURE__*/_jsx(Tooltip, {
589
- title: t("Rewatch tutorial"),
591
+ title: t("rewatch_tutorial"),
590
592
  children: /*#__PURE__*/_jsx(IconButton, {
591
593
  color: "inherit",
592
594
  onClick: () => {
593
595
  setJoyrideRewatched(true);
594
- setJoyrideState({
595
- ...joyrideState,
596
- run: true
597
- });
596
+ startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
597
+ ...prevState,
598
+ run: true,
599
+ steps
600
+ })));
598
601
  },
599
602
  size: "large",
600
603
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
@@ -754,6 +757,7 @@ function Translator({
754
757
  t: t,
755
758
  languageTag: languageTag
756
759
  }), learnLang !== "irish" && /*#__PURE__*/_jsx(Box, {
760
+ id: "speakButton",
757
761
  children: /*#__PURE__*/_jsx(WaveFormLite, {
758
762
  src: pollyResult ? pollyResult : "",
759
763
  controls: false,
@@ -911,6 +915,7 @@ function Translator({
911
915
  })]
912
916
  })
913
917
  }), isWordBankEnabled && /*#__PURE__*/_jsx(Grid, {
918
+ id: "useWordBank",
914
919
  children: /*#__PURE__*/_jsx(Tooltip, {
915
920
  title: t("use_wordbank_if_stuck"),
916
921
  enterDelay: 500,
@@ -1009,6 +1014,7 @@ function Translator({
1009
1014
  close: t("close"),
1010
1015
  last: t("last"),
1011
1016
  next: t("next"),
1017
+ nextWithProgress: t("next_with_progress"),
1012
1018
  skip: t("skip")
1013
1019
  },
1014
1020
  options: {
@@ -48,7 +48,7 @@ function FeedbackForm({
48
48
  const theme = useTheme();
49
49
  const [allSelected, setAllSelected] = useState("false");
50
50
  const handleAttendeeChange = event => {
51
- if (event.target.value === t("All")) {
51
+ if (event.target.value === t("all")) {
52
52
  setAllSelected(true);
53
53
  handleChange({
54
54
  target: {
@@ -305,7 +305,7 @@ function FeedbackForm({
305
305
  label: "Student",
306
306
  disabled: attendeesData?.length < 1,
307
307
  children: [{
308
- username: t("All")
308
+ username: t("all")
309
309
  }, ...attendeesData].map((attendee, index) => /*#__PURE__*/_jsx(MenuItem, {
310
310
  value: index === 0 ? "All" : attendeesData[index - 1],
311
311
  children: /*#__PURE__*/_jsxs(Box, {
@@ -524,7 +524,7 @@ function ImageSelector({
524
524
  children: /*#__PURE__*/_jsx(Typography, {
525
525
  component: "p",
526
526
  variant: "body1",
527
- children: t("Drag and drop image from your computer")
527
+ children: t("Drag_and_drop_image_from_your_computer")
528
528
  })
529
529
  })]
530
530
  }), /*#__PURE__*/_jsxs("div", {
@@ -542,7 +542,7 @@ function ImageSelector({
542
542
  className: classes.button,
543
543
  onClick: () => setIsImageLibraryOpen(!isImageLibraryOpen),
544
544
  startIcon: /*#__PURE__*/_jsx(CollectionsIcon, {}),
545
- children: t("Image Library")
545
+ children: t("image_library")
546
546
  }), /*#__PURE__*/_jsx("input", {
547
547
  accept: "image/*",
548
548
  className: classes.input,
@@ -556,7 +556,7 @@ function ImageSelector({
556
556
  className: classes.button,
557
557
  component: "span",
558
558
  startIcon: /*#__PURE__*/_jsx(CloudUploadIcon, {}),
559
- children: t("Upload Image")
559
+ children: t("upload_image")
560
560
  })
561
561
  }), /*#__PURE__*/_jsx(DefaultButton, {
562
562
  variant: "outlined",
@@ -214,7 +214,7 @@ function PdfUploader({
214
214
  },
215
215
  children: [/*#__PURE__*/_jsx(Grid, {
216
216
  children: /*#__PURE__*/_jsx(Tooltip, {
217
- title: t("Previous"),
217
+ title: t("previous"),
218
218
  children: /*#__PURE__*/_jsx("span", {
219
219
  children: /*#__PURE__*/_jsx(IconButton, {
220
220
  size: "large",
@@ -231,7 +231,7 @@ function PdfUploader({
231
231
  })
232
232
  }), /*#__PURE__*/_jsx(Grid, {
233
233
  children: /*#__PURE__*/_jsx(Tooltip, {
234
- title: t("Next"),
234
+ title: t("next"),
235
235
  children: /*#__PURE__*/_jsx("span", {
236
236
  children: /*#__PURE__*/_jsx(IconButton, {
237
237
  size: "large",
@@ -50,7 +50,7 @@ export default function Settings({
50
50
  label: t("subscription"),
51
51
  id: "Subscription"
52
52
  }, !isGenerated ? {
53
- label: t("Email Notifications"),
53
+ label: t("email_notifications"),
54
54
  id: "EmailNotifications"
55
55
  } : null];
56
56
  } else {
@@ -24,6 +24,7 @@ import GameSettings from "../GameSettings/GameSettings";
24
24
  import FinalScoreDrawer from "../Misc/FinalScoreDrawer/FinalScoreDrawer";
25
25
  import RewardEarned from "../../Dialogs/RewardEarned/RewardEarned";
26
26
  import LiveGameTutorial from "../../Dialogs/LiveGameTutorial/LiveGameTutorial";
27
+ import { startJoyrideWhenReady } from "../../utils/joyride";
27
28
  import HelpIcon from "@mui/icons-material/Help";
28
29
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
29
30
  const useStyles = makeStyles()(theme => ({
@@ -244,10 +245,11 @@ export default function Game({
244
245
  });
245
246
  useEffect(() => {
246
247
  if (open && isCreator && !localStorage.getItem(`nualang-live-tour-completed`)) {
247
- setJoyrideState(prevState => ({
248
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
248
249
  ...prevState,
249
- run: true
250
- }));
250
+ run: true,
251
+ steps
252
+ })));
251
253
  }
252
254
  }, [open, isCreator]);
253
255
  const handleJoyrideCallback = data => {
@@ -368,13 +370,14 @@ export default function Game({
368
370
  children: /*#__PURE__*/_jsx(SettingsIcon, {})
369
371
  })
370
372
  }), /*#__PURE__*/_jsx(Tooltip, {
371
- title: t("Rewatch tutorial"),
373
+ title: t("rewatch_tutorial"),
372
374
  children: /*#__PURE__*/_jsx(IconButton, {
373
375
  color: "inherit",
374
- onClick: () => setJoyrideState({
375
- ...joyrideState,
376
- run: true
377
- }),
376
+ onClick: () => startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
377
+ ...prevState,
378
+ run: true,
379
+ steps
380
+ }))),
378
381
  size: "large",
379
382
  children: /*#__PURE__*/_jsx(LightbulbIcon, {})
380
383
  })
@@ -650,6 +653,7 @@ export default function Game({
650
653
  close: t("close"),
651
654
  last: t("last"),
652
655
  next: t("next"),
656
+ nextWithProgress: t("next_with_progress"),
653
657
  skip: t("skip")
654
658
  },
655
659
  options: {
@@ -69,7 +69,7 @@ export default function GameMembers({
69
69
  id: "search",
70
70
  name: "search",
71
71
  "data-cy": "search",
72
- label: t("Search"),
72
+ label: t("search"),
73
73
  value: searchText,
74
74
  onChange: ev => setSearchText(ev.target.value),
75
75
  margin: "dense",
@@ -94,7 +94,7 @@ export default function GameMembers({
94
94
  setMembersInRoomOpen(!membersInRoomOpen);
95
95
  },
96
96
  children: [/*#__PURE__*/_jsx(ListItemText, {
97
- primary: "In Game"
97
+ primary: t("in_game")
98
98
  }), /*#__PURE__*/_jsx(Typography, {
99
99
  sx: {
100
100
  pr: 1
@@ -134,7 +134,7 @@ export default function GameMembers({
134
134
  setMembersNotInRoomOpen(!membersNotInRoomOpen);
135
135
  },
136
136
  children: [/*#__PURE__*/_jsx(ListItemText, {
137
- primary: "Not In Game"
137
+ primary: t("not_in_game")
138
138
  }), /*#__PURE__*/_jsx(Typography, {
139
139
  sx: {
140
140
  pr: 1
@@ -86,7 +86,7 @@ export default function GameSettings({
86
86
  variant: "h5",
87
87
  component: "h1",
88
88
  gutterBottom: true,
89
- children: "Game Settings"
89
+ children: t("game_settings")
90
90
  }), /*#__PURE__*/_jsx(Grid, {
91
91
  sx: {
92
92
  mt: 2
@@ -5,7 +5,20 @@ import SwipeableDrawer from "@mui/material/SwipeableDrawer";
5
5
  import { useTheme } from "@mui/material/styles";
6
6
  import { QRCode } from "react-qrcode-logo";
7
7
  import nualangImage from "../../img/nualangSquareIcon.svg";
8
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+
9
+ // Sentinel handed to i18next as the interpolation value so we can split the
10
+ // translated sentence and drop a React node in the placeholder's position.
11
+ // Splitting on the real value would break when it is empty or repeated, and
12
+ // splitting on the raw "{{name}}" token would not work because i18next has
13
+ // already substituted it.
14
+ import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
15
+ const PLACEHOLDER = "\u0000";
16
+ function withNode(sentence, node) {
17
+ const [before, after = ""] = sentence.split(PLACEHOLDER);
18
+ return /*#__PURE__*/_jsxs(_Fragment, {
19
+ children: [before, node, after]
20
+ });
21
+ }
9
22
  export default function HowToJoin({
10
23
  t,
11
24
  classroomName,
@@ -30,11 +43,11 @@ export default function HowToJoin({
30
43
  variant: "h5",
31
44
  component: "h1",
32
45
  gutterBottom: true,
33
- children: "How to Join the Game"
46
+ children: t("how_to_join_title")
34
47
  }), /*#__PURE__*/_jsx(Typography, {
35
48
  variant: "body1",
36
49
  gutterBottom: true,
37
- children: "To join the game, follow these steps:"
50
+ children: t("how_to_join_intro")
38
51
  }), /*#__PURE__*/_jsxs(List, {
39
52
  sx: {
40
53
  listStyleType: "decimal",
@@ -48,20 +61,22 @@ export default function HowToJoin({
48
61
  primary: /*#__PURE__*/_jsx(Typography, {
49
62
  component: "span",
50
63
  variant: "body1",
51
- children: "Sign in to your account."
64
+ children: t("how_to_join_sign_in")
52
65
  })
53
66
  })
54
67
  }), /*#__PURE__*/_jsx(ListItem, {
55
68
  children: /*#__PURE__*/_jsx(ListItemText, {
56
- primary: /*#__PURE__*/_jsxs(Typography, {
69
+ primary: /*#__PURE__*/_jsx(Typography, {
57
70
  component: "span",
58
71
  variant: "body1",
59
- children: ["Open the classroom", " ", /*#__PURE__*/_jsx(Link, {
72
+ children: withNode(t("how_to_join_open_classroom", {
73
+ classroom: PLACEHOLDER
74
+ }), /*#__PURE__*/_jsx(Link, {
60
75
  href: classroomURL,
61
76
  target: "_blank",
62
77
  rel: "noreferrer",
63
78
  children: classroomName
64
- }), " ", "or scan its QR Code below", " "]
79
+ }))
65
80
  })
66
81
  })
67
82
  }), /*#__PURE__*/_jsx(Box, {
@@ -81,12 +96,14 @@ export default function HowToJoin({
81
96
  })
82
97
  }), /*#__PURE__*/_jsx(ListItem, {
83
98
  children: /*#__PURE__*/_jsx(ListItemText, {
84
- primary: /*#__PURE__*/_jsxs(Typography, {
99
+ primary: /*#__PURE__*/_jsx(Typography, {
85
100
  component: "span",
86
101
  variant: "body1",
87
- children: ["Look for the ", /*#__PURE__*/_jsx("b", {
102
+ children: withNode(t("how_to_join_find_button", {
103
+ button: PLACEHOLDER
104
+ }), /*#__PURE__*/_jsx("b", {
88
105
  children: t("join_live_game")
89
- }), " button and click on it."]
106
+ }))
90
107
  })
91
108
  })
92
109
  }), /*#__PURE__*/_jsx(ListItem, {
@@ -94,7 +111,7 @@ export default function HowToJoin({
94
111
  primary: /*#__PURE__*/_jsx(Typography, {
95
112
  component: "span",
96
113
  variant: "body1",
97
- children: "Get ready to play."
114
+ children: t("how_to_join_get_ready")
98
115
  })
99
116
  })
100
117
  })]
@@ -450,10 +450,7 @@ function LiveListener({
450
450
  onClick: () => {
451
451
  handleUseWordBank();
452
452
  },
453
- startIcon: /*#__PURE__*/_jsx(Bank, {})
454
- // Commented out to fix lint warning - jsx-no-duplicate-props
455
- // id="useWordBank"
456
- ,
453
+ startIcon: /*#__PURE__*/_jsx(Bank, {}),
457
454
  children: t("use_word_bank")
458
455
  })
459
456
  })
@@ -104,7 +104,7 @@ const Search = ({
104
104
  className: className,
105
105
  onChange: e => setSearchText(e.target.value),
106
106
  type: "text",
107
- placeholder: t("Search images"),
107
+ placeholder: t("search_images"),
108
108
  value: searchText,
109
109
  margin: "dense",
110
110
  variant: "outlined",
@@ -213,7 +213,7 @@ function ImageLibrary({
213
213
  id: "selectedCategory",
214
214
  name: "selectedCategory",
215
215
  "data-cy": "selectedCategory",
216
- label: t("Categories"),
216
+ label: t("categories"),
217
217
  value: selectedCategory || "",
218
218
  onClick: event => {
219
219
  handleCategoryClick(event.target.dataset.value);
@@ -237,7 +237,7 @@ function ImageLibrary({
237
237
  className: classes.categories,
238
238
  children: [/*#__PURE__*/_jsx(Typography, {
239
239
  className: classes.categoriesTitle,
240
- children: t("Categories")
240
+ children: t("categories")
241
241
  }), /*#__PURE__*/_jsx(List, {
242
242
  className: classes.list,
243
243
  component: "nav",
@@ -17,6 +17,7 @@ import { makeStyles } from "tss-react/mui";
17
17
  import Typography from "@mui/material/Typography";
18
18
  import NewText from "../../img/NewText.svg";
19
19
  import { Joyride, STATUS } from "react-joyride";
20
+ import { startJoyrideWhenReady } from "../../utils/joyride";
20
21
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
21
22
  function TabPanel(props) {
22
23
  const {
@@ -192,25 +193,27 @@ function ResponsiveTabs({
192
193
  spotlightPadding: 8,
193
194
  target: "#discussGroupedDiscussions"
194
195
  }, {
196
+ // Submissions no longer have their own list — they were combined into the
197
+ // grouped discussions view, so this step points at the same container.
195
198
  content: /*#__PURE__*/_jsxs("div", {
196
199
  children: [/*#__PURE__*/_jsx("h2", {
197
- children: t("discuss_refresh_discussions")
200
+ children: t("discuss_submissions")
198
201
  }), /*#__PURE__*/_jsx("br", {}), /*#__PURE__*/_jsx("p", {
199
- children: t("discuss_refresh_discussions_desc")
202
+ children: t("discuss_submissions_desc")
200
203
  })]
201
204
  }),
202
205
  spotlightPadding: 8,
203
- target: "#discussRefreshScheduled"
206
+ target: "#discussGroupedDiscussions"
204
207
  }, {
205
208
  content: /*#__PURE__*/_jsxs("div", {
206
209
  children: [/*#__PURE__*/_jsx("h2", {
207
- children: t("discuss_submissions")
210
+ children: t("discuss_refresh_discussions")
208
211
  }), /*#__PURE__*/_jsx("br", {}), /*#__PURE__*/_jsx("p", {
209
- children: t("discuss_submissions_desc")
212
+ children: t("discuss_refresh_discussions_desc")
210
213
  })]
211
214
  }),
212
215
  spotlightPadding: 8,
213
- target: "#discussSubmissions"
216
+ target: "#discussRefreshScheduled"
214
217
  }];
215
218
  const discussTabIndex = tabs.findIndex(tab => tab.id === "Discuss");
216
219
  const [joyrideState, setJoyrideState] = useState({
@@ -229,19 +232,28 @@ function ResponsiveTabs({
229
232
  creatorRun,
230
233
  creatorSteps
231
234
  } = creatorJoyrideState;
235
+
236
+ // The video chat flags resolve async, and the creator's Discuss tab is rendered on
237
+ // `isCreator` alone - it does not wait for `isVideoChatEnabledInSettings`. So a
238
+ // creator can already be sitting on Discuss when the setting flips on, with neither
239
+ // `value` nor `discussTabIndex` changing. Keep the flags in the deps so the tour still
240
+ // starts, and guard on `run`/`creatorRun` so a flag change mid-tour does not cancel
241
+ // the tour's poll and restart it from the first step.
232
242
  useEffect(() => {
233
- if (discussTabIndex === value && !isCreator && !localStorage.getItem(`student-discuss-tour-completed`) && isVideoChatEnabled && isVideoChatEnabledInSettings) {
234
- setJoyrideState(prevState => ({
243
+ if (discussTabIndex === value && !isCreator && !run && !localStorage.getItem(`student-discuss-tour-completed`) && isVideoChatEnabled && isVideoChatEnabledInSettings) {
244
+ return startJoyrideWhenReady(joyrideSteps, steps => setJoyrideState(prevState => ({
235
245
  ...prevState,
236
- run: true
237
- }));
238
- } else if (discussTabIndex === value && isCreator && !localStorage.getItem(`creator-discuss-tour-completed`) && isVideoChatEnabled && isVideoChatEnabledInSettings) {
239
- setCreatorJoyrideState(prevState => ({
246
+ run: true,
247
+ steps
248
+ })));
249
+ } else if (discussTabIndex === value && isCreator && !creatorRun && !localStorage.getItem(`creator-discuss-tour-completed`) && isVideoChatEnabled && isVideoChatEnabledInSettings) {
250
+ return startJoyrideWhenReady(creatorJoyrideSteps, creatorSteps => setCreatorJoyrideState(prevState => ({
240
251
  ...prevState,
241
- creatorRun: true
242
- }));
252
+ creatorRun: true,
253
+ creatorSteps
254
+ })));
243
255
  }
244
- }, [value, discussTabIndex]);
256
+ }, [value, discussTabIndex, isCreator, isVideoChatEnabled, isVideoChatEnabledInSettings, run, creatorRun]);
245
257
  const handleJoyrideCallback = data => {
246
258
  const {
247
259
  status,
@@ -336,6 +348,7 @@ function ResponsiveTabs({
336
348
  close: t("close"),
337
349
  last: t("last"),
338
350
  next: t("next"),
351
+ nextWithProgress: t("next_with_progress"),
339
352
  skip: t("skip")
340
353
  },
341
354
  options: {
@@ -358,6 +371,7 @@ function ResponsiveTabs({
358
371
  close: t("close"),
359
372
  last: t("last"),
360
373
  next: t("next"),
374
+ nextWithProgress: t("next_with_progress"),
361
375
  skip: t("skip")
362
376
  },
363
377
  options: {
@@ -4,7 +4,7 @@ export default function useConfirm(t) {
4
4
  const confirmed = await ConfirmDialog({
5
5
  title: title || t("confirmation"),
6
6
  text: text || t("are_you_sure"),
7
- submitText: submitText || t("Yes"),
7
+ submitText: submitText || t("yes"),
8
8
  isDelete: isDelete,
9
9
  isArchive: isArchive,
10
10
  isUnarchive: isUnarchive,
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Helpers for keeping react-joyride tours in sync with what is actually on screen.
3
+ *
4
+ * When a step's target is not in the DOM, react-joyride polls for it for
5
+ * `targetWaitTimeout` (1s by default) while showing its loading spinner, then emits
6
+ * TARGET_NOT_FOUND and jumps to the next step. To the user that reads as a step that
7
+ * "loads" and then gets skipped, and the "x of y" progress count includes steps they
8
+ * never see.
9
+ *
10
+ * Most of our tours target elements that are conditionally rendered - the speak button
11
+ * is hidden for Irish, the word bank toggle depends on course settings, the skip button
12
+ * is hidden in live games - so resolving the steps against the live DOM before the tour
13
+ * starts is what keeps the tour honest.
14
+ */
15
+
16
+ const DEFAULT_TIMEOUT = 1000;
17
+ const DEFAULT_INTERVAL = 50;
18
+
19
+ /**
20
+ * Whether an element and all of its ancestors are visible.
21
+ *
22
+ * Mirrors react-joyride's own check. Being in the DOM is not enough for it: a target
23
+ * inside a `keepMounted` drawer or a closed MUI modal is present but hidden, and
24
+ * joyride treats that exactly like a missing target - spinner, then skip.
25
+ * @param element The element to check
26
+ * @returns {boolean}
27
+ */
28
+ const isElementVisible = element => {
29
+ if (!element) return false;
30
+ let node = element;
31
+ while (node && node !== document.body) {
32
+ if (node instanceof HTMLElement) {
33
+ const {
34
+ display,
35
+ visibility
36
+ } = getComputedStyle(node);
37
+ if (display === "none" || visibility === "hidden") return false;
38
+ }
39
+ node = node.parentElement;
40
+ }
41
+ return true;
42
+ };
43
+
44
+ /**
45
+ * Returns true when a joyride step's target is on screen.
46
+ *
47
+ * A step with no target is never resolvable, and it is not merely useless: joyride
48
+ * validates the array with `steps.every(...)`, so one target-less step makes the whole
49
+ * array invalid, `setSteps` is skipped and the tour silently runs the stale list it
50
+ * already had. Those steps have to be dropped, not passed through.
51
+ *
52
+ * `body` is always present. Function targets are left for joyride to resolve, since
53
+ * calling one here would repeat whatever work it does.
54
+ * @param step A joyride step
55
+ * @returns {boolean}
56
+ */
57
+ export const isJoyrideTargetPresent = step => {
58
+ const target = step && step.target;
59
+ if (!target) {
60
+ return false;
61
+ }
62
+ if (typeof target === "function") {
63
+ return true;
64
+ }
65
+ if (typeof target === "object") {
66
+ return "current" in target && isElementVisible(target.current);
67
+ }
68
+ if (typeof target !== "string") {
69
+ return false;
70
+ }
71
+ if (target === "body") {
72
+ return true;
73
+ }
74
+ try {
75
+ return isElementVisible(document.querySelector(target));
76
+ } catch (error) {
77
+ console.error(`Invalid joyride target selector: ${target}`, error);
78
+ return false;
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Drops the steps whose target is not currently rendered. Only useful once the tour's
84
+ * targets have had a chance to mount - prefer `startJoyrideWhenReady` for that.
85
+ * @param steps The full list of joyride steps
86
+ * @returns {Array} The steps that have a target on screen
87
+ */
88
+ export const resolveJoyrideSteps = (steps = []) => steps.filter(isJoyrideTargetPresent);
89
+
90
+ /**
91
+ * Resolves a tour's steps once its targets have mounted, then hands them to `onReady`.
92
+ *
93
+ * Exercises put their tour inside a MUI `Dialog`, and a portal only renders its
94
+ * children on the render after the component that opens it commits. Filtering straight
95
+ * away in an effect would therefore drop every step, so poll until either every target
96
+ * has appeared or `timeout` is reached - whatever is missing by then is a target this
97
+ * screen does not render at all.
98
+ * @param steps The full list of joyride steps
99
+ * @param onReady Called once with the steps that have a target on screen
100
+ * @param options.timeout How long to wait for missing targets, in ms
101
+ * @param options.interval How often to re-check, in ms
102
+ * @returns {Function} Cancels the pending check - use it as an effect cleanup
103
+ */
104
+ export const startJoyrideWhenReady = (steps = [], onReady, options = {}) => {
105
+ const {
106
+ timeout = DEFAULT_TIMEOUT,
107
+ interval = DEFAULT_INTERVAL
108
+ } = options;
109
+ let timeoutId = null;
110
+ let cancelled = false;
111
+ let waited = 0;
112
+ const check = () => {
113
+ if (cancelled) return;
114
+ const resolved = resolveJoyrideSteps(steps);
115
+ if (resolved.length === steps.length || waited >= timeout) {
116
+ onReady(resolved);
117
+ return;
118
+ }
119
+ waited += interval;
120
+ timeoutId = setTimeout(check, interval);
121
+ };
122
+ check();
123
+ return () => {
124
+ cancelled = true;
125
+ if (timeoutId) clearTimeout(timeoutId);
126
+ };
127
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1410",
3
+ "version": "0.1.1411",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [