@nualang/nualang-ui-components 0.1.1340 → 0.1.1350

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.
@@ -15,8 +15,10 @@ import CircularProgress from "@mui/material/CircularProgress";
15
15
  import HelpIcon from "@mui/icons-material/Help";
16
16
  import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
17
17
  import { removeAllSymbols, localeCompare } from "../../utils/index";
18
+ import LinearProgress from "@mui/material/LinearProgress";
18
19
  // components
19
20
  import ResponsiveDialog from "../ResponsiveDialog/ResponsiveDialog";
21
+ import NualaCreating from "../../Misc/NualaCreating/NualaCreating";
20
22
  import { capitalize } from "../../utils/index";
21
23
  import InputHelper from "../../Forms/InputHelper/InputHelper";
22
24
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -42,13 +44,10 @@ function CreatePhrase({
42
44
  open,
43
45
  handleClose,
44
46
  handleTranslate,
45
- submitText,
46
- dialogTitle,
47
47
  learnLang = "",
48
48
  forLang = "",
49
49
  onSubmit,
50
50
  update = false,
51
- handleChange,
52
51
  image = "",
53
52
  forLangCharacters,
54
53
  learnLangCharacters,
@@ -76,11 +75,52 @@ function CreatePhrase({
76
75
  const [englishTranslation, setEnglishTranslation] = useState("");
77
76
  const [autoSuggestedImages, setAutoSuggestedImages] = useState([]);
78
77
  const [translationList, setTranslationList] = useState(otherProps.translations || []);
78
+ const [definition, setDefinition] = useState("");
79
+ const [definitionList, setDefinitionList] = useState(otherProps.definitions || []);
79
80
  const [autoTranslatedText, setAutoTranslatedText] = useState(false);
80
81
  const [checkingRecogniton, setCheckingRecogniton] = useState(false);
81
82
  const [recognised, setRecognised] = useState(false);
82
83
  const [notRecognised, setNotRecognised] = useState(false);
83
84
  const [recognisedPhrase, setRecognisedPhrase] = useState("");
85
+
86
+ // Keep definitionList in sync when editing an existing phrase in update mode
87
+ // (e.g. switching between different phrases while dialog stays mounted)
88
+ useEffect(() => {
89
+ setDefinitionList(otherProps.definitions || []);
90
+ }, [otherProps.definitions, otherProps.definition, open]);
91
+ const handleEditDefinition = (event, index) => {
92
+ const {
93
+ target: {
94
+ value
95
+ }
96
+ } = event;
97
+ const tempArray = definitionList.slice();
98
+ tempArray[index] = value;
99
+ setDefinitionList([...tempArray]);
100
+ };
101
+ useEffect(() => {
102
+ if (open) {
103
+ setPhrase(otherProps.phrase || "");
104
+ setAlternativeVersions(otherProps.alternativeVersions || []);
105
+ setTranslationList(otherProps.translations || []);
106
+ setDefinitionList(otherProps.definitions || []);
107
+ setSelectedImage({
108
+ image: image || "",
109
+ imageSelectionMethod: "default_image"
110
+ });
111
+ }
112
+ }, [open, otherProps.phrase, otherProps.alternativeVersions, otherProps.translations, otherProps.definitions, image]);
113
+ const handleAddDefinition = () => {
114
+ setDefinitionList(prev => {
115
+ const trimmed = definition ? definition.trim() : "";
116
+ if (trimmed && !prev.includes(trimmed)) return [...prev, trimmed];
117
+ return prev;
118
+ });
119
+ setDefinition("");
120
+ };
121
+ const handleRemoveDefinition = index => {
122
+ setDefinitionList(prev => prev.filter((_, i) => i !== index));
123
+ };
84
124
  const checkIsPronunciationCorrect = (finalTranscript, alternativeTranscripts, alternativeVersions) => {
85
125
  let stringMatch;
86
126
  if (alternativeTranscripts && alternativeTranscripts.length > 0) {
@@ -179,6 +219,13 @@ function CreatePhrase({
179
219
  });
180
220
  }
181
221
  }, [image]);
222
+
223
+ // reset form state whenever the dialog is closed
224
+ useEffect(() => {
225
+ if (!open) {
226
+ resetPhraseState();
227
+ }
228
+ }, [open]);
182
229
  const handleEditAlternative = (event, index) => {
183
230
  const {
184
231
  target: {
@@ -199,21 +246,6 @@ function CreatePhrase({
199
246
  const handleRemoveAlternative = index => {
200
247
  setAlternativeVersions(prevAlternativeVersions => prevAlternativeVersions.filter((t, i) => i !== index));
201
248
  };
202
-
203
- // // translation
204
- // const generateTranslations = async () => {
205
- // const translatedText = await handleTranslate(phrase, learnLang, forLang);
206
- // if (translatedText && translatedText !== "") {
207
- // setAutoTranslatedText(translatedText);
208
- // setTranslationList((prevTranslationList) => {
209
- // if (!prevTranslationList.includes(translatedText)) {
210
- // return [...prevTranslationList, translatedText];
211
- // }
212
- // return prevTranslationList;
213
- // });
214
- // }
215
- // };
216
-
217
249
  function validateResponse(response) {
218
250
  if (!Array.isArray(response)) {
219
251
  return false;
@@ -230,6 +262,7 @@ function CreatePhrase({
230
262
  }
231
263
  const generateTranslations = async () => {
232
264
  try {
265
+ setIsSubmitting(true);
233
266
  let chatGptResponse = await makeChatGptApiRequest({
234
267
  model: "gpt-4o",
235
268
  promptKey: "generateTranslations",
@@ -257,8 +290,7 @@ function CreatePhrase({
257
290
  const handleEditTranslation = (event, index) => {
258
291
  const {
259
292
  target: {
260
- value,
261
- dataset
293
+ value
262
294
  }
263
295
  } = event;
264
296
  const tempArray = translationList.slice();
@@ -267,15 +299,16 @@ function CreatePhrase({
267
299
  };
268
300
  const handleAddTranslation = () => {
269
301
  setTranslationList(prevTranslationList => {
270
- if (translation && !prevTranslationList.includes(translation.trim())) {
271
- return [...prevTranslationList, translation.trim()];
302
+ const trimmed = translation ? translation.trim() : "";
303
+ if (trimmed && !prevTranslationList.includes(trimmed)) {
304
+ return [...prevTranslationList, trimmed];
272
305
  }
273
306
  return prevTranslationList;
274
307
  });
275
308
  setTranslation("");
276
309
  };
277
310
  const handleRemoveTranslation = index => {
278
- setTranslationList(prevTranslationList => prevTranslationList.filter((t, i) => i !== index));
311
+ setTranslationList(prevTranslationList => prevTranslationList.filter((_, i) => i !== index));
279
312
  };
280
313
  const resetPhraseState = () => {
281
314
  setPhrase("");
@@ -283,6 +316,8 @@ function CreatePhrase({
283
316
  setAlternativeVersions([]);
284
317
  setTranslation("");
285
318
  setTranslationList([]);
319
+ setDefinition("");
320
+ setDefinitionList([]);
286
321
  setSelectedImage(initialSelectedImagestate);
287
322
  setAutoTranslatedText("");
288
323
  };
@@ -296,16 +331,26 @@ function CreatePhrase({
296
331
  const finalTranslation = translation ? translation.trim() : "";
297
332
  let finalTranslationList = translationList;
298
333
  let finalAlternativeVersionsList = alternativeVersions.filter(str => str.replace(/\s/g, "").length);
334
+
299
335
  // If a user has entered the translation but hasn't clicked the button, add it to the list
300
336
  if (finalTranslation !== "" && !finalTranslationList.includes(finalTranslation)) {
301
337
  finalTranslationList = [...finalTranslationList, finalTranslation];
302
338
  }
339
+
303
340
  // Same idea for the alternative versions
304
341
  if (alternativeVersion !== "" && !finalAlternativeVersionsList.includes(alternativeVersion)) {
305
342
  finalAlternativeVersionsList = [...finalAlternativeVersionsList, alternativeVersion];
306
343
  }
344
+
345
+ // Definitions: add pending input if not submitted via Enter
346
+ let finalDefinitionList = definitionList;
347
+ const finalDefinitionInput = definition ? definition.trim() : "";
348
+ if (finalDefinitionInput !== "" && !finalDefinitionList.includes(finalDefinitionInput)) {
349
+ finalDefinitionList = [...finalDefinitionList, finalDefinitionInput];
350
+ }
307
351
  const response = await onSubmit({
308
352
  phrase: phrase.trim(),
353
+ definitions: finalDefinitionList,
309
354
  translations: finalTranslationList,
310
355
  alternativeVersions: finalAlternativeVersionsList,
311
356
  image: selectedImage.image,
@@ -376,123 +421,148 @@ function CreatePhrase({
376
421
  submitText: title,
377
422
  dialogTitle: title,
378
423
  isSubmitDisabled: isSubmitting,
379
- children: [/*#__PURE__*/_jsx(DialogContentText, {
380
- children: desc
381
- }), /*#__PURE__*/_jsx(InputHelper, {
382
- t: t,
383
- value: phrase,
384
- onChange: e => {
385
- setPhrase(e.target.value);
386
- if (notRecognised || recognised) {
387
- setNotRecognised(false);
388
- setRecognised(false);
389
- }
390
- },
391
- onKeyDown: e => {
392
- if (e.key === "Enter" && translationInputRef && translationInputRef.current) {
393
- translationInputRef.current.focus();
394
- }
424
+ children: [/*#__PURE__*/_jsxs(Grid, {
425
+ container: true,
426
+ sx: {
427
+ width: "100%"
395
428
  },
396
- margin: "normal",
397
- autoFocus: true,
398
- id: "phrase",
399
- name: "phrase",
400
- label: t("language_phrase", {
401
- language: capitalize(t(learnLang.toLowerCase()))
402
- }),
403
- type: "text",
404
- variant: "outlined",
405
- fullWidth: true,
406
- characters: learnLangCharacters ? learnLangCharacters : null
407
- }), phrase && phrase.trim() !== "" && /*#__PURE__*/_jsxs(_Fragment, {
408
- children: [/*#__PURE__*/_jsx(Tooltip, {
409
- title: t(`add_alternative_version_explanation`),
410
- classes: {
411
- tooltip: classes.tooltip
429
+ children: [/*#__PURE__*/_jsx(Grid, {
430
+ textAlign: "center",
431
+ sx: {
432
+ ...(!isSubmitting && {
433
+ display: "none"
434
+ })
412
435
  },
413
- children: /*#__PURE__*/_jsx(DefaultButton, {
414
- onClick: () => handleAddAlternative(phrase),
415
- disabled: !phrase,
416
- size: "sm",
417
- className: classes.alternativeVersionButton,
418
- children: t("add_alternative_version")
419
- })
420
- }), learnLang !== forLang && /*#__PURE__*/_jsx(DefaultButton, {
421
- onClick: generateTranslations,
422
- disabled: !phrase || Array.isArray(translationList) && translationList.includes(autoTranslatedText),
423
- size: "sm",
424
- className: classes.alternativeVersionButton,
425
- startIcon: /*#__PURE__*/_jsx(AutoFixHighIcon, {}),
426
- children: t("auto_generate_phrase_translation")
427
- }), learnLang !== forLang && (import.meta.env.REACT_APP_STAGE === "dev" || window.location.host === "localhost:9009") && /*#__PURE__*/_jsx(Box, {
436
+ mx: 2,
437
+ size: 12,
438
+ children: /*#__PURE__*/_jsx(NualaCreating, {})
439
+ }), /*#__PURE__*/_jsx(Grid, {
428
440
  sx: {
429
- display: "inline-flex",
430
- alignItems: "center"
441
+ ...(!isSubmitting && {
442
+ display: "none"
443
+ })
444
+ },
445
+ mt: 2,
446
+ size: 12,
447
+ children: /*#__PURE__*/_jsx(LinearProgress, {})
448
+ })]
449
+ }), !isSubmitting && /*#__PURE__*/_jsxs(_Fragment, {
450
+ children: [/*#__PURE__*/_jsx(DialogContentText, {
451
+ children: desc
452
+ }), /*#__PURE__*/_jsx(InputHelper, {
453
+ t: t,
454
+ value: phrase,
455
+ onChange: e => {
456
+ setPhrase(e.target.value);
457
+ if (notRecognised || recognised) {
458
+ setNotRecognised(false);
459
+ setRecognised(false);
460
+ }
461
+ },
462
+ onKeyDown: e => {
463
+ if (e.key === "Enter" && translationInputRef?.current) {
464
+ translationInputRef.current.focus();
465
+ }
431
466
  },
432
- children: /*#__PURE__*/_jsx(Box, {
467
+ margin: "normal",
468
+ autoFocus: true,
469
+ id: "phrase",
470
+ name: "phrase",
471
+ label: t("language_phrase", {
472
+ language: capitalize(t(learnLang.toLowerCase()))
473
+ }),
474
+ type: "text",
475
+ variant: "outlined",
476
+ fullWidth: true,
477
+ characters: learnLangCharacters ? learnLangCharacters : null
478
+ }), phrase && phrase.trim() !== "" && /*#__PURE__*/_jsxs(_Fragment, {
479
+ children: [/*#__PURE__*/_jsx(Tooltip, {
480
+ title: t(`add_alternative_version_explanation`),
481
+ classes: {
482
+ tooltip: classes.tooltip
483
+ },
484
+ children: /*#__PURE__*/_jsx(DefaultButton, {
485
+ onClick: () => handleAddAlternative(phrase),
486
+ disabled: !phrase,
487
+ size: "sm",
488
+ className: classes.alternativeVersionButton,
489
+ children: t("add_alternative_version")
490
+ })
491
+ }), learnLang !== forLang && /*#__PURE__*/_jsx(DefaultButton, {
492
+ onClick: generateTranslations,
493
+ disabled: !phrase || Array.isArray(translationList) && translationList.includes(autoTranslatedText),
494
+ size: "sm",
495
+ className: classes.alternativeVersionButton,
496
+ startIcon: /*#__PURE__*/_jsx(AutoFixHighIcon, {}),
497
+ children: t("auto_generate_phrase_translation")
498
+ }), learnLang !== forLang && (import.meta.env.REACT_APP_STAGE === "dev" || window.location.host === "localhost:9009") && /*#__PURE__*/_jsx(Box, {
433
499
  sx: {
434
- position: "relative"
500
+ display: "inline-flex",
501
+ alignItems: "center"
435
502
  },
436
- children: notRecognised ? /*#__PURE__*/_jsx(Tooltip, {
437
- title: recognisedPhrase !== "" ? t("speech_recognition_error_info", {
438
- phrase: `"${phrase}"`,
439
- recognisedPhrase: `"${recognisedPhrase}"`
440
- }) : t("speech_recognition_error_info_no_phrase"),
441
- classes: {
442
- tooltip: classes.tooltip
503
+ children: /*#__PURE__*/_jsx(Box, {
504
+ sx: {
505
+ position: "relative"
443
506
  },
444
- children: /*#__PURE__*/_jsx(Box, {
445
- children: /*#__PURE__*/_jsx(DefaultButton, {
446
- size: "sm",
447
- className: classes.alternativeVersionButton,
448
- startIcon: /*#__PURE__*/_jsx(WarningIcon, {
449
- color: "error"
450
- }),
451
- disabled: true,
452
- children: t("phrase_not_recognised")
453
- })
454
- })
455
- }) : /*#__PURE__*/_jsxs(_Fragment, {
456
- children: [/*#__PURE__*/_jsxs(DefaultButton, {
457
- size: "sm",
458
- className: classes.alternativeVersionButton,
459
- onClick: handleCheckRecognition,
460
- disabled: checkingRecogniton || recognised || notRecognised,
461
- startIcon: endIcon,
462
- children: [recognised ? t("phrase_recognised") : null, checkingRecogniton ? t("checking_recognition") : null, !checkingRecogniton && !recognised && !notRecognised ? t("check_recognition") : null]
463
- }), !checkingRecogniton && !recognised && !notRecognised ? /*#__PURE__*/_jsx(Tooltip, {
464
- title: t("speech_recognition_info"),
507
+ children: notRecognised ? /*#__PURE__*/_jsx(Tooltip, {
508
+ title: recognisedPhrase !== "" ? t("speech_recognition_error_info", {
509
+ phrase: `"${phrase}"`,
510
+ recognisedPhrase: `"${recognisedPhrase}"`
511
+ }) : t("speech_recognition_error_info_no_phrase"),
465
512
  classes: {
466
513
  tooltip: classes.tooltip
467
514
  },
468
- children: /*#__PURE__*/_jsx(IconButton, {
469
- sx: {
470
- padding: 0
471
- },
472
- children: /*#__PURE__*/_jsx(HelpIcon, {
473
- fontSize: "small"
515
+ children: /*#__PURE__*/_jsx(Box, {
516
+ children: /*#__PURE__*/_jsx(DefaultButton, {
517
+ size: "sm",
518
+ className: classes.alternativeVersionButton,
519
+ startIcon: /*#__PURE__*/_jsx(WarningIcon, {
520
+ color: "error"
521
+ }),
522
+ disabled: true,
523
+ children: t("phrase_not_recognised")
474
524
  })
475
525
  })
476
- }) : /*#__PURE__*/_jsx(_Fragment, {}), checkingRecogniton && /*#__PURE__*/_jsx(CircularProgress, {
477
- size: 24,
478
- sx: {
479
- position: "absolute",
480
- top: "50%",
481
- left: "50%",
482
- marginTop: "-12px",
483
- marginLeft: "-12px"
484
- }
485
- })]
526
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
527
+ children: [/*#__PURE__*/_jsxs(DefaultButton, {
528
+ size: "sm",
529
+ className: classes.alternativeVersionButton,
530
+ onClick: handleCheckRecognition,
531
+ disabled: checkingRecogniton || recognised || notRecognised,
532
+ startIcon: endIcon,
533
+ children: [recognised ? t("phrase_recognised") : null, checkingRecogniton ? t("checking_recognition") : null, !checkingRecogniton && !recognised && !notRecognised ? t("check_recognition") : null]
534
+ }), !checkingRecogniton && !recognised && !notRecognised ? /*#__PURE__*/_jsx(Tooltip, {
535
+ title: t("speech_recognition_info"),
536
+ classes: {
537
+ tooltip: classes.tooltip
538
+ },
539
+ children: /*#__PURE__*/_jsx(IconButton, {
540
+ sx: {
541
+ padding: 0
542
+ },
543
+ children: /*#__PURE__*/_jsx(HelpIcon, {
544
+ fontSize: "small"
545
+ })
546
+ })
547
+ }) : null, checkingRecogniton && /*#__PURE__*/_jsx(CircularProgress, {
548
+ size: 24,
549
+ sx: {
550
+ position: "absolute",
551
+ top: "50%",
552
+ left: "50%",
553
+ marginTop: "-12px",
554
+ marginLeft: "-12px"
555
+ }
556
+ })]
557
+ })
486
558
  })
487
- })
488
- })]
489
- }), phrase && /*#__PURE__*/_jsx(Grid, {
490
- container: true,
491
- direction: "row",
492
- justifyContent: "center",
493
- alignItems: "flex-end",
494
- children: alternativeVersions ? alternativeVersions.map((alternativeAns, idx) => {
495
- return /*#__PURE__*/_jsxs(Fragment, {
559
+ })]
560
+ }), phrase && /*#__PURE__*/_jsx(Grid, {
561
+ container: true,
562
+ direction: "row",
563
+ justifyContent: "center",
564
+ alignItems: "flex-end",
565
+ children: alternativeVersions?.map((alternativeAns, idx) => /*#__PURE__*/_jsxs(Fragment, {
496
566
  children: [/*#__PURE__*/_jsx(Grid, {
497
567
  className: classes.alternativeVersionItem,
498
568
  size: 10,
@@ -500,9 +570,7 @@ function CreatePhrase({
500
570
  t: t,
501
571
  name: `alternativeVersion${idx}`,
502
572
  value: alternativeAns,
503
- onChange: e => {
504
- handleEditAlternative(e, idx);
505
- },
573
+ onChange: e => handleEditAlternative(e, idx),
506
574
  margin: "none",
507
575
  autoFocus: true,
508
576
  type: "text",
@@ -513,53 +581,45 @@ function CreatePhrase({
513
581
  className: classes.alternativeVersionItem,
514
582
  size: 1,
515
583
  children: /*#__PURE__*/_jsx(IconButton, {
516
- onClick: () => {
517
- handleRemoveAlternative(idx);
518
- },
584
+ onClick: () => handleRemoveAlternative(idx),
519
585
  size: "large",
520
586
  "aria-label": "Remove Alternative",
521
587
  children: /*#__PURE__*/_jsx(CloseIcon, {})
522
588
  })
523
589
  })]
524
- }, `alternative_answers_${idx}`);
525
- }) : null
526
- }), learnLang !== forLang && /*#__PURE__*/_jsx(InputHelper, {
527
- inputRef: translationInputRef,
528
- t: t,
529
- value: translation,
530
- onChange: e => setTranslation(e.target.value),
531
- onKeyDown: e => {
532
- if (e.key === "Enter") {
533
- handleAddTranslation();
534
- }
535
- },
536
- margin: "normal",
537
- id: "translation",
538
- name: "translation",
539
- label: t("language_translation", {
540
- language: capitalize(t(forLang.toLowerCase()))
541
- }),
542
- helperText: translation && translation.trim() !== "" ? t("language_translation_helper_text") : "",
543
- type: "text",
544
- variant: "outlined",
545
- fullWidth: true,
546
- characters: forLangCharacters ? forLangCharacters : null
547
- }), /*#__PURE__*/_jsxs(Grid, {
548
- container: true,
549
- direction: "row",
550
- justifyContent: "center",
551
- alignItems: "flex-end",
552
- children: [translationList ? translationList.map((translation, idx) => {
553
- return /*#__PURE__*/_jsxs(Fragment, {
590
+ }, `alternative_answers_${idx}`))
591
+ }), learnLang !== forLang && /*#__PURE__*/_jsx(InputHelper, {
592
+ inputRef: translationInputRef,
593
+ t: t,
594
+ value: translation,
595
+ onChange: e => setTranslation(e.target.value),
596
+ onKeyDown: e => {
597
+ if (e.key === "Enter") handleAddTranslation();
598
+ },
599
+ margin: "normal",
600
+ id: "translation",
601
+ name: "translation",
602
+ label: t("language_translation", {
603
+ language: capitalize(t(forLang.toLowerCase()))
604
+ }),
605
+ helperText: translation && translation.trim() !== "" ? t("language_translation_helper_text") : "",
606
+ type: "text",
607
+ variant: "outlined",
608
+ fullWidth: true,
609
+ characters: forLangCharacters ? forLangCharacters : null
610
+ }), /*#__PURE__*/_jsxs(Grid, {
611
+ container: true,
612
+ direction: "row",
613
+ justifyContent: "center",
614
+ alignItems: "flex-end",
615
+ children: [translationList?.map((translationItem, idx) => /*#__PURE__*/_jsxs(Fragment, {
554
616
  children: [/*#__PURE__*/_jsx(Grid, {
555
617
  className: classes.translationItem,
556
618
  size: 10,
557
619
  children: /*#__PURE__*/_jsx(InputHelper, {
558
620
  t: t,
559
- value: translation,
560
- onChange: e => {
561
- handleEditTranslation(e, idx);
562
- },
621
+ value: translationItem,
622
+ onChange: e => handleEditTranslation(e, idx),
563
623
  margin: "none",
564
624
  type: "text",
565
625
  fullWidth: true,
@@ -572,30 +632,75 @@ function CreatePhrase({
572
632
  className: classes.translationItem,
573
633
  size: 1,
574
634
  children: /*#__PURE__*/_jsx(IconButton, {
575
- onClick: () => {
576
- handleRemoveTranslation(idx);
577
- },
635
+ onClick: () => handleRemoveTranslation(idx),
578
636
  size: "large",
579
637
  "aria-label": "Remove Translation",
580
638
  children: /*#__PURE__*/_jsx(CloseIcon, {})
581
639
  })
582
640
  })]
583
- }, `translist_${idx}`);
584
- }) : null, /*#__PURE__*/_jsx(Grid, {
585
- size: 12,
586
- children: /*#__PURE__*/_jsx(ImageSelector, {
587
- t: t,
588
- name: "image",
589
- value: selectedImage.image,
590
- handleChange: handleImageSelection,
591
- fileSizeLimit: fileSizeLimit,
592
- removeImage: removePhraseImage,
593
- autoCrop: true,
594
- autoSuggest: true,
595
- autoSuggestedImages: autoSuggestedImages,
596
- selectedImage: selectedImage,
597
- verificationStatus: verificationStatus
598
- })
641
+ }, `translist_${idx}`)), learnLang !== forLang && /*#__PURE__*/_jsxs(_Fragment, {
642
+ children: [/*#__PURE__*/_jsx(Grid, {
643
+ size: 12,
644
+ children: /*#__PURE__*/_jsx(InputHelper, {
645
+ t: t,
646
+ value: definition,
647
+ onChange: e => setDefinition(e.target.value),
648
+ onKeyDown: e => {
649
+ if (e.key === "Enter") handleAddDefinition();
650
+ },
651
+ margin: "normal",
652
+ id: "definition",
653
+ name: "definition",
654
+ label: t("phrase_definition"),
655
+ helperText: definition && definition.trim() !== "" ? t("phrase_definition_helper_text") : "",
656
+ type: "text",
657
+ variant: "outlined",
658
+ fullWidth: true,
659
+ characters: forLangCharacters ? forLangCharacters : null
660
+ })
661
+ }), definitionList?.map((def, idx) => /*#__PURE__*/_jsxs(Fragment, {
662
+ children: [/*#__PURE__*/_jsx(Grid, {
663
+ className: classes.translationItem,
664
+ size: 10,
665
+ children: /*#__PURE__*/_jsx(InputHelper, {
666
+ t: t,
667
+ value: def,
668
+ onChange: e => handleEditDefinition(e, idx),
669
+ margin: "none",
670
+ type: "text",
671
+ fullWidth: true,
672
+ characters: forLangCharacters ? forLangCharacters : null,
673
+ inputProps: {
674
+ "data-definition-index": idx
675
+ }
676
+ })
677
+ }), /*#__PURE__*/_jsx(Grid, {
678
+ className: classes.translationItem,
679
+ size: 1,
680
+ children: /*#__PURE__*/_jsx(IconButton, {
681
+ onClick: () => handleRemoveDefinition(idx),
682
+ size: "large",
683
+ "aria-label": "Remove Definition",
684
+ children: /*#__PURE__*/_jsx(CloseIcon, {})
685
+ })
686
+ })]
687
+ }, `deflist_${idx}`))]
688
+ }), /*#__PURE__*/_jsx(Grid, {
689
+ size: 12,
690
+ children: /*#__PURE__*/_jsx(ImageSelector, {
691
+ t: t,
692
+ name: "image",
693
+ value: selectedImage.image,
694
+ handleChange: handleImageSelection,
695
+ fileSizeLimit: fileSizeLimit,
696
+ removeImage: removePhraseImage,
697
+ autoCrop: true,
698
+ autoSuggest: true,
699
+ autoSuggestedImages: autoSuggestedImages,
700
+ selectedImage: selectedImage,
701
+ verificationStatus: verificationStatus
702
+ })
703
+ })]
599
704
  })]
600
705
  })]
601
706
  });
@@ -12,11 +12,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
12
12
  const useStyles = makeStyles()(theme => ({
13
13
  phraseText: {
14
14
  width: "80%",
15
- wordBreak: "break-all"
15
+ wordBreak: "break-word",
16
+ overflowWrap: "break-word"
16
17
  },
17
18
  phraseTextSmall: {
18
19
  width: "60%",
19
- wordBreak: "break-all"
20
+ wordBreak: "break-word",
21
+ overflowWrap: "break-word"
20
22
  },
21
23
  avatar: {
22
24
  cursor: "pointer",
@@ -29,6 +31,7 @@ const useStyles = makeStyles()(theme => ({
29
31
  function Phrase({
30
32
  alternativeVersions,
31
33
  translations,
34
+ definitions,
32
35
  phrase
33
36
  }) {
34
37
  const theme = useTheme();
@@ -36,6 +39,27 @@ function Phrase({
36
39
  classes
37
40
  } = useStyles();
38
41
  const isLargeScreen = useMediaQuery(theme.breakpoints.up("sm"));
42
+ const primary = `${phrase}${Array.isArray(alternativeVersions) && alternativeVersions.length ? " - " : ""}${(alternativeVersions || []).join(", ")}`;
43
+
44
+ // Show translations first, then definitions (if any) on a new line.
45
+ const secondaryParts = [];
46
+ if (Array.isArray(translations) && translations.length) {
47
+ secondaryParts.push((translations || []).join(", "));
48
+ }
49
+ if (Array.isArray(definitions) && definitions.length) {
50
+ secondaryParts.push((definitions || []).join(", "));
51
+ }
52
+ const secondary = secondaryParts.join("\n");
53
+ console.log({
54
+ primary,
55
+ secondary
56
+ });
57
+ console.log("Phrase render", {
58
+ phrase,
59
+ translations,
60
+ definitions,
61
+ alternativeVersions
62
+ });
39
63
  return /*#__PURE__*/_jsx(ListItem, {
40
64
  "data-cy": "phrase-list-item",
41
65
  children: isLargeScreen ? /*#__PURE__*/_jsx(Grid, {
@@ -46,8 +70,13 @@ function Phrase({
46
70
  className: classes.phraseText,
47
71
  children: /*#__PURE__*/_jsx(ListItemText, {
48
72
  className: classes.phrases,
49
- primary: `${phrase}${Array.isArray(alternativeVersions) && alternativeVersions.length ? " - " : ""}${(alternativeVersions || []).join(", ")}`,
50
- secondary: (translations || []).join(", ")
73
+ primary: primary,
74
+ secondary: secondary,
75
+ secondaryTypographyProps: {
76
+ style: {
77
+ whiteSpace: "pre-line"
78
+ }
79
+ }
51
80
  })
52
81
  }) : /*#__PURE__*/_jsx(Grid, {
53
82
  container: true,
@@ -57,8 +86,13 @@ function Phrase({
57
86
  className: classes.phraseTextSmall,
58
87
  children: /*#__PURE__*/_jsx(ListItemText, {
59
88
  className: classes.phrases,
60
- primary: `${phrase}${Array.isArray(alternativeVersions) && alternativeVersions.length ? " - " : ""}${(alternativeVersions || []).join(", ")}`,
61
- secondary: (translations || []).join(", ")
89
+ primary: primary,
90
+ secondary: secondary,
91
+ secondaryTypographyProps: {
92
+ style: {
93
+ whiteSpace: "pre-line"
94
+ }
95
+ }
62
96
  })
63
97
  })
64
98
  });
@@ -85,11 +119,13 @@ function GeneratePhrases({
85
119
  const phrases = isPhrases ? currentPhrases.map(({
86
120
  phrase,
87
121
  translations,
122
+ definitions,
88
123
  image,
89
124
  voices
90
125
  }) => ({
91
126
  phrase,
92
127
  translations,
128
+ definitions,
93
129
  image,
94
130
  voices
95
131
  })) : null;
@@ -114,6 +150,7 @@ function GeneratePhrases({
114
150
  if (typeof item !== "object" || item === null || Array.isArray(item)) {
115
151
  return false;
116
152
  }
153
+ // definitions is OPTIONAL and we do NOT validate it per your request
117
154
  if (!Object.hasOwn(item, "phrase") || !Object.hasOwn(item, "translations") || !Object.hasOwn(item, "image")) {
118
155
  return false;
119
156
  }
@@ -158,7 +195,13 @@ function GeneratePhrases({
158
195
  if (!validateResponse(phrasesArray)) {
159
196
  throw new Error("Invalid AI response");
160
197
  }
161
- setGeneratedPhrases(phrasesArray);
198
+
199
+ // Ensure definitions exists as an array (assumed array; no validation)
200
+ const normalized = phrasesArray.map(p => ({
201
+ ...p,
202
+ definitions: Array.isArray(p.definitions) ? p.definitions : []
203
+ }));
204
+ setGeneratedPhrases(normalized);
162
205
  setIsGenerating(false);
163
206
  } catch (error) {
164
207
  console.error(error);
@@ -238,7 +281,6 @@ function GeneratePhrases({
238
281
  onChange: handleChange,
239
282
  multiline: true,
240
283
  rows: 3
241
- // maxLength={300}
242
284
  })
243
285
  }), /*#__PURE__*/_jsx(Grid, {
244
286
  mb: 2,
@@ -254,9 +296,7 @@ function GeneratePhrases({
254
296
  margin: "normal",
255
297
  variant: "outlined",
256
298
  fullWidth: true,
257
- onChange: handleChange
258
- // inputProps={{ maxLength: 200 }}
259
- ,
299
+ onChange: handleChange,
260
300
  InputProps: {
261
301
  endAdornment: /*#__PURE__*/_jsx(InputAdornment, {
262
302
  position: "end",
@@ -61,7 +61,8 @@ const useStyles = makeStyles()(theme => ({
61
61
  }));
62
62
  const rowTemplate = {
63
63
  phrase: "",
64
- translations: "",
64
+ translations: [],
65
+ definitions: [],
65
66
  image: "",
66
67
  voices: [],
67
68
  alternativeVersions: []
@@ -9,8 +9,9 @@ import InputAdornment from "@mui/material/InputAdornment";
9
9
  import FormControl from "@mui/material/FormControl";
10
10
  import Translate from "@mui/icons-material/Translate";
11
11
  import Tooltip from "@mui/material/Tooltip";
12
- import Menu from "@mui/material/Menu";
13
- import MenuItem from "@mui/material/MenuItem";
12
+ import Popover from "@mui/material/Popover";
13
+ import Box from "@mui/material/Box";
14
+ import ButtonBase from "@mui/material/ButtonBase";
14
15
  import FormHelperText from "@mui/material/FormHelperText";
15
16
  import { Smile } from "react-feather";
16
17
  import Picker from "emoji-picker-react";
@@ -29,7 +30,6 @@ export function Adornment(props) {
29
30
  adornmentPosition = "end",
30
31
  iconButtonEdge = "end",
31
32
  icon = /*#__PURE__*/_jsx(Translate, {}),
32
- width = 90,
33
33
  title = "accents_and_special_characters",
34
34
  activeRef,
35
35
  whiteIcon = false,
@@ -77,6 +77,24 @@ export function Adornment(props) {
77
77
  }
78
78
  }
79
79
  };
80
+ const cellSx = {
81
+ width: 40,
82
+ height: 40,
83
+ display: "flex",
84
+ alignItems: "center",
85
+ justifyContent: "center",
86
+ fontSize: "1.15rem",
87
+ borderRadius: 1,
88
+ cursor: "pointer",
89
+ transition: "background-color 0.1s",
90
+ "&:hover": {
91
+ backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.08)"
92
+ },
93
+ "&:focus-visible": {
94
+ outline: `2px solid ${theme.palette.primary.main}`,
95
+ outlineOffset: -2
96
+ }
97
+ };
80
98
  return /*#__PURE__*/_jsx(_Fragment, {
81
99
  children: /*#__PURE__*/_jsxs(InputAdornment, {
82
100
  position: adornmentPosition,
@@ -113,7 +131,7 @@ export function Adornment(props) {
113
131
  children: /*#__PURE__*/_jsx(Smile, {})
114
132
  })
115
133
  })]
116
- }), characters && /*#__PURE__*/_jsxs(_Fragment, {
134
+ }), characters && characters.length > 0 && /*#__PURE__*/_jsxs(_Fragment, {
117
135
  children: [/*#__PURE__*/_jsx(Tooltip, {
118
136
  title: t(title),
119
137
  children: /*#__PURE__*/_jsx(IconButton, {
@@ -140,22 +158,35 @@ export function Adornment(props) {
140
158
  },
141
159
  children: icon
142
160
  })
143
- }), /*#__PURE__*/_jsx(Menu, {
144
- id: `char-menu-${name}`,
161
+ }), /*#__PURE__*/_jsx(Popover, {
162
+ id: `char-grid-${name}`,
145
163
  anchorEl: anchorEl,
146
- keepMounted: true,
147
164
  open: Boolean(anchorEl),
148
165
  onClose: handleCloseAccents,
149
- PaperProps: {
150
- style: {
151
- maxHeight: 48 * 4.5,
152
- width: width
153
- }
166
+ anchorOrigin: {
167
+ vertical: "bottom",
168
+ horizontal: "center"
169
+ },
170
+ transformOrigin: {
171
+ vertical: "top",
172
+ horizontal: "center"
154
173
  },
155
- children: characters.map(c => /*#__PURE__*/_jsx(MenuItem, {
156
- onClick: () => handleClickOption(c),
157
- children: c
158
- }, c))
174
+ children: /*#__PURE__*/_jsx(Box, {
175
+ sx: {
176
+ display: "grid",
177
+ gridTemplateColumns: "repeat(7, 40px)",
178
+ gap: 0.25,
179
+ p: 1
180
+ },
181
+ role: "grid",
182
+ "aria-label": t(title),
183
+ children: characters.map(c => /*#__PURE__*/_jsx(ButtonBase, {
184
+ sx: cellSx,
185
+ onClick: () => handleClickOption(c),
186
+ "aria-label": c,
187
+ children: c
188
+ }, c))
189
+ })
159
190
  })]
160
191
  })]
161
192
  })
@@ -172,8 +203,7 @@ function InputHelper(props) {
172
203
  margin,
173
204
  inputRef,
174
205
  maxLength,
175
- autoFocus,
176
- learnLang
206
+ autoFocus
177
207
  } = props;
178
208
  const textInput = useRef(null);
179
209
  const activeRef = inputRef || textInput;
@@ -188,10 +218,9 @@ function InputHelper(props) {
188
218
  }
189
219
  }
190
220
  };
191
- const adornment = characters ? /*#__PURE__*/_jsx(Adornment, {
221
+ const adornment = characters && characters.length > 0 ? /*#__PURE__*/_jsx(Adornment, {
192
222
  ...props,
193
223
  activeRef: activeRef,
194
- learnLang: learnLang,
195
224
  id: `${id}-adornment`,
196
225
  role: "button",
197
226
  tabIndex: 0,
@@ -7,9 +7,10 @@ import MoreVertIcon from "@mui/icons-material/MoreVert";
7
7
  import EditIcon from "@mui/icons-material/Edit";
8
8
  import DeleteIcon from "@mui/icons-material/Delete";
9
9
  import ImageSearchIcon from "@mui/icons-material/ImageSearch";
10
+ import GTranslateIcon from "@mui/icons-material/GTranslate";
11
+ import MenuBookIcon from '@mui/icons-material/MenuBook';
10
12
  import ListenerDialog from "../../Dialogs/Listener/Listener";
11
13
  import UpdatePhraseDialog from "../../Dialogs/CreatePhrase/CreatePhrase";
12
- import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
13
14
  import ViewImageDialog from "../../Dialogs/ViewImageDialog/ViewImageDialog";
14
15
  import { propsAreEqual } from "../../utils";
15
16
  import { useTheme } from "@mui/material/styles";
@@ -22,6 +23,21 @@ const useStyles = makeStyles()(theme => ({
22
23
  root: {
23
24
  width: "100%"
24
25
  },
26
+ translationRow: {
27
+ display: "flex",
28
+ alignItems: "flex-start",
29
+ gap: "6px",
30
+ lineHeight: 1.2
31
+ },
32
+ iconBeforeText: {
33
+ display: "inline-flex",
34
+ alignItems: "center",
35
+ justifyContent: "center",
36
+ width: 14,
37
+ height: 14,
38
+ color: theme.palette.text.secondary,
39
+ marginTop: 1
40
+ },
25
41
  phraseText: {
26
42
  width: "80%",
27
43
  wordBreak: "break-all"
@@ -31,15 +47,27 @@ const useStyles = makeStyles()(theme => ({
31
47
  wordBreak: "break-all"
32
48
  },
33
49
  avatar: {
34
- // width: 50,
35
- // height: 50,
36
50
  cursor: "pointer",
37
- margin: theme.spacing()
51
+ margin: theme.spacing(),
52
+ marginRight: 20,
53
+ width: 55,
54
+ height: 55
38
55
  },
39
56
  avatarNoClick: {
40
- // width: 50,
41
- // height: 50,
42
- margin: theme.spacing()
57
+ margin: theme.spacing(),
58
+ marginRight: 20,
59
+ width: 55,
60
+ height: 55
61
+ },
62
+ translationsText: {
63
+ overflow: "hidden",
64
+ whiteSpace: "nowrap",
65
+ textOverflow: "ellipsis"
66
+ },
67
+ definitionsText: {
68
+ overflow: "hidden",
69
+ whiteSpace: "nowrap",
70
+ textOverflow: "ellipsis"
43
71
  }
44
72
  }));
45
73
  function Phrase(props) {
@@ -61,7 +89,6 @@ function Phrase(props) {
61
89
  learnLang,
62
90
  handleSpeak,
63
91
  userAttributes,
64
- disableDragAndDrop = false,
65
92
  handleUpdatePhrase,
66
93
  uploadAudio,
67
94
  voices = {},
@@ -77,10 +104,14 @@ function Phrase(props) {
77
104
  } = props;
78
105
  const {
79
106
  phrase = "",
80
- translations,
107
+ translations = [],
108
+ definitions = [],
81
109
  alternativeVersions,
82
110
  image
83
111
  } = props.phrase || {};
112
+ const hasDefinitions = (definitions || []).length > 0;
113
+ const hasTranslations = (translations || []).length > 0;
114
+ const useCompactSecondary = hasDefinitions && hasTranslations;
84
115
  const handleImageOpen = () => {
85
116
  setImageOpen(true);
86
117
  };
@@ -111,6 +142,11 @@ function Phrase(props) {
111
142
  return;
112
143
  }
113
144
  };
145
+ console.log("Phrase render", {
146
+ phrase,
147
+ translations,
148
+ alternativeVersions
149
+ });
114
150
  const handleCloseMenu = () => {
115
151
  setAnchorEl(null);
116
152
  };
@@ -159,7 +195,51 @@ function Phrase(props) {
159
195
  children: /*#__PURE__*/_jsx(ListItemText, {
160
196
  className: classes.phrases,
161
197
  primary: `${phrase}${Array.isArray(alternativeVersions) && alternativeVersions.length ? " - " : ""}${(alternativeVersions || []).join(", ")}`,
162
- secondary: (translations || []).join(", ")
198
+ secondary: /*#__PURE__*/_jsxs("div", {
199
+ children: [/*#__PURE__*/_jsxs(Box, {
200
+ mt: 0.25,
201
+ className: classes.translationRow,
202
+ children: [/*#__PURE__*/_jsx(Tooltip, {
203
+ title: t("translation") || "Translation",
204
+ children: /*#__PURE__*/_jsx("span", {
205
+ className: classes.iconBeforeText,
206
+ "aria-hidden": true,
207
+ children: /*#__PURE__*/_jsx(GTranslateIcon, {
208
+ fontSize: "small"
209
+ })
210
+ })
211
+ }), /*#__PURE__*/_jsx(Tooltip, {
212
+ title: (translations || []).join(", "),
213
+ children: /*#__PURE__*/_jsx(Typography, {
214
+ variant: "body2",
215
+ color: "textSecondary",
216
+ className: classes.translationsText,
217
+ children: (translations || []).join(", ")
218
+ })
219
+ })]
220
+ }), (props.phrase.definitions || []).length > 0 && /*#__PURE__*/_jsxs(Box, {
221
+ mt: 0.25,
222
+ className: classes.translationRow,
223
+ children: [/*#__PURE__*/_jsx(Tooltip, {
224
+ title: t("definition") || "Definition",
225
+ children: /*#__PURE__*/_jsx("span", {
226
+ className: classes.iconBeforeText,
227
+ "aria-hidden": true,
228
+ children: /*#__PURE__*/_jsx(MenuBookIcon, {
229
+ fontSize: "small"
230
+ })
231
+ })
232
+ }), /*#__PURE__*/_jsx(Tooltip, {
233
+ title: (props.phrase.definitions || []).join("; "),
234
+ children: /*#__PURE__*/_jsx(Typography, {
235
+ variant: "body2",
236
+ color: "textSecondary",
237
+ className: `${useCompactSecondary ? classes.secondaryTextCompact : ""} ${classes.definitionsText}`,
238
+ children: (props.phrase.definitions || []).join("; ")
239
+ })
240
+ })]
241
+ })]
242
+ })
163
243
  })
164
244
  }) : /*#__PURE__*/_jsx(Grid, {
165
245
  container: true,
@@ -170,7 +250,51 @@ function Phrase(props) {
170
250
  children: /*#__PURE__*/_jsx(ListItemText, {
171
251
  className: classes.phrases,
172
252
  primary: `${phrase}${Array.isArray(alternativeVersions) && alternativeVersions.length ? " - " : ""}${(alternativeVersions || []).join(", ")}`,
173
- secondary: (translations || []).join(", ")
253
+ secondary: /*#__PURE__*/_jsxs("div", {
254
+ children: [/*#__PURE__*/_jsxs(Box, {
255
+ mt: 0.25,
256
+ className: classes.translationRow,
257
+ children: [/*#__PURE__*/_jsx(Tooltip, {
258
+ title: t("translation") || "Translation",
259
+ children: /*#__PURE__*/_jsx("span", {
260
+ className: classes.iconBeforeText,
261
+ "aria-hidden": true,
262
+ children: /*#__PURE__*/_jsx(GTranslateIcon, {
263
+ fontSize: "small"
264
+ })
265
+ })
266
+ }), /*#__PURE__*/_jsx(Tooltip, {
267
+ title: (translations || []).join(", "),
268
+ children: /*#__PURE__*/_jsx(Typography, {
269
+ variant: "body2",
270
+ color: "textSecondary",
271
+ className: classes.translationsText,
272
+ children: (translations || []).join(", ")
273
+ })
274
+ })]
275
+ }), (props.phrase.definitions || []).length > 0 && /*#__PURE__*/_jsxs(Box, {
276
+ mt: 0.25,
277
+ className: classes.translationRow,
278
+ children: [/*#__PURE__*/_jsx(Tooltip, {
279
+ title: t("definition") || "Definition",
280
+ children: /*#__PURE__*/_jsx("span", {
281
+ className: classes.iconBeforeText,
282
+ "aria-hidden": true,
283
+ children: /*#__PURE__*/_jsx(MenuBookIcon, {
284
+ fontSize: "small"
285
+ })
286
+ })
287
+ }), /*#__PURE__*/_jsx(Tooltip, {
288
+ title: (props.phrase.definitions || []).join("; "),
289
+ children: /*#__PURE__*/_jsx(Typography, {
290
+ variant: "body2",
291
+ color: "textSecondary",
292
+ className: classes.definitionsText,
293
+ children: (props.phrase.definitions || []).join("; ")
294
+ })
295
+ })]
296
+ })]
297
+ })
174
298
  })
175
299
  }), /*#__PURE__*/_jsxs(ListItemSecondaryAction, {
176
300
  children: [!disableActions && !isLargeScreen && isCreator && /*#__PURE__*/_jsxs("div", {
@@ -287,6 +411,7 @@ function Phrase(props) {
287
411
  image: props.phrase.image,
288
412
  translations: translations,
289
413
  alternativeVersions: props.phrase.alternativeVersions,
414
+ definitions: props.phrase.definitions,
290
415
  voices: props.phrase.voices,
291
416
  learnLang: learnLang,
292
417
  forLang: forLang,
@@ -57,6 +57,7 @@ function Classrooms({
57
57
  handleViewUserProfile,
58
58
  featureFlags
59
59
  }) {
60
+ console.log("Classrooms render");
60
61
  const {
61
62
  classes
62
63
  } = useStyles();
@@ -1659,6 +1659,7 @@ export default function ViewClassroom({
1659
1659
  featureFlags,
1660
1660
  ...otherProps
1661
1661
  }) {
1662
+ console.log("ViewClassroom render");
1662
1663
  const {
1663
1664
  classes
1664
1665
  } = useStyles();
@@ -102,6 +102,7 @@ const useStyles = makeStyles()(theme => ({
102
102
  position: "relative"
103
103
  }
104
104
  }));
105
+ console.log("ViewTopic render");
105
106
  function OverflowMenu({
106
107
  t,
107
108
  topicId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1340",
3
+ "version": "0.1.1350",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [