@nualang/nualang-ui-components 0.1.1352 → 0.1.1353
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.
- package/dist/Dialogs/GenerateDefinitions/GenerateDefinitions.js +1 -0
- package/dist/Dialogs/GeneratePhrases/GeneratePhrases.js +33 -3
- package/dist/Dialogs/UploadPhrases/UploadPhrases.js +75 -14
- package/dist/Dialogs/UploadPhrases/upload-phrases-csv.png +0 -0
- package/dist/Editors/Phrases/Phrases.js +19 -3
- package/dist/Lists/Phrases/Phrases.js +9 -5
- package/dist/Misc/ChatBubble/ChatBubble.js +7 -2
- package/dist/Misc/HoverText/HoverText.js +18 -7
- package/dist/Misc/RubyText/RubyText.js +27 -0
- package/dist/Screens/Courses/ViewCourse/ViewTopic/ViewTopic.js +0 -89
- package/dist/Tables/Progress/Header.js +557 -0
- package/dist/Tables/Progress/Progress.js +54 -349
- package/dist/Tables/Progress/ProgressList.js +21 -43
- package/dist/Tables/Progress/ProgressList.test.js +48 -0
- package/dist/Tables/Progress/ProgressTable.js +58 -1027
- package/dist/Tables/Progress/ProgressTable.test.js +258 -0
- package/dist/Tables/Progress/ProgressTableRow.js +270 -0
- package/dist/Tables/Progress/README.md +53 -0
- package/dist/Tables/Progress/TableToolbar.js +130 -0
- package/dist/Tables/Progress/TableToolbar.test.js +69 -0
- package/dist/Tables/Progress/cells/index.js +446 -0
- package/dist/Tables/Progress/cells/index.test.js +345 -0
- package/dist/Tables/Progress/constants.js +6 -0
- package/dist/Tables/Progress/exportToExcel.js +224 -0
- package/dist/Tables/Progress/exportToExcel.test.js +135 -0
- package/dist/Tables/Progress/helpers.js +42 -0
- package/dist/Tables/Progress/helpers.test.js +23 -0
- package/dist/Tables/Progress/sorting.js +78 -0
- package/dist/Tables/Progress/sorting.test.js +69 -0
- package/dist/Tables/Progress/styledComponents.js +156 -0
- package/dist/Tables/Progress/useProgressUrlParams.js +147 -0
- package/dist/Tables/Progress/useProgressUrlParams.test.js +183 -0
- package/dist/Tables/Progress/utils.js +2 -2
- package/dist/Tables/Progress/utils.test.js +129 -0
- package/dist/Tables/Progress/viewState.js +25 -0
- package/dist/hooks/usePhonetics.js +42 -0
- package/dist/utils/japaneseTokenizer.js +55 -0
- package/package.json +4 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
2
|
import ResponsiveDialog from "../ResponsiveDialog/ResponsiveDialog";
|
|
3
3
|
import { AutoFixHigh } from "@mui/icons-material";
|
|
4
|
-
import { Alert, Grid, InputAdornment, LinearProgress, MenuItem, Slider, TextField, Tooltip, Typography, ListItem, ListItemText, useMediaQuery } from "@mui/material";
|
|
4
|
+
import { Alert, Grid, InputAdornment, LinearProgress, MenuItem, Slider, TextField, Tooltip, Typography, ListItem, ListItemText, useMediaQuery, FormControlLabel, Switch, Box } from "@mui/material";
|
|
5
5
|
import { useTheme } from "@mui/material/styles";
|
|
6
6
|
import { Link } from "react-router";
|
|
7
7
|
import InfoIcon from "@mui/icons-material/Info";
|
|
@@ -115,6 +115,7 @@ function GeneratePhrases({
|
|
|
115
115
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
116
116
|
const [phraseType, setPhraseType] = useState("words");
|
|
117
117
|
const [generatedPhrases, setGeneratedPhrases] = useState([]);
|
|
118
|
+
const [generateDefinitions, setGenerateDefinitions] = useState(true);
|
|
118
119
|
const isPhrases = currentPhrases && currentPhrases.length > 0;
|
|
119
120
|
const phrases = isPhrases ? currentPhrases.map(({
|
|
120
121
|
phrase,
|
|
@@ -188,7 +189,8 @@ function GeneratePhrases({
|
|
|
188
189
|
difficulty,
|
|
189
190
|
phraseType,
|
|
190
191
|
isPhrases,
|
|
191
|
-
phrases
|
|
192
|
+
phrases,
|
|
193
|
+
generateDefinitions
|
|
192
194
|
}
|
|
193
195
|
});
|
|
194
196
|
const phrasesArray = chatGptResponse.result;
|
|
@@ -217,6 +219,9 @@ function GeneratePhrases({
|
|
|
217
219
|
const handleCancel = () => {
|
|
218
220
|
setGeneratedPhrases([]);
|
|
219
221
|
};
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
setGenerateDefinitions(phraseType === 'words');
|
|
224
|
+
}, [phraseType]);
|
|
220
225
|
return /*#__PURE__*/_jsxs(ResponsiveDialog, {
|
|
221
226
|
open: open,
|
|
222
227
|
closeText: generatedPhrases.length > 0 ? t("cancel") : t("close"),
|
|
@@ -353,6 +358,31 @@ function GeneratePhrases({
|
|
|
353
358
|
min: 4,
|
|
354
359
|
max: 16
|
|
355
360
|
})]
|
|
361
|
+
}), /*#__PURE__*/_jsxs(Grid, {
|
|
362
|
+
size: 12,
|
|
363
|
+
children: [/*#__PURE__*/_jsx(Typography, {
|
|
364
|
+
children: /*#__PURE__*/_jsx(Box, {
|
|
365
|
+
children: t("generate_definitions")
|
|
366
|
+
})
|
|
367
|
+
}), /*#__PURE__*/_jsx(FormControlLabel, {
|
|
368
|
+
label: "",
|
|
369
|
+
control: /*#__PURE__*/_jsx(_Fragment, {
|
|
370
|
+
children: /*#__PURE__*/_jsx("span", {
|
|
371
|
+
children: /*#__PURE__*/_jsx(Switch, {
|
|
372
|
+
checked: generateDefinitions,
|
|
373
|
+
onChange: e => {
|
|
374
|
+
setGenerateDefinitions(e.target.checked);
|
|
375
|
+
},
|
|
376
|
+
inputProps: {
|
|
377
|
+
"aria-label": "controlled"
|
|
378
|
+
},
|
|
379
|
+
sx: {
|
|
380
|
+
marginLeft: 0.5
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
})]
|
|
356
386
|
})]
|
|
357
387
|
}), generatedPhrases.length > 0 && /*#__PURE__*/_jsxs(_Fragment, {
|
|
358
388
|
children: [/*#__PURE__*/_jsx(Typography, {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { useState, useRef, useEffect } from "react";
|
|
2
2
|
import { CSVLink } from "react-csv";
|
|
3
|
-
import { Box, DialogContentText, Typography, Alert, TextField, Divider } from "@mui/material";
|
|
3
|
+
import { Box, DialogContentText, Typography, Alert, TextField, Divider, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from "@mui/material";
|
|
4
4
|
import { makeStyles } from "tss-react/mui";
|
|
5
5
|
import FileCopyIcon from "@mui/icons-material/FileCopy";
|
|
6
6
|
import DownloadIcon from "@mui/icons-material/Download";
|
|
7
|
+
import FrontHandIcon from '@mui/icons-material/FrontHand';
|
|
8
|
+
import WavingHandIcon from '@mui/icons-material/WavingHand';
|
|
9
|
+
import BadgeIcon from '@mui/icons-material/Badge';
|
|
7
10
|
import PhraseList from "../../Lists/Phrases/Phrases";
|
|
8
|
-
import ExampleCSVImage from "./upload-phrases-csv.png";
|
|
9
11
|
import ParseScriptErrors from "../../Lists/ParseScriptErrors/ParseScriptErrors";
|
|
10
12
|
import DefaultButton from "../../Misc/DefaultColourButton/DefaultColourButton";
|
|
11
13
|
import CSVUploader from "../../Misc/CSVUploader/CSVUploader";
|
|
@@ -19,7 +21,6 @@ import DialogContent from "@mui/material/DialogContent";
|
|
|
19
21
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
20
22
|
import { useTheme } from "@mui/material/styles";
|
|
21
23
|
import Popper from "@mui/material/Popper";
|
|
22
|
-
import Paper from "@mui/material/Paper";
|
|
23
24
|
import Grow from "@mui/material/Grow";
|
|
24
25
|
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
|
25
26
|
import MenuItem from "@mui/material/MenuItem";
|
|
@@ -29,14 +30,17 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
29
30
|
const phrasesData = [{
|
|
30
31
|
phrase: "Hello",
|
|
31
32
|
translation: "hola",
|
|
33
|
+
definitions: "a greeting",
|
|
32
34
|
image: "https://images.nualang.com/eyJidWNrZXQiOiJudWFsYW5nLWltYWdlcy1wcm9kIiwia2V5IjoicGhyYXNlcy9EYTNXcThzUUktMTYwMzg4Nzg3MjU2NS53ZWJwIn0="
|
|
33
35
|
}, {
|
|
34
36
|
phrase: "Goodbye",
|
|
35
37
|
translation: "adios",
|
|
38
|
+
definitions: "a farewell",
|
|
36
39
|
image: "https://images.nualang.com/eyJidWNrZXQiOiJudWFsYW5nLWltYWdlcy1wcm9kIiwia2V5IjoicGhyYXNlcy9RbXBFcWZYdHItMTYwMzg4Nzk2MjAzMC53ZWJwIn0="
|
|
37
40
|
}, {
|
|
38
41
|
phrase: "My name is",
|
|
39
42
|
translation: "mi nombre es | me llamo",
|
|
43
|
+
definitions: "used to introduce yourself",
|
|
40
44
|
image: "https://images.nualang.com/eyJidWNrZXQiOiJudWFsYW5nLWltYWdlcy1wcm9kIiwia2V5IjoicGhyYXNlcy9hREFmczhFeTEtMTYwMzg5MDM2MjAxNy53ZWJwIn0="
|
|
41
45
|
}];
|
|
42
46
|
const phrasesHeaders = [{
|
|
@@ -45,6 +49,9 @@ const phrasesHeaders = [{
|
|
|
45
49
|
}, {
|
|
46
50
|
label: "translations",
|
|
47
51
|
key: "translation"
|
|
52
|
+
}, {
|
|
53
|
+
label: "definitions",
|
|
54
|
+
key: "definitions"
|
|
48
55
|
}, {
|
|
49
56
|
label: "image",
|
|
50
57
|
key: "image"
|
|
@@ -54,6 +61,22 @@ const PhrasesCsvLink = {
|
|
|
54
61
|
data: phrasesData,
|
|
55
62
|
filename: "phrases_template.csv"
|
|
56
63
|
};
|
|
64
|
+
const csvStructureExampleRows = [{
|
|
65
|
+
phrase: "hello",
|
|
66
|
+
translations: "hola",
|
|
67
|
+
definitions: "A word used to greet someone",
|
|
68
|
+
image: FrontHandIcon
|
|
69
|
+
}, {
|
|
70
|
+
phrase: "goodbye",
|
|
71
|
+
translations: "adios",
|
|
72
|
+
definitions: "A word used when leaving someone",
|
|
73
|
+
image: WavingHandIcon
|
|
74
|
+
}, {
|
|
75
|
+
phrase: "my name is",
|
|
76
|
+
translations: "mi nombre es | me llamo",
|
|
77
|
+
definitions: "A phrase used to tell someone your name",
|
|
78
|
+
image: BadgeIcon
|
|
79
|
+
}];
|
|
57
80
|
const useStyles = makeStyles()(theme => ({
|
|
58
81
|
parseErrorsBox: {
|
|
59
82
|
marginTop: theme.spacing()
|
|
@@ -65,10 +88,6 @@ const useStyles = makeStyles()(theme => ({
|
|
|
65
88
|
dialog: {
|
|
66
89
|
marginBottom: "15px"
|
|
67
90
|
},
|
|
68
|
-
image: {
|
|
69
|
-
height: "auto",
|
|
70
|
-
width: "100%"
|
|
71
|
-
},
|
|
72
91
|
csvButton: {
|
|
73
92
|
textDecoration: "none"
|
|
74
93
|
},
|
|
@@ -92,6 +111,7 @@ function parsePhraseWithAlternatives(phraseString) {
|
|
|
92
111
|
* - phrase: Primary phrase with optional alternatives separated by '|'
|
|
93
112
|
* Example: "Hello|Hi|Hey"
|
|
94
113
|
* - translations: Multiple translations separated by '|'
|
|
114
|
+
* - definitions: Multiple definitions separated by '|'
|
|
95
115
|
* - image: Optional image URL
|
|
96
116
|
*/
|
|
97
117
|
|
|
@@ -135,10 +155,12 @@ function UploadPhrases({
|
|
|
135
155
|
alternatives
|
|
136
156
|
} = parsePhraseWithAlternatives(data.phrase);
|
|
137
157
|
const formattedTranslations = data.translations.split("|").map(t => t.trim().replace(/^"|"$/g, "").trim());
|
|
158
|
+
const formattedDefinitions = data.definitions ? data.definitions.split("|").map(d => d.trim().replace(/^"|"$/g, "").trim()).filter(Boolean) : [];
|
|
138
159
|
return {
|
|
139
160
|
phrase: primary,
|
|
140
161
|
alternativeVersions: alternatives,
|
|
141
162
|
translations: formattedTranslations,
|
|
163
|
+
definitions: formattedDefinitions,
|
|
142
164
|
image: data.image ? data.image.trim() : "",
|
|
143
165
|
voices: [],
|
|
144
166
|
idx: index
|
|
@@ -175,8 +197,6 @@ function UploadPhrases({
|
|
|
175
197
|
setErrorMessage("File must be in the .csv format");
|
|
176
198
|
return;
|
|
177
199
|
} else if (results.errors && results.errors.length) {
|
|
178
|
-
// formats errors coming from CSVReader into common error format
|
|
179
|
-
// used for validating rows
|
|
180
200
|
const formattedErrors = results.errors.reduce((acc, cur) => {
|
|
181
201
|
acc.push({
|
|
182
202
|
row: cur.row,
|
|
@@ -212,10 +232,51 @@ function UploadPhrases({
|
|
|
212
232
|
children: t("upload_phrases_desc")
|
|
213
233
|
}), /*#__PURE__*/_jsx(Box, {
|
|
214
234
|
py: 1,
|
|
215
|
-
children: /*#__PURE__*/_jsx(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
235
|
+
children: /*#__PURE__*/_jsx(TableContainer, {
|
|
236
|
+
component: Paper,
|
|
237
|
+
variant: "outlined",
|
|
238
|
+
children: /*#__PURE__*/_jsxs(Table, {
|
|
239
|
+
size: "small",
|
|
240
|
+
"aria-label": "CSV structure example",
|
|
241
|
+
children: [/*#__PURE__*/_jsx(TableHead, {
|
|
242
|
+
children: /*#__PURE__*/_jsxs(TableRow, {
|
|
243
|
+
children: [/*#__PURE__*/_jsx(TableCell, {
|
|
244
|
+
children: /*#__PURE__*/_jsx("strong", {
|
|
245
|
+
children: t("phrase")
|
|
246
|
+
})
|
|
247
|
+
}), /*#__PURE__*/_jsx(TableCell, {
|
|
248
|
+
children: /*#__PURE__*/_jsx("strong", {
|
|
249
|
+
children: t("translations")
|
|
250
|
+
})
|
|
251
|
+
}), /*#__PURE__*/_jsx(TableCell, {
|
|
252
|
+
children: /*#__PURE__*/_jsx("strong", {
|
|
253
|
+
children: t("definitions")
|
|
254
|
+
})
|
|
255
|
+
}), /*#__PURE__*/_jsx(TableCell, {
|
|
256
|
+
children: /*#__PURE__*/_jsx("strong", {
|
|
257
|
+
children: t("image")
|
|
258
|
+
})
|
|
259
|
+
})]
|
|
260
|
+
})
|
|
261
|
+
}), /*#__PURE__*/_jsx(TableBody, {
|
|
262
|
+
children: csvStructureExampleRows.map((row, index) => {
|
|
263
|
+
const ImageIcon = row.image;
|
|
264
|
+
return /*#__PURE__*/_jsxs(TableRow, {
|
|
265
|
+
children: [/*#__PURE__*/_jsx(TableCell, {
|
|
266
|
+
children: row.phrase
|
|
267
|
+
}), /*#__PURE__*/_jsx(TableCell, {
|
|
268
|
+
children: row.translations
|
|
269
|
+
}), /*#__PURE__*/_jsx(TableCell, {
|
|
270
|
+
children: row.definitions
|
|
271
|
+
}), /*#__PURE__*/_jsx(TableCell, {
|
|
272
|
+
children: /*#__PURE__*/_jsx(ImageIcon, {
|
|
273
|
+
fontSize: "small"
|
|
274
|
+
})
|
|
275
|
+
})]
|
|
276
|
+
}, `${row.phrase}-${index}`);
|
|
277
|
+
})
|
|
278
|
+
})]
|
|
279
|
+
})
|
|
219
280
|
})
|
|
220
281
|
}), /*#__PURE__*/_jsxs(Box, {
|
|
221
282
|
pb: 2,
|
|
@@ -257,7 +318,7 @@ function UploadPhrases({
|
|
|
257
318
|
id: "csvDataInput",
|
|
258
319
|
name: "csvDataInput",
|
|
259
320
|
label: t("paste_your_csv_data"),
|
|
260
|
-
placeholder: `phrase, translations\n"Hola", "Hello | Hi"\n"Adios", "Goodbye"`,
|
|
321
|
+
placeholder: `phrase, translations, definitions\n"Hola", "Hello | Hi", "a greeting"\n"Adios", "Goodbye", "a farewell"`,
|
|
261
322
|
value: csvDataInput,
|
|
262
323
|
fullWidth: true,
|
|
263
324
|
multiline: true,
|
|
Binary file
|
|
@@ -315,7 +315,8 @@ function PhrasesEditor(props) {
|
|
|
315
315
|
...r,
|
|
316
316
|
phrase: r.phrase.split("|")[0].trim() || "",
|
|
317
317
|
alternativeVersions: updatedAlternativeVersions,
|
|
318
|
-
translations: (r.translations || "").split("|").map(t => t.trim()).filter(Boolean)
|
|
318
|
+
translations: (r.translations || "").split("|").map(t => t.trim()).filter(Boolean),
|
|
319
|
+
definitions: (r.definitions || "").split("|").map(d => d.trim()).filter(Boolean)
|
|
319
320
|
};
|
|
320
321
|
});
|
|
321
322
|
};
|
|
@@ -405,7 +406,22 @@ function PhrasesEditor(props) {
|
|
|
405
406
|
onChange: onFieldChange(row.id, "translations")
|
|
406
407
|
});
|
|
407
408
|
},
|
|
408
|
-
width:
|
|
409
|
+
width: 25
|
|
410
|
+
}, {
|
|
411
|
+
id: "definitions",
|
|
412
|
+
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
413
|
+
children: t("definitions")
|
|
414
|
+
}),
|
|
415
|
+
value: (row, {
|
|
416
|
+
focus
|
|
417
|
+
}) => {
|
|
418
|
+
return /*#__PURE__*/_jsx(Input, {
|
|
419
|
+
value: row.definitions,
|
|
420
|
+
focus: focus,
|
|
421
|
+
onChange: onFieldChange(row.id, "definitions")
|
|
422
|
+
});
|
|
423
|
+
},
|
|
424
|
+
width: 25
|
|
409
425
|
}, {
|
|
410
426
|
id: "image",
|
|
411
427
|
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
@@ -475,7 +491,7 @@ function PhrasesEditor(props) {
|
|
|
475
491
|
phrase: [row.phrase || "", ...(row.alternativeVersions || [])].filter(Boolean).join(" | ")
|
|
476
492
|
}));
|
|
477
493
|
const parser = new Parser({
|
|
478
|
-
fields: ["phrase", "translations", "image"],
|
|
494
|
+
fields: ["phrase", "translations", "definitions", "image"],
|
|
479
495
|
defaultValue: ""
|
|
480
496
|
});
|
|
481
497
|
const csv = parser.parse(formattedRows);
|
|
@@ -70,6 +70,11 @@ const useStyles = makeStyles()(theme => ({
|
|
|
70
70
|
overflow: "hidden",
|
|
71
71
|
whiteSpace: "nowrap",
|
|
72
72
|
textOverflow: "ellipsis"
|
|
73
|
+
},
|
|
74
|
+
actionButton: {
|
|
75
|
+
marginTop: theme.spacing(0.5),
|
|
76
|
+
marginBottom: theme.spacing(0.5),
|
|
77
|
+
marginRight: theme.spacing()
|
|
73
78
|
}
|
|
74
79
|
}));
|
|
75
80
|
function Phrase(props) {
|
|
@@ -144,11 +149,6 @@ function Phrase(props) {
|
|
|
144
149
|
return;
|
|
145
150
|
}
|
|
146
151
|
};
|
|
147
|
-
console.log("Phrase render", {
|
|
148
|
-
phrase,
|
|
149
|
-
translations,
|
|
150
|
-
alternativeVersions
|
|
151
|
-
});
|
|
152
152
|
const handleCloseMenu = () => {
|
|
153
153
|
setAnchorEl(null);
|
|
154
154
|
};
|
|
@@ -593,6 +593,7 @@ function PhraseList({
|
|
|
593
593
|
color: "primary",
|
|
594
594
|
onClick: () => handleDeletePhrases(),
|
|
595
595
|
startIcon: isLargeScreen ? /*#__PURE__*/_jsx(DeleteIcon, {}) : null,
|
|
596
|
+
className: classes.actionButton,
|
|
596
597
|
style: {
|
|
597
598
|
marginLeft: "10px"
|
|
598
599
|
},
|
|
@@ -601,6 +602,7 @@ function PhraseList({
|
|
|
601
602
|
disabled: selectedPhrases.length === 0,
|
|
602
603
|
variant: "contained",
|
|
603
604
|
color: "secondary",
|
|
605
|
+
className: classes.actionButton,
|
|
604
606
|
"aria-label": t("generate_definitions"),
|
|
605
607
|
onClick: () => setIsGenerateDefinitionsOpen(true),
|
|
606
608
|
startIcon: isLargeScreen ? /*#__PURE__*/_jsx(AutoFixHighIcon, {}) : null,
|
|
@@ -705,10 +707,12 @@ function PhraseList({
|
|
|
705
707
|
style: {
|
|
706
708
|
marginLeft: "10px"
|
|
707
709
|
},
|
|
710
|
+
className: classes.actionButton,
|
|
708
711
|
children: isLargeScreen ? t("Remove Phrases") : /*#__PURE__*/_jsx(DeleteIcon, {})
|
|
709
712
|
}), /*#__PURE__*/_jsx(Button, {
|
|
710
713
|
variant: "contained",
|
|
711
714
|
color: "secondary",
|
|
715
|
+
className: classes.actionButton,
|
|
712
716
|
disabled: selectedPhrases.length === 0,
|
|
713
717
|
"aria-label": t("generate_definitions"),
|
|
714
718
|
onClick: () => setIsGenerateDefinitionsOpen(true),
|
|
@@ -5,7 +5,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
5
5
|
const useStyles = makeStyles()(theme => ({
|
|
6
6
|
grid: {
|
|
7
7
|
backgroundColor: theme.palette.background.paper,
|
|
8
|
-
padding: 15,
|
|
9
8
|
marginTop: 5,
|
|
10
9
|
marginBottom: 10,
|
|
11
10
|
marginRight: 5,
|
|
@@ -41,9 +40,14 @@ function ChatBubble({
|
|
|
41
40
|
const {
|
|
42
41
|
classes
|
|
43
42
|
} = useStyles();
|
|
43
|
+
const showPhonetics = learnLang === "chinese" || learnLang === "japanese";
|
|
44
44
|
return /*#__PURE__*/_jsx(Grid, {
|
|
45
45
|
className: classes.grid,
|
|
46
46
|
id: id,
|
|
47
|
+
sx: {
|
|
48
|
+
padding: "15px",
|
|
49
|
+
paddingTop: showPhonetics ? "30px" : "15px"
|
|
50
|
+
},
|
|
47
51
|
children: /*#__PURE__*/_jsx(HoverText, {
|
|
48
52
|
handleTranslate: handleTranslate,
|
|
49
53
|
handleSpeak: handleSpeak,
|
|
@@ -59,7 +63,8 @@ function ChatBubble({
|
|
|
59
63
|
isMessage: isMessage,
|
|
60
64
|
muteSound: muteSound,
|
|
61
65
|
t: t,
|
|
62
|
-
languageTag: languageTag
|
|
66
|
+
languageTag: languageTag,
|
|
67
|
+
showPhonetics: showPhonetics
|
|
63
68
|
})
|
|
64
69
|
});
|
|
65
70
|
}
|
|
@@ -3,9 +3,11 @@ import { Typography, Tooltip } from "@mui/material";
|
|
|
3
3
|
import { useTheme } from "@emotion/react";
|
|
4
4
|
import ReactMarkdown from "react-markdown";
|
|
5
5
|
import { removeExtraWhiteSpaces } from "../../utils";
|
|
6
|
+
import { usePhonetics } from "../../hooks/usePhonetics";
|
|
7
|
+
import RubyText from "../RubyText/RubyText";
|
|
6
8
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
7
9
|
function wordTextPropsAreEqual(prevProps, nextProps) {
|
|
8
|
-
return prevProps.value === nextProps.value && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover;
|
|
10
|
+
return prevProps.value === nextProps.value && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover && prevProps.showPhonetics === nextProps.showPhonetics;
|
|
9
11
|
}
|
|
10
12
|
const isOnlyUnderscore = str => /^([_])\1+$/.test(str);
|
|
11
13
|
const WordText = /*#__PURE__*/memo(props => {
|
|
@@ -20,7 +22,8 @@ const WordText = /*#__PURE__*/memo(props => {
|
|
|
20
22
|
voicePitch,
|
|
21
23
|
voiceSpeed,
|
|
22
24
|
muteSound,
|
|
23
|
-
disableHover
|
|
25
|
+
disableHover,
|
|
26
|
+
showPhonetics
|
|
24
27
|
} = props;
|
|
25
28
|
const [translationWord, setTranslationWord] = useState({
|
|
26
29
|
translatedText: ""
|
|
@@ -28,6 +31,8 @@ const WordText = /*#__PURE__*/memo(props => {
|
|
|
28
31
|
const {
|
|
29
32
|
translatedText
|
|
30
33
|
} = translationWord;
|
|
34
|
+
const isPhoneticLanguage = learnLang === "chinese" || learnLang === "japanese";
|
|
35
|
+
const phoneticSegments = usePhonetics(showPhonetics && isPhoneticLanguage ? value?.trim() : null, learnLang);
|
|
31
36
|
const translateWord = async word => {
|
|
32
37
|
try {
|
|
33
38
|
const translationResult = await handleTranslate(word, learnLang, forLang);
|
|
@@ -73,6 +78,9 @@ const WordText = /*#__PURE__*/memo(props => {
|
|
|
73
78
|
children: value
|
|
74
79
|
});
|
|
75
80
|
}
|
|
81
|
+
const displayContent = showPhonetics && phoneticSegments ? /*#__PURE__*/_jsx(RubyText, {
|
|
82
|
+
segments: phoneticSegments
|
|
83
|
+
}) : value;
|
|
76
84
|
return /*#__PURE__*/_jsx(Tooltip, {
|
|
77
85
|
onOpen: () => handleHover(value),
|
|
78
86
|
enterDelay: 500,
|
|
@@ -82,7 +90,7 @@ const WordText = /*#__PURE__*/memo(props => {
|
|
|
82
90
|
children: /*#__PURE__*/_jsx("span", {
|
|
83
91
|
translate: "no",
|
|
84
92
|
"data-text": value.trim(),
|
|
85
|
-
children:
|
|
93
|
+
children: displayContent
|
|
86
94
|
})
|
|
87
95
|
});
|
|
88
96
|
}, wordTextPropsAreEqual);
|
|
@@ -113,7 +121,8 @@ function HoverText({
|
|
|
113
121
|
muteSound,
|
|
114
122
|
disableHover,
|
|
115
123
|
t,
|
|
116
|
-
languageTag
|
|
124
|
+
languageTag,
|
|
125
|
+
showPhonetics
|
|
117
126
|
}) {
|
|
118
127
|
const theme = useTheme();
|
|
119
128
|
let formattedSource = formatUnderscores ? detectAndFormatUnderscores(source) : source;
|
|
@@ -163,7 +172,8 @@ function HoverText({
|
|
|
163
172
|
voicePitch: voicePitch,
|
|
164
173
|
voiceSpeed: voiceSpeed,
|
|
165
174
|
muteSound: muteSound,
|
|
166
|
-
disableHover: disableHover
|
|
175
|
+
disableHover: disableHover,
|
|
176
|
+
showPhonetics: showPhonetics
|
|
167
177
|
}, index);
|
|
168
178
|
}
|
|
169
179
|
return word;
|
|
@@ -233,7 +243,8 @@ function HoverText({
|
|
|
233
243
|
voice: voice,
|
|
234
244
|
voicePitch: voicePitch,
|
|
235
245
|
voiceSpeed: voiceSpeed,
|
|
236
|
-
muteSound: muteSound
|
|
246
|
+
muteSound: muteSound,
|
|
247
|
+
showPhonetics: showPhonetics
|
|
237
248
|
}, index)), formattedSource && /*#__PURE__*/_jsx(ReactMarkdown, {
|
|
238
249
|
components: components,
|
|
239
250
|
children: formattedSource
|
|
@@ -242,6 +253,6 @@ function HoverText({
|
|
|
242
253
|
});
|
|
243
254
|
}
|
|
244
255
|
function hoverTextPropsAreEqual(prevProps, nextProps) {
|
|
245
|
-
return prevProps.source === nextProps.source && prevProps.wordList === nextProps.wordList && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.isMessage === nextProps.isMessage && prevProps.gutterBottom === nextProps.gutterBottom && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover;
|
|
256
|
+
return prevProps.source === nextProps.source && prevProps.wordList === nextProps.wordList && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.isMessage === nextProps.isMessage && prevProps.gutterBottom === nextProps.gutterBottom && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover && prevProps.showPhonetics === nextProps.showPhonetics;
|
|
246
257
|
}
|
|
247
258
|
export default /*#__PURE__*/memo(HoverText, hoverTextPropsAreEqual);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
function RubyText({
|
|
3
|
+
segments
|
|
4
|
+
}) {
|
|
5
|
+
if (!segments) return null;
|
|
6
|
+
return /*#__PURE__*/_jsx(_Fragment, {
|
|
7
|
+
children: segments.map((seg, i) => seg.phonetic ? /*#__PURE__*/_jsxs("ruby", {
|
|
8
|
+
style: {
|
|
9
|
+
rubyAlign: "center"
|
|
10
|
+
},
|
|
11
|
+
children: [seg.text, /*#__PURE__*/_jsx("rp", {
|
|
12
|
+
children: "("
|
|
13
|
+
}), /*#__PURE__*/_jsx("rt", {
|
|
14
|
+
style: {
|
|
15
|
+
fontSize: "0.6em",
|
|
16
|
+
opacity: 0.75
|
|
17
|
+
},
|
|
18
|
+
children: seg.phonetic
|
|
19
|
+
}), /*#__PURE__*/_jsx("rp", {
|
|
20
|
+
children: ")"
|
|
21
|
+
})]
|
|
22
|
+
}, i) : /*#__PURE__*/_jsx("span", {
|
|
23
|
+
children: seg.text
|
|
24
|
+
}, i))
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export default RubyText;
|
|
@@ -23,11 +23,9 @@ import PhraseList from "../../../../Lists/Phrases/Phrases";
|
|
|
23
23
|
import UpdateTopic from "../../../../Forms/UpdateTopic/UpdateTopic";
|
|
24
24
|
import UpgradeSubscription from "../../../../Dialogs/UpgradeSubscription/UpgradeSubscription";
|
|
25
25
|
import ShareDrawer from "../../../../Misc/ShareButton/ShareDrawer";
|
|
26
|
-
import Notes from "../../../../Misc/Notes/Notes";
|
|
27
26
|
import { randomId } from "../../../../utils/index";
|
|
28
27
|
import PlaceholderImages from "../../../../utils/placeholder-images/index";
|
|
29
28
|
import useConfirm from "../../../../hooks/useConfirm";
|
|
30
|
-
import PhrasesEditor from "../../../../Editors/Phrases/Phrases";
|
|
31
29
|
import ProgressiveCardMedia from "../../../../Misc/ProgressiveCardMedia/ProgressiveCardMedia";
|
|
32
30
|
import CreatorNotSubscribed from "../../../../Dialogs/CreatorNotSubscribed/CreatorNotSubscribed";
|
|
33
31
|
import Overlay from "../../../../Misc/Overlay/Overlay";
|
|
@@ -520,8 +518,6 @@ function Topic({
|
|
|
520
518
|
const [isViewPdfOpen, setIsViewPdfOpen] = useState(false);
|
|
521
519
|
const handleOpenViewPDF = () => setIsViewPdfOpen(true);
|
|
522
520
|
const handleCloseViewPdf = () => setIsViewPdfOpen(false);
|
|
523
|
-
const [isCreatePhraseDialogOpen, setIsCreatePhraseDialogOpen] = useState(false);
|
|
524
|
-
const [isUploadPhrasesDialogOpen, setIsUploadPhrasesDialogOpen] = useState(false);
|
|
525
521
|
const topicCompletion = useMemo(() => {
|
|
526
522
|
if (isMember && topic?.topicId && topic?.completions?.length) {
|
|
527
523
|
return calcTopicCompletions(topic, topic.completions, false, null, isChallengeModeStudent).completion;
|
|
@@ -653,34 +649,6 @@ function Topic({
|
|
|
653
649
|
bannerXs,
|
|
654
650
|
difficulty
|
|
655
651
|
} = topic;
|
|
656
|
-
const handleAddPhrase = async values => {
|
|
657
|
-
try {
|
|
658
|
-
const response = await handleCreatePhrase(courseId, sectionId, topicId, values);
|
|
659
|
-
if (response) {
|
|
660
|
-
setIsCreatePhraseDialogOpen(false);
|
|
661
|
-
return response;
|
|
662
|
-
}
|
|
663
|
-
return null;
|
|
664
|
-
} catch (error) {
|
|
665
|
-
console.error(error);
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
const updatePhrase = async (index, updatedPhrase) => {
|
|
670
|
-
try {
|
|
671
|
-
const response = await handleUpdatePhrase(courseId, sectionId, topicId, index, updatedPhrase);
|
|
672
|
-
return response;
|
|
673
|
-
} catch (error) {
|
|
674
|
-
console.error(error);
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
const updatePhrases = async phrases => {
|
|
678
|
-
try {
|
|
679
|
-
handleUpdatePhrases(courseId, sectionId, topicId, phrases);
|
|
680
|
-
} catch (error) {
|
|
681
|
-
console.error(error);
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
652
|
const updateRoleplays = async roleplays => {
|
|
685
653
|
try {
|
|
686
654
|
handleUpdateRoleplays(courseId, sectionId, topicId, roleplays);
|
|
@@ -740,13 +708,6 @@ function Topic({
|
|
|
740
708
|
}), t("confirmDeletionText"));
|
|
741
709
|
if (confirmed) handleDeleteTopic(topic);
|
|
742
710
|
};
|
|
743
|
-
const handleRemovePhrase = async phraseId => {
|
|
744
|
-
try {
|
|
745
|
-
await handleDeletePhrase(courseId, sectionId, topicId, phraseId);
|
|
746
|
-
} catch (error) {
|
|
747
|
-
console.error(error);
|
|
748
|
-
}
|
|
749
|
-
};
|
|
750
711
|
const deletePDF = async () => {
|
|
751
712
|
const confirmed = await confirm(t("delete_pdf"), t("delete_pdf_confirmation"));
|
|
752
713
|
if (confirmed) {
|
|
@@ -1061,56 +1022,6 @@ function Topic({
|
|
|
1061
1022
|
buttonText: !authenticated ? t("sign_in") : t("join"),
|
|
1062
1023
|
onClick: !authenticated ? handleSignIn : handleJoin
|
|
1063
1024
|
})]
|
|
1064
|
-
}), /*#__PURE__*/_jsx(Box, {
|
|
1065
|
-
py: 1,
|
|
1066
|
-
children: /*#__PURE__*/_jsx(Paper, {
|
|
1067
|
-
children: /*#__PURE__*/_jsx(Box, {
|
|
1068
|
-
py: 1,
|
|
1069
|
-
children: /*#__PURE__*/_jsx(PhrasesEditor, {
|
|
1070
|
-
t: t,
|
|
1071
|
-
phrases: phrases,
|
|
1072
|
-
isCreator: isCreator,
|
|
1073
|
-
isMember: isMember,
|
|
1074
|
-
handleTranslate: handleTranslate,
|
|
1075
|
-
handleUpdatePhrase: updatePhrase,
|
|
1076
|
-
handleRemovePhrase: handleRemovePhrase,
|
|
1077
|
-
uploadAudio: uploadAudio,
|
|
1078
|
-
handleSpeak: handleSpeak,
|
|
1079
|
-
updatePhrases: updatePhrases,
|
|
1080
|
-
learnLang: learnLang,
|
|
1081
|
-
forLang: forLang,
|
|
1082
|
-
voices: voices,
|
|
1083
|
-
userAttributes: userAttributes,
|
|
1084
|
-
disablePadding: true,
|
|
1085
|
-
forLangCharacters: forLangCharacters,
|
|
1086
|
-
learnLangCharacters: learnLangCharacters,
|
|
1087
|
-
fileSizeLimit: fileSizeLimit,
|
|
1088
|
-
handleSpreadsheetSaveChanges: handleSpreadsheetSaveChanges,
|
|
1089
|
-
isUploadPhrasesDialogOpen: isUploadPhrasesDialogOpen,
|
|
1090
|
-
setIsUploadPhrasesDialogOpen: setIsUploadPhrasesDialogOpen,
|
|
1091
|
-
isCreatePhraseDialogOpen: isCreatePhraseDialogOpen,
|
|
1092
|
-
setIsCreatePhraseDialogOpen: setIsCreatePhraseDialogOpen,
|
|
1093
|
-
handleAddPhrase: handleAddPhrase,
|
|
1094
|
-
topicName: topicName,
|
|
1095
|
-
validatePhraseRecognition: validatePhraseRecognition,
|
|
1096
|
-
siteLanguage: siteLanguage,
|
|
1097
|
-
makeChatGptApiRequest: makeChatGptApiRequest,
|
|
1098
|
-
difficulty: difficulty,
|
|
1099
|
-
subscription: subscription,
|
|
1100
|
-
topicGoal: topicGoal,
|
|
1101
|
-
verificationStatus: verificationStatus
|
|
1102
|
-
})
|
|
1103
|
-
})
|
|
1104
|
-
})
|
|
1105
|
-
}), /*#__PURE__*/_jsx(Box, {
|
|
1106
|
-
py: 1,
|
|
1107
|
-
children: /*#__PURE__*/_jsx(Notes, {
|
|
1108
|
-
t: t,
|
|
1109
|
-
isCreator: isCreator,
|
|
1110
|
-
initialValues: topic,
|
|
1111
|
-
onSubmit: handleUpdateTopic,
|
|
1112
|
-
handleNotesImageUpload: handleNotesImageUpload
|
|
1113
|
-
})
|
|
1114
1025
|
}), isCreator && /*#__PURE__*/_jsx(Box, {
|
|
1115
1026
|
py: 1,
|
|
1116
1027
|
children: /*#__PURE__*/_jsx(UpdateTopic, {
|