@nualang/nualang-ui-components 0.1.1364 → 0.1.1365
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/Editors/Phrases/Phrases.js +30 -101
- package/dist/Editors/Phrases/SpreadsheetEditor/SpreadsheetEditor.js +91 -44
- package/dist/Forms/DiscussMultiStepFormDialog/MultiStepFormDialog.js +9 -2
- package/dist/Screens/Classrooms/ViewClassroom/ViewClassroom.js +4 -1
- package/dist/hooks/useSpreadsheetState.js +59 -55
- package/package.json +2 -2
- package/dist/Lists/SpreadsheetGrid/SpreadsheetGrid.js +0 -88
|
@@ -7,7 +7,6 @@ import SpreadsheetEditor from "./SpreadsheetEditor/SpreadsheetEditor";
|
|
|
7
7
|
import CreatePhraseDialog from "../../Dialogs/CreatePhrase/CreatePhrase";
|
|
8
8
|
import UploadPhrasesDialog from "../../Dialogs/UploadPhrases/UploadPhrases";
|
|
9
9
|
import DeleteIcon from "@mui/icons-material/Delete";
|
|
10
|
-
import { Input } from "react-spreadsheet-grid";
|
|
11
10
|
import useSpreadsheetState from "../../hooks/useSpreadsheetState";
|
|
12
11
|
import DefaultButton from "../../Misc/DefaultColourButton/DefaultColourButton";
|
|
13
12
|
import { Parser } from "@json2csv/plainjs";
|
|
@@ -134,14 +133,15 @@ const CreatorComponent = ({
|
|
|
134
133
|
t,
|
|
135
134
|
mode,
|
|
136
135
|
errors,
|
|
136
|
+
hasNonEmptyRows,
|
|
137
137
|
rows,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
onColumnResize,
|
|
142
|
-
onFieldChange,
|
|
143
|
-
isRowEmpty,
|
|
138
|
+
processRowUpdate,
|
|
139
|
+
selectionModel,
|
|
140
|
+
onRowSelectionModelChange,
|
|
144
141
|
saveChanges,
|
|
142
|
+
addRow,
|
|
143
|
+
isDirty,
|
|
144
|
+
hasTrailingEmptyRow,
|
|
145
145
|
phrases,
|
|
146
146
|
learnLang,
|
|
147
147
|
forLang,
|
|
@@ -202,14 +202,15 @@ const CreatorComponent = ({
|
|
|
202
202
|
}), mode === Modes.SPREADSHEET && /*#__PURE__*/_jsx(SpreadsheetEditor, {
|
|
203
203
|
t: t,
|
|
204
204
|
errors: errors,
|
|
205
|
+
hasNonEmptyRows: hasNonEmptyRows,
|
|
205
206
|
rows: rows,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
207
|
+
processRowUpdate: processRowUpdate,
|
|
208
|
+
selectionModel: selectionModel,
|
|
209
|
+
onRowSelectionModelChange: onRowSelectionModelChange,
|
|
210
|
+
saveChanges: saveChanges,
|
|
211
|
+
addRow: addRow,
|
|
212
|
+
isDirty: isDirty,
|
|
213
|
+
hasTrailingEmptyRow: hasTrailingEmptyRow
|
|
213
214
|
})]
|
|
214
215
|
});
|
|
215
216
|
function EmptyPhraseList({
|
|
@@ -291,6 +292,10 @@ function PhrasesEditor(props) {
|
|
|
291
292
|
scriptDownloadRef
|
|
292
293
|
} = props;
|
|
293
294
|
const [errors, setErrors] = useState([]);
|
|
295
|
+
const [selectionModel, setSelectionModel] = useState({
|
|
296
|
+
type: "include",
|
|
297
|
+
ids: new Set()
|
|
298
|
+
});
|
|
294
299
|
const [spreadsheetPhrases, setSpreadsheetPhrases] = useState(Object.keys(phrases).length > 0 ? phrases : [rowTemplate]);
|
|
295
300
|
const isRowEmpty = row => {
|
|
296
301
|
let rowEmpty = true;
|
|
@@ -356,96 +361,20 @@ function PhrasesEditor(props) {
|
|
|
356
361
|
});
|
|
357
362
|
return validationErrors;
|
|
358
363
|
}, [validateRow]);
|
|
359
|
-
|
|
360
|
-
// Column definition for spreadsheet grid
|
|
361
|
-
const initColumns = () => [{
|
|
362
|
-
id: "id",
|
|
363
|
-
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
364
|
-
children: t("row")
|
|
365
|
-
}),
|
|
366
|
-
value: row => {
|
|
367
|
-
return /*#__PURE__*/_jsx(Typography, {
|
|
368
|
-
children: row.id + 1
|
|
369
|
-
});
|
|
370
|
-
},
|
|
371
|
-
width: 10
|
|
372
|
-
}, {
|
|
373
|
-
id: "phrase",
|
|
374
|
-
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
375
|
-
children: t("phrase")
|
|
376
|
-
}),
|
|
377
|
-
value: (row, {
|
|
378
|
-
focus
|
|
379
|
-
}) => {
|
|
380
|
-
const phraseDisplay = [row.phrase || "", ...(row.alternativeVersions || [])].filter(Boolean).join(" | ");
|
|
381
|
-
return /*#__PURE__*/_jsx(Input, {
|
|
382
|
-
value: phraseDisplay,
|
|
383
|
-
placeholder: t("enter_phrase_with_alternatives"),
|
|
384
|
-
focus: focus,
|
|
385
|
-
onChange: onFieldChange(row.id, "phrase")
|
|
386
|
-
});
|
|
387
|
-
},
|
|
388
|
-
width: 30
|
|
389
|
-
}, {
|
|
390
|
-
id: "translations",
|
|
391
|
-
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
392
|
-
children: t("translations")
|
|
393
|
-
}),
|
|
394
|
-
value: (row, {
|
|
395
|
-
focus
|
|
396
|
-
}) => {
|
|
397
|
-
return /*#__PURE__*/_jsx(Input, {
|
|
398
|
-
value: row.translations,
|
|
399
|
-
focus: focus,
|
|
400
|
-
onChange: onFieldChange(row.id, "translations")
|
|
401
|
-
});
|
|
402
|
-
},
|
|
403
|
-
width: 25
|
|
404
|
-
}, {
|
|
405
|
-
id: "definitions",
|
|
406
|
-
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
407
|
-
children: t("definitions")
|
|
408
|
-
}),
|
|
409
|
-
value: (row, {
|
|
410
|
-
focus
|
|
411
|
-
}) => {
|
|
412
|
-
return /*#__PURE__*/_jsx(Input, {
|
|
413
|
-
value: row.definitions,
|
|
414
|
-
focus: focus,
|
|
415
|
-
onChange: onFieldChange(row.id, "definitions")
|
|
416
|
-
});
|
|
417
|
-
},
|
|
418
|
-
width: 25
|
|
419
|
-
}, {
|
|
420
|
-
id: "image",
|
|
421
|
-
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
422
|
-
children: t("image")
|
|
423
|
-
}),
|
|
424
|
-
value: (row, {
|
|
425
|
-
focus
|
|
426
|
-
}) => {
|
|
427
|
-
return /*#__PURE__*/_jsx(Input, {
|
|
428
|
-
value: row.image,
|
|
429
|
-
focus: focus,
|
|
430
|
-
onChange: onFieldChange(row.id, "image")
|
|
431
|
-
});
|
|
432
|
-
},
|
|
433
|
-
width: 30
|
|
434
|
-
}];
|
|
435
364
|
const {
|
|
436
365
|
rows,
|
|
437
|
-
|
|
366
|
+
addRow,
|
|
367
|
+
isDirty,
|
|
368
|
+
hasTrailingEmptyRow,
|
|
438
369
|
selectedRow,
|
|
439
|
-
onCellClick,
|
|
440
|
-
onColumnResize,
|
|
441
370
|
deleteSelectedRow,
|
|
442
|
-
|
|
371
|
+
processRowUpdate
|
|
443
372
|
} = useSpreadsheetState({
|
|
444
373
|
rowTemplate,
|
|
445
374
|
data: spreadsheetPhrases,
|
|
446
|
-
initColumns,
|
|
447
375
|
isRowEmpty
|
|
448
376
|
});
|
|
377
|
+
const hasNonEmptyRows = rows.some(r => !isRowEmpty(r));
|
|
449
378
|
|
|
450
379
|
// Validates rows whenever spreadsheet gets edited
|
|
451
380
|
useEffect(() => {
|
|
@@ -559,11 +488,6 @@ function PhrasesEditor(props) {
|
|
|
559
488
|
mode: mode,
|
|
560
489
|
errors: errors,
|
|
561
490
|
rows: rows,
|
|
562
|
-
columns: columns,
|
|
563
|
-
selectedRow: selectedRow,
|
|
564
|
-
onCellClick: onCellClick,
|
|
565
|
-
onColumnResize: onColumnResize,
|
|
566
|
-
onFieldChange: onFieldChange,
|
|
567
491
|
isRowEmpty: isRowEmpty,
|
|
568
492
|
saveChanges: saveChanges,
|
|
569
493
|
phrases: phrases,
|
|
@@ -592,7 +516,12 @@ function PhrasesEditor(props) {
|
|
|
592
516
|
otherPhraseLists: otherPhraseLists,
|
|
593
517
|
otherPhraseListsLoading: otherPhraseListsLoading,
|
|
594
518
|
handleTransferPhrase: handleTransferPhrase,
|
|
595
|
-
handleCreatePhraseListFromSelection: handleCreatePhraseListFromSelection
|
|
519
|
+
handleCreatePhraseListFromSelection: handleCreatePhraseListFromSelection,
|
|
520
|
+
addRow: addRow,
|
|
521
|
+
isDirty: isDirty,
|
|
522
|
+
hasTrailingEmptyRow: hasTrailingEmptyRow,
|
|
523
|
+
hasNonEmptyRows: hasNonEmptyRows,
|
|
524
|
+
processRowUpdate: processRowUpdate
|
|
596
525
|
}) : /*#__PURE__*/_jsx(EmptyPhraseList, {
|
|
597
526
|
t: t,
|
|
598
527
|
siteLanguage: siteLanguage,
|
|
@@ -1,58 +1,93 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import PropTypes from "prop-types";
|
|
2
3
|
import { Box, Button, Typography } from "@mui/material";
|
|
3
4
|
import { makeStyles } from "tss-react/mui";
|
|
4
|
-
import {
|
|
5
|
+
import { DataGrid } from "@mui/x-data-grid";
|
|
6
|
+
import AddIcon from "@mui/icons-material/Add";
|
|
5
7
|
import ParseScriptErrors from "../../../Lists/ParseScriptErrors/ParseScriptErrors";
|
|
6
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
9
|
const useStyles = makeStyles()(theme => ({
|
|
8
|
-
|
|
9
|
-
marginTop: theme.spacing(),
|
|
10
|
+
dataGridBox: {
|
|
10
11
|
padding: theme.spacing(2)
|
|
11
12
|
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
dataGrid: {
|
|
14
|
+
"& .MuiDataGrid-columnHeader": {
|
|
15
|
+
backgroundColor: theme.palette.primary.main,
|
|
16
|
+
color: theme.palette.primary.contrastText
|
|
17
|
+
},
|
|
18
|
+
"& .MuiDataGrid-columnHeaderCheckbox .MuiSvgIcon-root": {
|
|
19
|
+
color: theme.palette.primary.contrastText
|
|
20
|
+
}
|
|
15
21
|
},
|
|
16
|
-
|
|
17
|
-
display: "flex",
|
|
18
|
-
justifyContent: "space-between",
|
|
19
|
-
alignItems: "center",
|
|
22
|
+
parseErrorsBox: {
|
|
20
23
|
padding: theme.spacing(2),
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
uploadButton: {
|
|
24
|
-
marginRight: theme.spacing()
|
|
24
|
+
paddingTop: 0
|
|
25
25
|
},
|
|
26
26
|
saveChangesBox: {
|
|
27
27
|
paddingLeft: theme.spacing(2),
|
|
28
28
|
paddingBottom: theme.spacing()
|
|
29
29
|
}
|
|
30
30
|
}));
|
|
31
|
-
function SpreadsheetEditor(
|
|
31
|
+
function SpreadsheetEditor({
|
|
32
|
+
t = text => text,
|
|
33
|
+
rows,
|
|
34
|
+
processRowUpdate,
|
|
35
|
+
selectionModel,
|
|
36
|
+
onRowSelectionModelChange,
|
|
37
|
+
errors,
|
|
38
|
+
hasNonEmptyRows,
|
|
39
|
+
saveChanges,
|
|
40
|
+
addRow,
|
|
41
|
+
isDirty,
|
|
42
|
+
hasTrailingEmptyRow
|
|
43
|
+
}) {
|
|
32
44
|
const {
|
|
33
45
|
classes
|
|
34
46
|
} = useStyles();
|
|
35
|
-
const {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
const columns = useMemo(() => [{
|
|
48
|
+
field: "phrase",
|
|
49
|
+
headerName: t("phrase"),
|
|
50
|
+
editable: true,
|
|
51
|
+
flex: 3,
|
|
52
|
+
minWidth: 150
|
|
53
|
+
}, {
|
|
54
|
+
field: "translations",
|
|
55
|
+
headerName: t("translations"),
|
|
56
|
+
editable: true,
|
|
57
|
+
flex: 2.5,
|
|
58
|
+
minWidth: 120
|
|
59
|
+
}, {
|
|
60
|
+
field: "definitions",
|
|
61
|
+
headerName: t("definitions"),
|
|
62
|
+
editable: true,
|
|
63
|
+
flex: 2.5,
|
|
64
|
+
minWidth: 120
|
|
65
|
+
}, {
|
|
66
|
+
field: "image",
|
|
67
|
+
headerName: t("image"),
|
|
68
|
+
editable: true,
|
|
69
|
+
flex: 3,
|
|
70
|
+
minWidth: 120
|
|
71
|
+
}], [t]);
|
|
45
72
|
return /*#__PURE__*/_jsxs(Box, {
|
|
46
73
|
children: [/*#__PURE__*/_jsx(Box, {
|
|
47
|
-
className: classes.
|
|
48
|
-
children: /*#__PURE__*/_jsx(
|
|
49
|
-
|
|
74
|
+
className: classes.dataGridBox,
|
|
75
|
+
children: /*#__PURE__*/_jsx(DataGrid, {
|
|
76
|
+
className: classes.dataGrid,
|
|
50
77
|
rows: rows,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
columns: columns,
|
|
79
|
+
processRowUpdate: processRowUpdate,
|
|
80
|
+
onProcessRowUpdateError: error => console.error(error),
|
|
81
|
+
rowSelectionModel: selectionModel,
|
|
82
|
+
onRowSelectionModelChange: onRowSelectionModelChange,
|
|
83
|
+
autoHeight: true,
|
|
84
|
+
hideFooter: true,
|
|
85
|
+
disableColumnMenu: true,
|
|
86
|
+
disableColumnSorting: true,
|
|
87
|
+
disableRowSelectionOnClick: true,
|
|
88
|
+
checkboxSelection: true,
|
|
89
|
+
showCellVerticalBorder: true,
|
|
90
|
+
showColumnVerticalBorder: true
|
|
56
91
|
})
|
|
57
92
|
}), errors.length > 0 && /*#__PURE__*/_jsxs(Box, {
|
|
58
93
|
className: classes.parseErrorsBox,
|
|
@@ -68,28 +103,40 @@ function SpreadsheetEditor(props) {
|
|
|
68
103
|
t: t,
|
|
69
104
|
errors: errors
|
|
70
105
|
})]
|
|
71
|
-
}), /*#__PURE__*/
|
|
106
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
72
107
|
className: classes.saveChangesBox,
|
|
73
|
-
|
|
108
|
+
sx: {
|
|
109
|
+
display: "flex",
|
|
110
|
+
gap: 1
|
|
111
|
+
},
|
|
112
|
+
children: [/*#__PURE__*/_jsx(Button, {
|
|
113
|
+
variant: "outlined",
|
|
114
|
+
onClick: addRow,
|
|
115
|
+
startIcon: /*#__PURE__*/_jsx(AddIcon, {}),
|
|
116
|
+
disabled: hasTrailingEmptyRow,
|
|
117
|
+
children: t("add_row")
|
|
118
|
+
}), /*#__PURE__*/_jsx(Button, {
|
|
74
119
|
variant: "contained",
|
|
75
120
|
color: "primary",
|
|
76
121
|
onClick: saveChanges,
|
|
77
|
-
disabled: errors.length > 0,
|
|
122
|
+
disabled: errors.length > 0 || !hasNonEmptyRows || !isDirty,
|
|
78
123
|
"data-cy": "save-spreadsheet-changes",
|
|
79
124
|
children: t("save_changes")
|
|
80
|
-
})
|
|
125
|
+
})]
|
|
81
126
|
})]
|
|
82
127
|
});
|
|
83
128
|
}
|
|
84
129
|
SpreadsheetEditor.propTypes = {
|
|
85
|
-
t: PropTypes.func
|
|
130
|
+
t: PropTypes.func,
|
|
86
131
|
rows: PropTypes.array,
|
|
132
|
+
processRowUpdate: PropTypes.func,
|
|
133
|
+
selectionModel: PropTypes.object,
|
|
134
|
+
onRowSelectionModelChange: PropTypes.func,
|
|
87
135
|
errors: PropTypes.array,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
saveChanges: PropTypes.func
|
|
136
|
+
hasNonEmptyRows: PropTypes.bool,
|
|
137
|
+
saveChanges: PropTypes.func,
|
|
138
|
+
addRow: PropTypes.func,
|
|
139
|
+
isDirty: PropTypes.bool,
|
|
140
|
+
hasTrailingEmptyRow: PropTypes.bool
|
|
94
141
|
};
|
|
95
142
|
export default SpreadsheetEditor;
|
|
@@ -141,9 +141,16 @@ export default function MultiStepFormDialog({
|
|
|
141
141
|
const [activeStep, setActiveStep] = useState(0);
|
|
142
142
|
const [skipped, setSkipped] = useState(new Set());
|
|
143
143
|
const steps = getSteps(t);
|
|
144
|
+
|
|
145
|
+
// Only re-sync from parent when dialog opens (not on every re-render)
|
|
146
|
+
// For edit: this picks up the selected discussion data
|
|
147
|
+
// For create: this resets to clean defaults
|
|
144
148
|
useEffect(() => {
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
if (open) {
|
|
150
|
+
setValues(initialValues);
|
|
151
|
+
setActiveStep(0);
|
|
152
|
+
}
|
|
153
|
+
}, [open]);
|
|
147
154
|
const handleGroupAssignmentChange = updatedValues => {
|
|
148
155
|
setValues(updatedValues);
|
|
149
156
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useMemo } from "react";
|
|
2
2
|
import { Link as RouterLink, useNavigate, useLocation } from "react-router";
|
|
3
3
|
import { Joyride, STATUS } from "react-joyride";
|
|
4
|
+
import dayjs from "dayjs";
|
|
4
5
|
import { Typography, Button, Box, Menu, MenuItem, IconButton, Tooltip } from "@mui/material";
|
|
5
6
|
import { useTheme } from "@mui/material/styles";
|
|
6
7
|
import { makeStyles } from "tss-react/mui";
|
|
@@ -1614,7 +1615,9 @@ function Classroom({
|
|
|
1614
1615
|
},
|
|
1615
1616
|
values: {
|
|
1616
1617
|
members: classroomMembers,
|
|
1617
|
-
recordTimeLimit: 1
|
|
1618
|
+
recordTimeLimit: 1,
|
|
1619
|
+
meetingDate: dayjs().add(1, "day").format("MM/DD/YYYY"),
|
|
1620
|
+
meetingTime: dayjs().add(1, "day").format("h:mm A")
|
|
1618
1621
|
},
|
|
1619
1622
|
isDialog: true,
|
|
1620
1623
|
loading: loading,
|
|
@@ -1,83 +1,87 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Custom hook to manage the state and handlers of spreadsheet in react-spreadsheet-grid component
|
|
5
|
-
* @param data
|
|
6
|
-
* @param rowTemplate
|
|
7
|
-
* @param initColumns
|
|
8
|
-
* @param isRowEmpty
|
|
9
|
-
* @returns {{onCellClick: (function(*): void), setRows: (value: (((prevState: *[]) => *[]) | *[])) => void, columns: unknown, onColumnResize: onColumnResize, selectedRow: unknown, rows: *[], onFieldChange: (function(*, *): function(*): void), isRowEmpty, setSelectedRow: (value: unknown) => void, deleteSelectedRow: deleteSelectedRow}}
|
|
10
|
-
*/
|
|
11
2
|
export default function useSpreadsheetState({
|
|
12
3
|
data = [],
|
|
13
4
|
rowTemplate,
|
|
14
|
-
initColumns,
|
|
15
5
|
isRowEmpty
|
|
16
6
|
}) {
|
|
7
|
+
// Converts a raw phrase row (arrays for translations/definitions, separate
|
|
8
|
+
// alternativeVersions) into the flat string format used by the spreadsheet.
|
|
17
9
|
const formatRows = rows => {
|
|
18
10
|
return rows.map((r, index) => {
|
|
19
|
-
const translationsArray = Array.isArray(r.translations) ? r.translations : r.translations.split("|").map(t => t.trim());
|
|
11
|
+
const translationsArray = Array.isArray(r.translations) ? r.translations : (r.translations || "").split("|").map(t => t.trim());
|
|
12
|
+
const definitionsArray = Array.isArray(r.definitions) ? r.definitions : (r.definitions || "").split("|").map(d => d.trim());
|
|
13
|
+
|
|
14
|
+
// Combine phrase + alternativeVersions into one pipe-separated string for
|
|
15
|
+
// display and editing. formatRowsToPhrases splits them back on save.
|
|
16
|
+
const phraseDisplay = [r.phrase || "", ...(r.alternativeVersions || [])].filter(Boolean).join(" | ");
|
|
20
17
|
return {
|
|
21
18
|
...r,
|
|
22
19
|
id: index,
|
|
23
|
-
|
|
20
|
+
phrase: phraseDisplay,
|
|
21
|
+
translations: translationsArray.join(" | "),
|
|
22
|
+
definitions: definitionsArray.join(" | ")
|
|
24
23
|
};
|
|
25
24
|
});
|
|
26
25
|
};
|
|
27
26
|
const [rows, setRows] = useState([]);
|
|
28
|
-
const [
|
|
29
|
-
const [selectedRow, setSelectedRow] = useState(undefined);
|
|
30
|
-
const onColumnResize = widthValues => {
|
|
31
|
-
const newColumns = [].concat(columns);
|
|
32
|
-
Object.keys(widthValues).forEach(columnId => {
|
|
33
|
-
const foundIndex = newColumns.findIndex(col => col.id === columnId);
|
|
34
|
-
newColumns[foundIndex].width = widthValues[columnId];
|
|
35
|
-
});
|
|
36
|
-
setColumns(newColumns);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// stringified to detect changes in nested objects
|
|
27
|
+
const [isDirty, setIsDirty] = useState(false);
|
|
40
28
|
const dataStringified = JSON.stringify(data);
|
|
41
29
|
useEffect(() => {
|
|
42
|
-
const
|
|
43
|
-
setRows(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
setRows(prevState => {
|
|
47
|
-
const newRows = [...prevState];
|
|
48
|
-
// Find the row that is being changed
|
|
49
|
-
const row = newRows.find(({
|
|
50
|
-
id
|
|
51
|
-
}) => id === rowId);
|
|
30
|
+
const formatted = formatRows(JSON.parse(dataStringified));
|
|
31
|
+
setRows(formatted);
|
|
32
|
+
setIsDirty(false);
|
|
33
|
+
}, [dataStringified]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
// Called by DataGrid after a cell edit is committed. Returns the updated row
|
|
36
|
+
// so DataGrid can update its own display, and appends an empty trailing row
|
|
37
|
+
// whenever the previous last row now has content.
|
|
38
|
+
const processRowUpdate = newRow => {
|
|
39
|
+
setRows(prevRows => {
|
|
40
|
+
const updated = prevRows.map(r => r.id === newRow.id ? newRow : r);
|
|
41
|
+
if (!isRowEmpty(updated[updated.length - 1])) {
|
|
42
|
+
updated.push({
|
|
43
|
+
...rowTemplate,
|
|
44
|
+
id: updated.length,
|
|
45
|
+
translations: "",
|
|
46
|
+
definitions: ""
|
|
59
47
|
});
|
|
60
48
|
}
|
|
61
|
-
return
|
|
49
|
+
return updated;
|
|
62
50
|
});
|
|
51
|
+
setIsDirty(true);
|
|
52
|
+
return newRow;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Removes rows whose ids are in selectedIds, re-indexes the remainder,
|
|
56
|
+
// and returns the new rows so callers can save immediately.
|
|
57
|
+
const deleteRows = selectedIds => {
|
|
58
|
+
const filtered = rows.filter(r => !selectedIds.has(r.id));
|
|
59
|
+
const newRows = formatRows(filtered);
|
|
60
|
+
setRows(newRows);
|
|
61
|
+
setIsDirty(true);
|
|
62
|
+
return newRows;
|
|
63
63
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
|
|
65
|
+
// Explicitly appends a new empty row regardless of current state.
|
|
66
|
+
const addRow = () => {
|
|
67
|
+
setRows(prevRows => {
|
|
68
|
+
const newRows = [...prevRows, {
|
|
69
|
+
...rowTemplate,
|
|
70
|
+
id: prevRows.length,
|
|
71
|
+
translations: "",
|
|
72
|
+
definitions: ""
|
|
73
|
+
}];
|
|
74
|
+
return newRows;
|
|
69
75
|
});
|
|
76
|
+
setIsDirty(true);
|
|
70
77
|
};
|
|
78
|
+
const hasTrailingEmptyRow = rows.length > 0 && isRowEmpty(rows[rows.length - 1]);
|
|
71
79
|
return {
|
|
72
80
|
rows,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
onColumnResize,
|
|
79
|
-
onFieldChange,
|
|
80
|
-
onCellClick,
|
|
81
|
-
isRowEmpty
|
|
81
|
+
processRowUpdate,
|
|
82
|
+
deleteRows,
|
|
83
|
+
addRow,
|
|
84
|
+
isDirty,
|
|
85
|
+
hasTrailingEmptyRow
|
|
82
86
|
};
|
|
83
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nualang/nualang-ui-components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1365",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"@emotion/styled": "^11.13",
|
|
21
21
|
"@hookform/resolvers": "^5.2.2",
|
|
22
22
|
"@json2csv/plainjs": "^7.0.1",
|
|
23
|
+
"@mui/x-data-grid": "^9.0.1",
|
|
23
24
|
"@nualang/avatars": "2.0.3",
|
|
24
25
|
"@nualang/rivescript": "^2.2.2-alpha1",
|
|
25
26
|
"axios": "^1.14.0",
|
|
@@ -76,7 +77,6 @@
|
|
|
76
77
|
"react-qrcode-logo": "^4.0.0",
|
|
77
78
|
"react-refresh": "^0.18.0",
|
|
78
79
|
"react-simplemde-editor": "5.2.0",
|
|
79
|
-
"react-spreadsheet-grid": "^2.3.1",
|
|
80
80
|
"react-swipeable-views": "^0.14.0",
|
|
81
81
|
"rehype-raw": "^7.0.0",
|
|
82
82
|
"socket.io-client": "^4.7.1",
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Typography } from "@mui/material";
|
|
3
|
-
import { Grid, Input } from "react-spreadsheet-grid";
|
|
4
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
-
const initRows = [{
|
|
6
|
-
id: 0,
|
|
7
|
-
phrase: "hello",
|
|
8
|
-
translations: "hola"
|
|
9
|
-
}, {
|
|
10
|
-
id: 1,
|
|
11
|
-
phrase: "dog",
|
|
12
|
-
translations: "madra"
|
|
13
|
-
}];
|
|
14
|
-
export default function SpreadsheetGrid({
|
|
15
|
-
t = text => text
|
|
16
|
-
}) {
|
|
17
|
-
// const [rows, setRows] = useState([]);
|
|
18
|
-
const [rows, setRows] = useState(initRows);
|
|
19
|
-
|
|
20
|
-
// A callback called every time a value changed.
|
|
21
|
-
// Every time it save a new value to the state.
|
|
22
|
-
const onFieldChange = (rowId, field) => value => {
|
|
23
|
-
// Find the row that is being changed
|
|
24
|
-
// const row = rows.find({ id } => id === rowId);
|
|
25
|
-
const row = rows.find(({
|
|
26
|
-
id
|
|
27
|
-
}) => id === rowId);
|
|
28
|
-
|
|
29
|
-
// Change a value of a field
|
|
30
|
-
row[field] = value;
|
|
31
|
-
setRows([].concat(rows));
|
|
32
|
-
};
|
|
33
|
-
const initColumns = () => [{
|
|
34
|
-
id: "phrase",
|
|
35
|
-
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
36
|
-
children: t("phrase")
|
|
37
|
-
}),
|
|
38
|
-
value: (row, {
|
|
39
|
-
focus
|
|
40
|
-
}) => {
|
|
41
|
-
return /*#__PURE__*/_jsx(Input, {
|
|
42
|
-
value: row.phrase,
|
|
43
|
-
focus: focus,
|
|
44
|
-
onChange: onFieldChange(row.id, "phrase")
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
width: 50
|
|
48
|
-
}, {
|
|
49
|
-
id: "translations",
|
|
50
|
-
title: () => /*#__PURE__*/_jsx(Typography, {
|
|
51
|
-
children: t("translations")
|
|
52
|
-
}),
|
|
53
|
-
value: (row, {
|
|
54
|
-
focus
|
|
55
|
-
}) => {
|
|
56
|
-
return /*#__PURE__*/_jsx(Input, {
|
|
57
|
-
value: row.translations,
|
|
58
|
-
focus: focus,
|
|
59
|
-
onChange: onFieldChange(row.id, "translations")
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
width: 50
|
|
63
|
-
}];
|
|
64
|
-
const [columns, setColumns] = useState(initColumns());
|
|
65
|
-
|
|
66
|
-
// Change columns width values in the state to not lose them.
|
|
67
|
-
const onColumnResize = widthValues => {
|
|
68
|
-
const newColumns = [].concat(columns);
|
|
69
|
-
Object.keys(widthValues).forEach(columnId => {
|
|
70
|
-
const foundIndex = newColumns.findIndex(col => col.id === columnId);
|
|
71
|
-
newColumns[foundIndex].width = widthValues[columnId];
|
|
72
|
-
});
|
|
73
|
-
setColumns(newColumns);
|
|
74
|
-
};
|
|
75
|
-
return /*#__PURE__*/_jsx(Grid, {
|
|
76
|
-
columns: columns,
|
|
77
|
-
rows: rows,
|
|
78
|
-
isColumnsResizable: true,
|
|
79
|
-
onColumnResize: onColumnResize,
|
|
80
|
-
getRowKey: row => row.id
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
SpreadsheetGrid.propTypes = {
|
|
84
|
-
// t: PropTypes.func.isRequired,
|
|
85
|
-
// initColumns: PropTypes.func.isRequired;
|
|
86
|
-
// onFieldChange: PropTypes.func.isRequired;
|
|
87
|
-
// onColumnResize: PropTypes.func.isRequired
|
|
88
|
-
};
|