@nualang/nualang-ui-components 0.1.1173 → 0.1.1174

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.
@@ -78,12 +78,14 @@ function VoiceSelector(props) {
78
78
  value = "",
79
79
  onVoicePitchChange,
80
80
  voicePitch,
81
+ generateAudio = false,
82
+ csv = false,
81
83
  ...otherProps
82
84
  } = props;
83
85
  const voiceAccents = Object.values(voices);
84
86
  const voicesArray = voiceAccents.flatMap(voiceOption => voiceOption.voiceOptions);
85
87
  const selectedVoice = (voicesArray || []).find(v => v.name === value);
86
- const devOrStorybook = process.env.REACT_APP_STAGE === "dev" || window.location.host === "localhost:9009";
88
+ const audioOrStorybook = generateAudio || window.location.host === "localhost:9009";
87
89
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.TextField, _extends({
88
90
  select: true,
89
91
  value: value
@@ -106,11 +108,11 @@ function VoiceSelector(props) {
106
108
  alt: ""
107
109
  }))), /*#__PURE__*/_react.default.createElement(_material.Grid, _extends({
108
110
  item: true
109
- }, devOrStorybook ? {
111
+ }, audioOrStorybook ? {
110
112
  sx: {
111
113
  flexGrow: 1
112
114
  }
113
- } : {}), voiceOption.isChild && voiceOption.isChild === true ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, " ", `${voiceOption.name} (${t("child")}) `, " ") : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, voiceOption.name)), devOrStorybook && /*#__PURE__*/_react.default.createElement(_material.Grid, {
115
+ } : {}), voiceOption.isChild && voiceOption.isChild === true ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, " ", `${voiceOption.name} (${t("child")}) `, " ") : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, voiceOption.name)), audioOrStorybook && /*#__PURE__*/_react.default.createElement(_material.Grid, {
114
116
  item: true,
115
117
  sx: {
116
118
  mr: 2,
@@ -122,7 +124,7 @@ function VoiceSelector(props) {
122
124
  }) : /*#__PURE__*/_react.default.createElement("img", {
123
125
  src: _aws.default,
124
126
  alt: "AWS Icon"
125
- }))))])])), value && /*#__PURE__*/_react.default.createElement(_material.Box, {
127
+ }))))])])), value && !csv && /*#__PURE__*/_react.default.createElement(_material.Box, {
126
128
  sx: {
127
129
  width: "100%",
128
130
  px: 1,
@@ -14,6 +14,16 @@ var _AvatarSelector = _interopRequireDefault(require("../../Misc/AvatarSelector/
14
14
  var _InputHelper = _interopRequireDefault(require("../../Forms/InputHelper/InputHelper"));
15
15
  var _utils = require("../../utils");
16
16
  var _AudioNuala = _interopRequireDefault(require("../../img/AudioNuala.svg"));
17
+ var _papaparse = _interopRequireDefault(require("papaparse"));
18
+ var _PlayArrow = _interopRequireDefault(require("@mui/icons-material/PlayArrow"));
19
+ var _jszip = _interopRequireDefault(require("jszip"));
20
+ var _fileSaver = require("file-saver");
21
+ var _lab = require("@mui/lab");
22
+ var _Add = _interopRequireDefault(require("@mui/icons-material/Add"));
23
+ var _Download = _interopRequireDefault(require("@mui/icons-material/Download"));
24
+ var _FileUpload = _interopRequireDefault(require("@mui/icons-material/FileUpload"));
25
+ var _ResponsiveDialog = _interopRequireDefault(require("../../Dialogs/ResponsiveDialog/ResponsiveDialog"));
26
+ var _Delete = _interopRequireDefault(require("@mui/icons-material/Delete"));
17
27
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
28
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
19
29
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -97,7 +107,8 @@ function ActorForm({
97
107
  filteredVoices,
98
108
  setFieldValue,
99
109
  setCheckAudioUrl,
100
- formik
110
+ formik,
111
+ csv = false
101
112
  }) {
102
113
  const [avatarSelector, setAvatarSelector] = (0, _react.useState)(false);
103
114
  const handleAvatarSelection = value => {
@@ -149,7 +160,9 @@ function ActorForm({
149
160
  helperText: touched?.actor?.voice && errors?.actor?.voice,
150
161
  error: touched?.actor?.voice && Boolean(errors?.actor?.voice),
151
162
  voicePitch: actor.voicePitch,
152
- onVoicePitchChange: handleVoicePitchChange
163
+ onVoicePitchChange: handleVoicePitchChange,
164
+ csv: csv,
165
+ generateAudio: true
153
166
  }), /*#__PURE__*/_react.default.createElement(_AvatarSelector.default, {
154
167
  t: t,
155
168
  open: avatarSelector,
@@ -157,7 +170,7 @@ function ActorForm({
157
170
  picture: actor.picture,
158
171
  handleClose: () => setAvatarSelector(false),
159
172
  handleSubmit: handleAvatarSelection
160
- }), formik.values.actor.voice && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.Box, {
173
+ }), formik.values.actor.voice && !csv && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.Box, {
161
174
  sx: {
162
175
  width: "100%",
163
176
  px: 1,
@@ -180,6 +193,193 @@ function ActorForm({
180
193
  learnLang: learnLang
181
194
  }))));
182
195
  }
196
+ function getVoiceLanguageCode(voice, languages) {
197
+ for (const languageCode in languages) {
198
+ const voicesObject = languages[languageCode].Voices;
199
+ if (voicesObject) {
200
+ for (const langKey in voicesObject) {
201
+ const voiceOptions = voicesObject[langKey].voiceOptions;
202
+ const matchingVoice = voiceOptions?.find(option => option.name === voice);
203
+ if (matchingVoice) {
204
+ return langKey;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ function AddPhraseDialog({
212
+ t,
213
+ open,
214
+ onClose,
215
+ handleAddPhrase,
216
+ languages,
217
+ avatars,
218
+ handleSpeak,
219
+ langChar
220
+ }) {
221
+ const [checkAudioUrl, setCheckAudioUrl] = (0, _react.useState)(false);
222
+ const [filteredVoices, setFilteredVoices] = (0, _react.useState)([]);
223
+ const [learnLang, setLearnLang] = (0, _react.useState)("english");
224
+ const formik = (0, _formik.useFormik)({
225
+ initialValues: {
226
+ learnLang: "english",
227
+ actor: {
228
+ voice: "",
229
+ voiceLanguageCode: "",
230
+ voicePitch: "",
231
+ picture: ""
232
+ },
233
+ phraseToSpeak: "",
234
+ voiceSpeed: "",
235
+ fileName: ""
236
+ },
237
+ validationSchema: Yup.object({
238
+ actor: Yup.object().shape({
239
+ voice: Yup.string().nullable(),
240
+ voiceLanguageCode: Yup.string().nullable(),
241
+ voicePitch: Yup.string().nullable(),
242
+ picture: Yup.string().nullable()
243
+ }),
244
+ phraseToSpeak: Yup.string().required("Required"),
245
+ fileName: Yup.string()
246
+ }),
247
+ onSubmit: values => {
248
+ const phraseToAdd = {
249
+ phrase: values.phraseToSpeak,
250
+ actor: values.actor,
251
+ fileName: values.fileName
252
+ };
253
+ handleAddPhrase(phraseToAdd);
254
+ formik.resetForm();
255
+ onClose();
256
+ }
257
+ });
258
+ (0, _react.useEffect)(() => {
259
+ setCheckAudioUrl(true);
260
+ }, [formik.values.phraseToSpeak]);
261
+ (0, _react.useEffect)(() => {
262
+ if (learnLang) {
263
+ const voicesForLang = languages[learnLang]?.Voices || [];
264
+ setFilteredVoices(voicesForLang);
265
+ }
266
+ }, [learnLang, languages]);
267
+ const handleLanguageChange = e => {
268
+ const selectedLang = e.target.value;
269
+ setLearnLang(selectedLang);
270
+ formik.setFieldValue("actor.voice", "");
271
+ setCheckAudioUrl(true);
272
+ };
273
+ return /*#__PURE__*/_react.default.createElement(_ResponsiveDialog.default, {
274
+ open: open,
275
+ onClose: onClose,
276
+ maxWidth: "80%"
277
+ }, /*#__PURE__*/_react.default.createElement(_material.DialogTitle, null, t("add_phrase")), /*#__PURE__*/_react.default.createElement(_material.DialogContent, null, /*#__PURE__*/_react.default.createElement(_material.Grid, {
278
+ container: true,
279
+ spacing: 2
280
+ }, /*#__PURE__*/_react.default.createElement(_material.Grid, {
281
+ item: true,
282
+ xs: 12,
283
+ marginTop: 2
284
+ }, /*#__PURE__*/_react.default.createElement(_LanguageSelector.default, {
285
+ id: "learnLang",
286
+ name: "learnLang",
287
+ label: t("language"),
288
+ fullWidth: true,
289
+ required: true,
290
+ t: t,
291
+ languages: languages,
292
+ value: learnLang,
293
+ onChange: handleLanguageChange,
294
+ onBlur: formik.handleBlur
295
+ })), /*#__PURE__*/_react.default.createElement(_material.Grid, {
296
+ item: true,
297
+ xs: 12
298
+ }, /*#__PURE__*/_react.default.createElement(ActorForm, {
299
+ t: t,
300
+ actor: formik.values.actor,
301
+ handleBlur: formik.handleBlur,
302
+ touched: formik.touched,
303
+ errors: formik.errors,
304
+ learnLang: learnLang,
305
+ filteredVoices: filteredVoices,
306
+ handleSpeak: handleSpeak,
307
+ avatars: avatars,
308
+ langChar: langChar,
309
+ setFieldValue: formik.setFieldValue,
310
+ formik: formik,
311
+ setCheckAudioUrl: setCheckAudioUrl,
312
+ csv: true
313
+ })), /*#__PURE__*/_react.default.createElement(_material.Grid, {
314
+ item: true,
315
+ xs: 12
316
+ }, /*#__PURE__*/_react.default.createElement(_material.TextField, {
317
+ id: "fileName",
318
+ "data-cy": "fileName",
319
+ name: "fileName",
320
+ label: t("file_name"),
321
+ value: formik.values.fileName,
322
+ onChange: formik.handleChange,
323
+ onBlur: formik.handleBlur,
324
+ type: "text",
325
+ margin: "normal",
326
+ variant: "outlined",
327
+ fullWidth: true,
328
+ helperText: formik.touched.fileName && formik.errors.fileName,
329
+ error: formik.touched.fileName && Boolean(formik.errors.fileName),
330
+ InputProps: {
331
+ endAdornment: /*#__PURE__*/_react.default.createElement(_material.InputAdornment, {
332
+ position: "end"
333
+ }, ".mp3")
334
+ }
335
+ })), /*#__PURE__*/_react.default.createElement(_material.Grid, {
336
+ item: true,
337
+ xs: 12,
338
+ marginTop: -1
339
+ }, /*#__PURE__*/_react.default.createElement(_InputHelper.default, {
340
+ t: t,
341
+ id: "phraseToSpeak",
342
+ "data-cy": "phraseToSpeak",
343
+ name: "phraseToSpeak",
344
+ label: t("phrase_to_speak"),
345
+ value: formik.values.phraseToSpeak,
346
+ onChange: formik.handleChange,
347
+ onBlur: formik.handleBlur,
348
+ type: "text",
349
+ margin: "normal",
350
+ variant: "outlined",
351
+ fullWidth: true,
352
+ required: true,
353
+ multiline: true,
354
+ characters: langChar,
355
+ learnLang: learnLang,
356
+ helperText: formik.touched.phraseToSpeak && formik.errors.phraseToSpeak,
357
+ error: formik.touched.phraseToSpeak && Boolean(formik.errors.phraseToSpeak)
358
+ })))), /*#__PURE__*/_react.default.createElement(_material.DialogActions, null, /*#__PURE__*/_react.default.createElement(_material.Button, {
359
+ onClick: onClose
360
+ }, t("close")), /*#__PURE__*/_react.default.createElement(_material.Button, {
361
+ variant: "outlined",
362
+ color: "primary",
363
+ disabled: formik.values.phraseToSpeak === "" || !formik.values.actor.voice,
364
+ onClick: () => handleSpeak({
365
+ text: formik.values.phraseToSpeak,
366
+ learnLanguage: learnLang,
367
+ voiceName: formik.values.actor.voice,
368
+ mute: false,
369
+ callback: () => {},
370
+ textContainer: null,
371
+ languageCode: formik.values.actor.voiceLanguageCode,
372
+ isHoverText: false,
373
+ pitch: formik.values.actor.voicePitch,
374
+ voiceSpeed: formik.values.voiceSpeed
375
+ })
376
+ }, t("play_audio")), /*#__PURE__*/_react.default.createElement(_material.Button, {
377
+ variant: "contained",
378
+ onClick: formik.handleSubmit,
379
+ color: "primary",
380
+ disabled: formik.values.phraseToSpeak === "" || !formik.values.actor.voice
381
+ }, t("add_phrase"))));
382
+ }
183
383
  function GenerateAudio({
184
384
  t,
185
385
  handleSpeak,
@@ -187,12 +387,18 @@ function GenerateAudio({
187
387
  languages,
188
388
  langChar,
189
389
  downloadAudio,
190
- audioUrl
390
+ audioUrl,
391
+ openSnackbar
191
392
  }) {
192
393
  const [learnLang, setLearnLang] = (0, _react.useState)("english");
193
394
  const [filteredVoices, setFilteredVoices] = (0, _react.useState)([]);
194
395
  const [checkAudioUrl, setCheckAudioUrl] = (0, _react.useState)(false);
195
396
  const [voiceSpeed, setVoiceSpeed] = (0, _react.useState)(50);
397
+ const [showCSV, setShowCSV] = (0, _react.useState)(false);
398
+ const [csvData, setCsvData] = (0, _react.useState)([]);
399
+ const [csvHeaders, setCsvHeaders] = (0, _react.useState)(["phrase", "fileName", "voice"]);
400
+ const [isGenerating, setIsGenerating] = (0, _react.useState)(false);
401
+ const [dialogOpen, setDialogOpen] = (0, _react.useState)(false);
196
402
  const formik = (0, _formik.useFormik)({
197
403
  initialValues: {
198
404
  actor: {
@@ -216,7 +422,7 @@ function GenerateAudio({
216
422
  phraseToSpeak: Yup.string().required("Required").test("noSpecialChars", "No special characters", value => !(0, _utils.containsInvalidSymbols)(value))
217
423
  }),
218
424
  onSubmit: values => {
219
- handleSpeak(values.phraseToSpeak, learnLang, values.actor.voice);
425
+ handleSpeak(values.phraseToSpeak, values.actor.voice);
220
426
  }
221
427
  });
222
428
  (0, _react.useEffect)(() => {
@@ -237,6 +443,187 @@ function GenerateAudio({
237
443
  const handleSpeedChange = (event, newValue) => {
238
444
  setVoiceSpeed(newValue);
239
445
  };
446
+ const playPhraseAudio = (phrase, rowVoice) => {
447
+ const voiceToUse = rowVoice || formik.values.actor.voice || "Joey";
448
+ handleSpeak({
449
+ text: phrase,
450
+ learnLanguage: formik.values.learnLang || "english",
451
+ voiceName: voiceToUse,
452
+ mute: false,
453
+ callback: () => {},
454
+ textContainer: null,
455
+ languageCode: getVoiceLanguageCode(voiceToUse, languages),
456
+ isHoverText: false,
457
+ pitch: null,
458
+ voiceSpeed: null
459
+ });
460
+ };
461
+ const languagesContainsVoice = voice => {
462
+ for (const languageCode in languages) {
463
+ const voicesObject = languages[languageCode].Voices;
464
+ if (voicesObject) {
465
+ for (const langKey in voicesObject) {
466
+ const voiceOptions = voicesObject[langKey].voiceOptions;
467
+ if (voiceOptions?.find(option => option.name === voice)) {
468
+ return true;
469
+ }
470
+ }
471
+ }
472
+ }
473
+ return false;
474
+ };
475
+ const handleAddPhrase = newPhrase => {
476
+ csvHeaders.length === 0 && setCsvHeaders(["phrase", "fileName", "voice"]);
477
+ const fileName = `audio_${csvData.length + 1}_${new Date().toISOString().replace(/:/g, "-").slice(0, 19)}.mp3`;
478
+ if (newPhrase.fileName && !newPhrase.fileName.endsWith(".mp3")) {
479
+ newPhrase.fileName = `${newPhrase.fileName}.mp3`;
480
+ }
481
+ const phraseWithDefaults = {
482
+ phrase: newPhrase.phrase,
483
+ fileName: newPhrase.fileName || fileName,
484
+ voice: newPhrase.actor.voice || "Joey"
485
+ };
486
+ setCsvData([...csvData, phraseWithDefaults]);
487
+ };
488
+ const downloadCSVTemplate = () => {
489
+ const csvTemplate = "phrase,fileName,voice\nThis is a test phrase,Test.mp3,Joey\n";
490
+ downloadCSV(csvTemplate, "template.csv");
491
+ };
492
+ const handleCSVUpload = event => {
493
+ const file = event.target.files[0];
494
+ if (file) {
495
+ _papaparse.default.parse(file, {
496
+ header: true,
497
+ skipEmptyLines: true,
498
+ complete: function (results) {
499
+ let headers = results.meta.fields || [];
500
+
501
+ // Ensure 'phrase' is present in the headers
502
+ if (headers.includes("phrase")) {
503
+ if (!headers.includes("fileName")) {
504
+ headers.push("fileName");
505
+ }
506
+ if (!headers.includes("voice")) {
507
+ headers.push("voice");
508
+ }
509
+ const data = results.data.map((row, index) => {
510
+ if (!row.phrase) {
511
+ openSnackbar(`Row ${index + 1} is missing a phrase.`, "error");
512
+ return null; // Invalid row, will be filtered out
513
+ }
514
+ if (!row.fileName) {
515
+ row.fileName = `audio_${index + 1}_${new Date().toISOString().replace(/:/g, "-").slice(0, 19)}.mp3`;
516
+ } else {
517
+ row.fileName = row.fileName.endsWith(".mp3") ? row.fileName : `${row.fileName}.mp3`;
518
+ }
519
+ row.voice = languagesContainsVoice(row.voice) ? row.voice : "Joey";
520
+ return row;
521
+ }).filter(Boolean);
522
+ setCsvData(csvData.length > 0 ? [...csvData, ...data] : data);
523
+ setCsvHeaders(csvHeaders.length > 0 ? csvHeaders : headers);
524
+ } else {
525
+ openSnackbar("CSV must contain 'phrase' header.", "error");
526
+ removeCSVFile();
527
+ }
528
+ },
529
+ error: function (error) {
530
+ console.error("Error parsing CSV: ", error);
531
+ }
532
+ });
533
+ }
534
+ };
535
+ const removeCSVFile = () => {
536
+ setCsvData([]);
537
+ setCsvHeaders([]);
538
+ };
539
+ const handleDeleteRow = rowIndex => {
540
+ const updatedCsvData = csvData.filter((_, index) => index !== rowIndex);
541
+ setCsvData(updatedCsvData);
542
+ };
543
+ const handleChange = (event, newValue) => {
544
+ setShowCSV(newValue);
545
+ };
546
+ const downloadCSV = (text, filename) => {
547
+ const element = document.createElement("a");
548
+ const file = new Blob([text], {
549
+ type: "text/plain"
550
+ });
551
+ element.href = URL.createObjectURL(file);
552
+ element.download = filename;
553
+ document.body.appendChild(element); // Required for this to work in FireFox
554
+ element.click();
555
+ };
556
+ const generateAudioForCSV = async () => {
557
+ const zip = new _jszip.default();
558
+ const timestamp = new Date().toISOString().replace(/:/g, "-").slice(0, 19);
559
+ const folder = zip.folder(`audio_files_${timestamp}`);
560
+ setIsGenerating(true);
561
+ const fileNameCount = {};
562
+ try {
563
+ for (const row of csvData) {
564
+ const phrase = row.phrase;
565
+ let fileName = row.fileName || `audio_${new Date().toISOString().replace(/:/g, "-").slice(0, 19)}`;
566
+ const rowVoice = row.voice || formik.values.actor.voice;
567
+ if (!phrase) {
568
+ openSnackbar("Invalid CSV data: 'phrase' column is required", "error");
569
+ continue;
570
+ }
571
+ try {
572
+ const result = await handleSpeak({
573
+ text: phrase,
574
+ learnLanguage: learnLang,
575
+ voiceName: rowVoice,
576
+ mute: true,
577
+ callback: () => {},
578
+ textContainer: null,
579
+ languageCode: getVoiceLanguageCode(rowVoice, languages),
580
+ isHoverText: false,
581
+ pitch: null,
582
+ voiceSpeed: null
583
+ });
584
+ const audioUrl = result ? result.audioUrl : null;
585
+ if (!audioUrl) continue;
586
+ const response = await fetch(audioUrl);
587
+ if (!response.ok) {
588
+ openSnackbar(`Failed to fetch audio for phrase: "${phrase}"`, "error");
589
+ continue;
590
+ }
591
+ const blob = await response.blob();
592
+ let fileNameWithExtension = fileName.endsWith(".mp3") ? fileName : `${fileName}.mp3`;
593
+ const contentType = blob.type;
594
+ const allowedAudioTypes = ["audio/mpeg", "audio/wav", "audio/ogg", "application/octet-stream"];
595
+ if (!allowedAudioTypes.includes(contentType)) {
596
+ openSnackbar(`Unsupported audio type for phrase: "${phrase}"`, "error");
597
+ continue;
598
+ }
599
+ if (fileNameCount[fileNameWithExtension]) {
600
+ fileNameCount[fileNameWithExtension] += 1;
601
+ const baseName = fileNameWithExtension.replace(".mp3", "");
602
+ fileNameWithExtension = `${baseName}_${fileNameCount[fileNameWithExtension]}.mp3`;
603
+ } else {
604
+ fileNameCount[fileNameWithExtension] = 1;
605
+ }
606
+ folder.file(fileNameWithExtension, blob);
607
+ } catch (error) {
608
+ console.error(`Error generating audio for ${fileName}: ${error.message}`);
609
+ }
610
+ }
611
+
612
+ // Prepare CSV data with updated headers and rows
613
+ const csvContent = [csvHeaders.join(","), ...csvData.map(row => csvHeaders.map(header => row[header]).join(","))].join("\n");
614
+
615
+ // Add CSV file to zip
616
+ zip.file(`updated_data_${timestamp}.csv`, csvContent);
617
+ const zipBlob = await zip.generateAsync({
618
+ type: "blob"
619
+ });
620
+ (0, _fileSaver.saveAs)(zipBlob, `audio_files_${timestamp}.zip`);
621
+ } catch (error) {
622
+ console.error(`Error generating audio files: ${error.message}`);
623
+ } finally {
624
+ setIsGenerating(false);
625
+ }
626
+ };
240
627
  return /*#__PURE__*/_react.default.createElement("form", {
241
628
  onSubmit: formik.handleSubmit
242
629
  }, /*#__PURE__*/_react.default.createElement(_material.Grid, {
@@ -248,16 +635,43 @@ function GenerateAudio({
248
635
  }
249
636
  }, /*#__PURE__*/_react.default.createElement(_material.Grid, {
250
637
  item: true,
251
- xs: 12,
638
+ xs: 6,
639
+ md: 4,
252
640
  style: {
253
- textAlign: "center"
641
+ textAlign: "center",
642
+ marginTop: "15px"
254
643
  }
255
644
  }, /*#__PURE__*/_react.default.createElement("img", {
256
645
  src: _AudioNuala.default,
257
646
  alt: "Audio Nuala",
258
647
  style: {
259
- maxWidth: "400px",
260
- marginTop: "15px"
648
+ maxWidth: "300px"
649
+ }
650
+ })), /*#__PURE__*/_react.default.createElement(_material.Grid, {
651
+ item: true,
652
+ xs: 6,
653
+ md: 4,
654
+ style: {
655
+ textAlign: "left"
656
+ }
657
+ }, /*#__PURE__*/_react.default.createElement(_material.Typography, {
658
+ variant: "h5",
659
+ fontWeight: "bold"
660
+ }, t("text_studio")), /*#__PURE__*/_react.default.createElement(_material.Typography, {
661
+ variant: "body1",
662
+ gutterBottom: true
663
+ }, t("text_studio_description"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
664
+ item: true,
665
+ xs: 12,
666
+ md: 8,
667
+ marginTop: 2,
668
+ marginBottom: 1,
669
+ style: {
670
+ textAlign: "center"
671
+ }
672
+ }, /*#__PURE__*/_react.default.createElement(_material.Divider, {
673
+ sx: {
674
+ borderBottomWidth: 4
261
675
  }
262
676
  })), /*#__PURE__*/_react.default.createElement(_material.Grid, {
263
677
  item: true,
@@ -268,9 +682,28 @@ function GenerateAudio({
268
682
  }, /*#__PURE__*/_react.default.createElement(_material.Grid, {
269
683
  item: true,
270
684
  xs: 12,
271
- md: 6,
272
- paddingRight: 1
273
- }, /*#__PURE__*/_react.default.createElement(_LanguageSelector.default, {
685
+ md: 12,
686
+ marginTop: 2,
687
+ marginBottom: 1.5
688
+ }, /*#__PURE__*/_react.default.createElement(_material.ToggleButtonGroup, {
689
+ color: "primary",
690
+ value: showCSV,
691
+ exclusive: true,
692
+ onChange: handleChange,
693
+ "aria-label": "Platform",
694
+ sx: {
695
+ marginLeft: "auto",
696
+ height: "30px"
697
+ }
698
+ }, /*#__PURE__*/_react.default.createElement(_material.ToggleButton, {
699
+ value: false
700
+ }, t("single_phrase")), /*#__PURE__*/_react.default.createElement(_material.ToggleButton, {
701
+ value: true
702
+ }, t("list_of_phrases")))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
703
+ item: true,
704
+ xs: 12,
705
+ md: 12
706
+ }, !showCSV && /*#__PURE__*/_react.default.createElement(_LanguageSelector.default, {
274
707
  id: "learnLang",
275
708
  name: "learnLang",
276
709
  "data-cy": "learnLang",
@@ -287,9 +720,8 @@ function GenerateAudio({
287
720
  })), /*#__PURE__*/_react.default.createElement(_material.Grid, {
288
721
  item: true,
289
722
  xs: 12,
290
- md: 6,
291
- paddingLeft: 1
292
- }, /*#__PURE__*/_react.default.createElement(ActorForm, {
723
+ md: 12
724
+ }, !showCSV && /*#__PURE__*/_react.default.createElement(ActorForm, {
293
725
  t: t,
294
726
  actor: formik.values.actor,
295
727
  handleBlur: formik.handleBlur,
@@ -311,7 +743,7 @@ function GenerateAudio({
311
743
  xs: 12,
312
744
  md: 12,
313
745
  marginBottom: 2
314
- }, /*#__PURE__*/_react.default.createElement(_InputHelper.default, {
746
+ }, !showCSV ? /*#__PURE__*/_react.default.createElement(_InputHelper.default, {
315
747
  t: t,
316
748
  id: "phraseToSpeak",
317
749
  "data-cy": "phraseToSpeak",
@@ -330,7 +762,144 @@ function GenerateAudio({
330
762
  learnLang: learnLang,
331
763
  helperText: formik.touched.phraseToSpeak && formik.errors.phraseToSpeak,
332
764
  error: formik.touched.phraseToSpeak && Boolean(formik.errors.phraseToSpeak)
765
+ }) : /*#__PURE__*/_react.default.createElement(_material.Grid, {
766
+ container: true,
767
+ item: true,
768
+ xs: 12,
769
+ md: 12,
770
+ spacing: 1,
771
+ marginTop: 0.5,
772
+ marginBottom: 1,
773
+ sx: {
774
+ display: "flex",
775
+ justifyContent: "left",
776
+ alignItems: "center"
777
+ }
778
+ }, /*#__PURE__*/_react.default.createElement(_material.Grid, {
779
+ item: true
780
+ }, /*#__PURE__*/_react.default.createElement(_material.Button, {
781
+ startIcon: /*#__PURE__*/_react.default.createElement(_Add.default, null),
782
+ onClick: () => setDialogOpen(true),
783
+ variant: "contained",
784
+ sx: {
785
+ marginBottom: 2
786
+ }
787
+ }, t("add_phrase"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
788
+ item: true
789
+ }, /*#__PURE__*/_react.default.createElement("label", {
790
+ htmlFor: "upload-csv"
791
+ }, /*#__PURE__*/_react.default.createElement(_material.Button, {
792
+ variant: "contained",
793
+ component: "span",
794
+ startIcon: /*#__PURE__*/_react.default.createElement(_FileUpload.default, null),
795
+ sx: {
796
+ marginBottom: 2
797
+ }
798
+ }, t("upload_csv"))), /*#__PURE__*/_react.default.createElement("input", {
799
+ id: "upload-csv",
800
+ type: "file",
801
+ accept: ".csv",
802
+ onChange: handleCSVUpload,
803
+ style: {
804
+ display: "none"
805
+ }
333
806
  })), /*#__PURE__*/_react.default.createElement(_material.Grid, {
807
+ item: true
808
+ }, /*#__PURE__*/_react.default.createElement(_material.Button, {
809
+ variant: "outlined",
810
+ startIcon: /*#__PURE__*/_react.default.createElement(_Download.default, null),
811
+ onClick: downloadCSVTemplate,
812
+ sx: {
813
+ marginBottom: 2
814
+ }
815
+ }, t("download_csv_template"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
816
+ item: true,
817
+ xs: 12,
818
+ sx: {
819
+ justifyContent: "left"
820
+ }
821
+ }, csvData.length === 0 && /*#__PURE__*/_react.default.createElement(_material.Typography, {
822
+ variant: "body1",
823
+ sx: {
824
+ marginTop: 1
825
+ }
826
+ }, t("no_phrases_yet"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
827
+ container: true,
828
+ item: true,
829
+ xs: 12,
830
+ md: 12,
831
+ spacing: 1,
832
+ marginBottom: 1
833
+ }, csvData.length > 0 && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.Grid, {
834
+ item: true,
835
+ xs: 12
836
+ }, /*#__PURE__*/_react.default.createElement(_material.TableContainer, {
837
+ component: _material.Paper
838
+ }, /*#__PURE__*/_react.default.createElement(_material.Table, {
839
+ sx: {
840
+ minWidth: 650
841
+ },
842
+ "aria-label": "csv table"
843
+ }, /*#__PURE__*/_react.default.createElement(_material.TableHead, null, /*#__PURE__*/_react.default.createElement(_material.TableRow, null, csvHeaders.map((header, index) => /*#__PURE__*/_react.default.createElement(_material.TableCell, {
844
+ key: index,
845
+ align: "center"
846
+ }, /*#__PURE__*/_react.default.createElement("strong", null, header))), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
847
+ align: "center"
848
+ }, /*#__PURE__*/_react.default.createElement("strong", null, t("play_audio"))), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
849
+ align: "center"
850
+ }, /*#__PURE__*/_react.default.createElement("strong", null, t("delete")), " "))), /*#__PURE__*/_react.default.createElement(_material.TableBody, null, csvData.map((row, rowIndex) => /*#__PURE__*/_react.default.createElement(_material.TableRow, {
851
+ key: rowIndex
852
+ }, csvHeaders.map((header, colIndex) => /*#__PURE__*/_react.default.createElement(_material.TableCell, {
853
+ key: colIndex,
854
+ align: "center"
855
+ }, row[header] || null)), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
856
+ align: "center"
857
+ }, /*#__PURE__*/_react.default.createElement(_material.IconButton, {
858
+ onClick: () => playPhraseAudio(row.phrase, row.voice),
859
+ "aria-label": "play"
860
+ }, /*#__PURE__*/_react.default.createElement(_PlayArrow.default, null))), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
861
+ align: "center"
862
+ }, /*#__PURE__*/_react.default.createElement(_material.IconButton, {
863
+ onClick: () => handleDeleteRow(rowIndex) // Call the row deletion function
864
+ ,
865
+ "aria-label": "delete"
866
+ }, /*#__PURE__*/_react.default.createElement(_Delete.default, null))))))))))), csvData.length > 0 && /*#__PURE__*/_react.default.createElement(_material.Grid, {
867
+ item: true,
868
+ xs: 12,
869
+ sx: {
870
+ display: "flex",
871
+ justifyContent: "left",
872
+ alignItems: "center",
873
+ marginTop: 1
874
+ }
875
+ }, /*#__PURE__*/_react.default.createElement(_lab.LoadingButton, {
876
+ variant: "contained",
877
+ onClick: generateAudioForCSV,
878
+ loading: isGenerating,
879
+ startIcon: /*#__PURE__*/_react.default.createElement(_Download.default, null),
880
+ sx: {
881
+ marginBottom: 2
882
+ }
883
+ }, t("download_audio")))), /*#__PURE__*/_react.default.createElement(AddPhraseDialog, {
884
+ t: t,
885
+ open: dialogOpen,
886
+ onClose: () => setDialogOpen(false),
887
+ handleAddPhrase: handleAddPhrase,
888
+ initialValues: {
889
+ learnLang: "english",
890
+ actor: {
891
+ voice: "",
892
+ voiceLanguageCode: "",
893
+ voicePitch: "",
894
+ picture: ""
895
+ },
896
+ phraseToSpeak: "",
897
+ voiceSpeed: 50
898
+ },
899
+ languages: languages,
900
+ avatars: avatars,
901
+ handleSpeak: handleSpeak
902
+ })), !showCSV && /*#__PURE__*/_react.default.createElement(_material.Grid, {
334
903
  container: true,
335
904
  item: true,
336
905
  xs: 12,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1173",
3
+ "version": "0.1.1174",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",
@@ -41,6 +41,7 @@
41
41
  "easymde": "2.18.0",
42
42
  "emoji-mart": "5.5.2",
43
43
  "export-to-csv": "^1.3.0",
44
+ "file-saver": "^2.0.5",
44
45
  "formik": "^2.2.9",
45
46
  "fs": "^0.0.1-security",
46
47
  "immutability-helper": "^3.0",
@@ -48,6 +49,7 @@
48
49
  "json-2-csv": "^5.5.1",
49
50
  "jsonlint": "^1.6.3",
50
51
  "jsonrepair": "^3.6.0",
52
+ "jszip": "^3.10.1",
51
53
  "luxon": "^3.3.0",
52
54
  "moment": "^2.29.4",
53
55
  "n2words": "^1.21.0",