@teselagen/ui 0.8.6-beta.22 → 0.8.6-beta.24
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/DataTable/EditabelCell.d.ts +7 -0
- package/DataTable/defaultProps.d.ts +43 -0
- package/DataTable/utils/computePresets.d.ts +1 -0
- package/DataTable/utils/convertSchema.d.ts +22 -2
- package/DataTable/utils/getAllRows.d.ts +1 -1
- package/DataTable/utils/handleCopyColumn.d.ts +1 -1
- package/DataTable/utils/handleCopyTable.d.ts +1 -1
- package/DataTable/utils/isBottomRightCornerOfRectangle.d.ts +10 -8
- package/DataTable/utils/isEntityClean.d.ts +3 -1
- package/DataTable/utils/primarySelectedValue.d.ts +1 -1
- package/DataTable/utils/removeCleanRows.d.ts +11 -3
- package/DataTable/utils/selection.d.ts +3 -1
- package/DataTable/utils/useDeepEqualMemo.d.ts +1 -0
- package/DataTable/utils/useTableEntities.d.ts +17 -4
- package/DataTable/utils/useTableParams.d.ts +49 -0
- package/index.cjs.js +125 -109
- package/index.es.js +125 -109
- package/package.json +2 -2
- package/src/DataTable/Columns.jsx +945 -0
- package/src/DataTable/EditabelCell.js +44 -0
- package/src/DataTable/EditabelCell.jsx +44 -0
- package/src/DataTable/RenderCell.jsx +191 -0
- package/src/DataTable/defaultProps.js +45 -0
- package/src/DataTable/index.js +96 -68
- package/src/DataTable/utils/computePresets.js +42 -0
- package/src/DataTable/utils/convertSchema.ts +79 -0
- package/src/DataTable/utils/getAllRows.js +2 -6
- package/src/DataTable/utils/handleCopyColumn.js +2 -2
- package/src/DataTable/utils/handleCopyTable.js +2 -2
- package/src/DataTable/utils/isBottomRightCornerOfRectangle.ts +27 -0
- package/src/DataTable/utils/isEntityClean.ts +15 -0
- package/src/DataTable/utils/primarySelectedValue.ts +1 -0
- package/src/DataTable/utils/queryParams.js +1 -1
- package/src/DataTable/utils/removeCleanRows.ts +25 -0
- package/src/DataTable/utils/selection.ts +11 -0
- package/src/DataTable/utils/useDeepEqualMemo.js +10 -0
- package/src/DataTable/utils/useTableEntities.ts +60 -0
- package/src/DataTable/utils/useTableParams.js +361 -0
- package/style.css +10537 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export const EditableCell = ({
|
|
4
|
+
cancelEdit,
|
|
5
|
+
dataTest,
|
|
6
|
+
finishEdit,
|
|
7
|
+
isNumeric,
|
|
8
|
+
initialValue
|
|
9
|
+
}) => {
|
|
10
|
+
const [value, setValue] = useState(initialValue);
|
|
11
|
+
const inputRef = useRef(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (inputRef.current) {
|
|
15
|
+
inputRef.current.focus();
|
|
16
|
+
}
|
|
17
|
+
}, [isNumeric]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<input
|
|
21
|
+
style={{
|
|
22
|
+
border: 0,
|
|
23
|
+
width: "95%",
|
|
24
|
+
fontSize: 12,
|
|
25
|
+
background: "none"
|
|
26
|
+
}}
|
|
27
|
+
ref={inputRef}
|
|
28
|
+
{...dataTest}
|
|
29
|
+
autoFocus
|
|
30
|
+
onKeyDown={e => {
|
|
31
|
+
e.stopPropagation();
|
|
32
|
+
if (e.key === "Enter") {
|
|
33
|
+
e.target.blur();
|
|
34
|
+
} else if (e.key === "Escape") {
|
|
35
|
+
cancelEdit();
|
|
36
|
+
}
|
|
37
|
+
}}
|
|
38
|
+
onBlur={() => finishEdit(value)}
|
|
39
|
+
onChange={e => setValue(e.target.value)}
|
|
40
|
+
type={isNumeric ? "number" : undefined}
|
|
41
|
+
value={value}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export const EditableCell = ({
|
|
4
|
+
cancelEdit,
|
|
5
|
+
dataTest,
|
|
6
|
+
finishEdit,
|
|
7
|
+
isNumeric,
|
|
8
|
+
initialValue
|
|
9
|
+
}) => {
|
|
10
|
+
const [value, setValue] = useState(initialValue);
|
|
11
|
+
const inputRef = useRef(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (inputRef.current) {
|
|
15
|
+
inputRef.current.focus();
|
|
16
|
+
}
|
|
17
|
+
}, [isNumeric]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<input
|
|
21
|
+
style={{
|
|
22
|
+
border: 0,
|
|
23
|
+
width: "95%",
|
|
24
|
+
fontSize: 12,
|
|
25
|
+
background: "none"
|
|
26
|
+
}}
|
|
27
|
+
ref={inputRef}
|
|
28
|
+
{...dataTest}
|
|
29
|
+
autoFocus
|
|
30
|
+
onKeyDown={e => {
|
|
31
|
+
e.stopPropagation();
|
|
32
|
+
if (e.key === "Enter") {
|
|
33
|
+
e.target.blur();
|
|
34
|
+
} else if (e.key === "Escape") {
|
|
35
|
+
cancelEdit();
|
|
36
|
+
}
|
|
37
|
+
}}
|
|
38
|
+
onBlur={() => finishEdit(value)}
|
|
39
|
+
onChange={e => setValue(e.target.value)}
|
|
40
|
+
type={isNumeric ? "number" : undefined}
|
|
41
|
+
value={value}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useSelector } from "react-redux";
|
|
3
|
+
import { Checkbox, Icon } from "@blueprintjs/core";
|
|
4
|
+
import {
|
|
5
|
+
getIdOrCodeOrIndex,
|
|
6
|
+
isBottomRightCornerOfRectangle,
|
|
7
|
+
PRIMARY_SELECTED_VAL
|
|
8
|
+
} from "./utils";
|
|
9
|
+
import { DropdownCell } from "./DropdownCell";
|
|
10
|
+
import { EditableCell } from "./EditabelCell";
|
|
11
|
+
import { getVals } from "./getVals";
|
|
12
|
+
import { CellDragHandle } from "./CellDragHandle";
|
|
13
|
+
|
|
14
|
+
export const RenderCell = ({
|
|
15
|
+
oldFunc,
|
|
16
|
+
getCopyTextForCell,
|
|
17
|
+
column,
|
|
18
|
+
isCellEditable,
|
|
19
|
+
isEntityDisabled,
|
|
20
|
+
finishCellEdit,
|
|
21
|
+
formName,
|
|
22
|
+
noEllipsis,
|
|
23
|
+
cancelCellEdit,
|
|
24
|
+
getCellHoverText,
|
|
25
|
+
selectedCells,
|
|
26
|
+
isSelectionARectangle,
|
|
27
|
+
startCellEdit,
|
|
28
|
+
tableRef,
|
|
29
|
+
onDragEnd,
|
|
30
|
+
args
|
|
31
|
+
}) => {
|
|
32
|
+
const editingCell = useSelector(
|
|
33
|
+
state => state.form?.[formName]?.values?.reduxFormEditingCell
|
|
34
|
+
);
|
|
35
|
+
const initialValue = useSelector(
|
|
36
|
+
state => state.form?.[formName]?.values?.reduxFormInitialValue
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const [row] = args;
|
|
40
|
+
const rowId = getIdOrCodeOrIndex(row.original, row.index);
|
|
41
|
+
const cellId = `${rowId}:${row.column.path}`;
|
|
42
|
+
const isEditingCell = editingCell === cellId;
|
|
43
|
+
let val = oldFunc(...args);
|
|
44
|
+
const oldVal = val;
|
|
45
|
+
const text = getCopyTextForCell(val, row, column);
|
|
46
|
+
const dataTest = {
|
|
47
|
+
"data-test": "tgCell_" + column.path
|
|
48
|
+
};
|
|
49
|
+
const fullValue = row.original?.[row.column.path];
|
|
50
|
+
|
|
51
|
+
if (isEditingCell) {
|
|
52
|
+
if (column.type === "genericSelect") {
|
|
53
|
+
const GenericSelectComp = column.GenericSelectComp;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<GenericSelectComp
|
|
57
|
+
rowId={rowId}
|
|
58
|
+
fullValue={fullValue}
|
|
59
|
+
initialValue={text}
|
|
60
|
+
{...dataTest}
|
|
61
|
+
finishEdit={(newVal, doNotStopEditing) => {
|
|
62
|
+
finishCellEdit(cellId, newVal, doNotStopEditing);
|
|
63
|
+
}}
|
|
64
|
+
dataTest={dataTest}
|
|
65
|
+
cancelEdit={cancelCellEdit}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (column.type === "dropdown" || column.type === "dropdownMulti") {
|
|
70
|
+
return (
|
|
71
|
+
<DropdownCell
|
|
72
|
+
isMulti={dataTest.isMulti || column.type === "dropdownMulti"}
|
|
73
|
+
initialValue={dataTest.initialValue || text}
|
|
74
|
+
options={getVals(column.values)}
|
|
75
|
+
finishEdit={(newVal, doNotStopEditing) => {
|
|
76
|
+
finishCellEdit(cellId, newVal, doNotStopEditing);
|
|
77
|
+
}}
|
|
78
|
+
dataTest={dataTest}
|
|
79
|
+
cancelEdit={cancelCellEdit}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
return (
|
|
84
|
+
<EditableCell
|
|
85
|
+
dataTest={dataTest}
|
|
86
|
+
cancelEdit={cancelCellEdit}
|
|
87
|
+
isNumeric={column.type === "number"}
|
|
88
|
+
initialValue={initialValue || text}
|
|
89
|
+
finishEdit={newVal => {
|
|
90
|
+
finishCellEdit(cellId, newVal);
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const isBool = column.type === "boolean";
|
|
98
|
+
if (isCellEditable && isBool) {
|
|
99
|
+
val = (
|
|
100
|
+
<Checkbox
|
|
101
|
+
disabled={isEntityDisabled(row.original)}
|
|
102
|
+
className="tg-cell-edit-boolean-checkbox"
|
|
103
|
+
checked={oldVal === "True"}
|
|
104
|
+
onChange={e => {
|
|
105
|
+
const checked = e.target.checked;
|
|
106
|
+
finishCellEdit(cellId, checked);
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
noEllipsis = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//wrap the original tableColumn.Cell function in another div in order to add a title attribute
|
|
114
|
+
let title = text;
|
|
115
|
+
if (getCellHoverText) title = getCellHoverText(...args);
|
|
116
|
+
else if (column.getTitleAttr) title = column.getTitleAttr(...args);
|
|
117
|
+
const isSelectedCell = selectedCells?.[cellId];
|
|
118
|
+
const {
|
|
119
|
+
isRect,
|
|
120
|
+
selectionGrid,
|
|
121
|
+
lastRowIndex,
|
|
122
|
+
lastCellIndex,
|
|
123
|
+
entityMap,
|
|
124
|
+
pathToIndex
|
|
125
|
+
} = isSelectionARectangle();
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<>
|
|
129
|
+
<div
|
|
130
|
+
style={{
|
|
131
|
+
...(!noEllipsis && {
|
|
132
|
+
textOverflow: "ellipsis",
|
|
133
|
+
overflow: "hidden"
|
|
134
|
+
})
|
|
135
|
+
}}
|
|
136
|
+
{...dataTest}
|
|
137
|
+
className="tg-cell-wrapper"
|
|
138
|
+
data-copy-text={text}
|
|
139
|
+
data-copy-json={JSON.stringify(
|
|
140
|
+
//tnw: eventually we'll parse these back out and use either the fullValue (for the generic selects) or the regular text vals for everything else
|
|
141
|
+
column.type === "genericSelect"
|
|
142
|
+
? {
|
|
143
|
+
__strVal: fullValue,
|
|
144
|
+
__genSelCol: column.path
|
|
145
|
+
}
|
|
146
|
+
: { __strVal: text }
|
|
147
|
+
)}
|
|
148
|
+
title={title || undefined}
|
|
149
|
+
>
|
|
150
|
+
{val}
|
|
151
|
+
</div>
|
|
152
|
+
{isCellEditable &&
|
|
153
|
+
(column.type === "dropdown" ||
|
|
154
|
+
column.type === "dropdownMulti" ||
|
|
155
|
+
column.type === "genericSelect") && (
|
|
156
|
+
<Icon
|
|
157
|
+
icon="caret-down"
|
|
158
|
+
style={{
|
|
159
|
+
position: "absolute",
|
|
160
|
+
right: 5,
|
|
161
|
+
opacity: 0.3
|
|
162
|
+
}}
|
|
163
|
+
className="cell-edit-dropdown"
|
|
164
|
+
onClick={() => {
|
|
165
|
+
startCellEdit(cellId);
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{isSelectedCell &&
|
|
171
|
+
(isRect
|
|
172
|
+
? isBottomRightCornerOfRectangle({
|
|
173
|
+
cellId,
|
|
174
|
+
selectionGrid,
|
|
175
|
+
lastRowIndex,
|
|
176
|
+
lastCellIndex,
|
|
177
|
+
entityMap,
|
|
178
|
+
pathToIndex
|
|
179
|
+
})
|
|
180
|
+
: isSelectedCell === PRIMARY_SELECTED_VAL) && (
|
|
181
|
+
<CellDragHandle
|
|
182
|
+
key={cellId}
|
|
183
|
+
thisTable={tableRef.current.tableRef}
|
|
184
|
+
cellId={cellId}
|
|
185
|
+
isSelectionARectangle={isSelectionARectangle}
|
|
186
|
+
onDragEnd={onDragEnd}
|
|
187
|
+
/>
|
|
188
|
+
)}
|
|
189
|
+
</>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { noop } from "lodash-es";
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line import/no-anonymous-default-export
|
|
4
|
+
export default {
|
|
5
|
+
//NOTE: DO NOT SET DEFAULTS HERE FOR PROPS THAT GET COMPUTED AS PART OF PRESET GROUPS IN computePresets
|
|
6
|
+
addFilters: noop,
|
|
7
|
+
className: "",
|
|
8
|
+
clearFilters: noop,
|
|
9
|
+
contextMenu: noop,
|
|
10
|
+
disabled: false,
|
|
11
|
+
entities: [],
|
|
12
|
+
extraClasses: "",
|
|
13
|
+
filters: [],
|
|
14
|
+
isCopyable: true,
|
|
15
|
+
isEntityDisabled: noop,
|
|
16
|
+
isLoading: false,
|
|
17
|
+
isSimple: false,
|
|
18
|
+
isSingleSelect: false,
|
|
19
|
+
maxHeight: 600,
|
|
20
|
+
noHeader: false,
|
|
21
|
+
noSelect: false,
|
|
22
|
+
noUserSelect: false,
|
|
23
|
+
onDeselect: noop,
|
|
24
|
+
onMultiRowSelect: noop,
|
|
25
|
+
onRowClick: noop,
|
|
26
|
+
onRowSelect: noop,
|
|
27
|
+
onSingleRowSelect: noop,
|
|
28
|
+
page: 1,
|
|
29
|
+
pageSize: 10,
|
|
30
|
+
reduxFormExpandedEntityIdMap: {},
|
|
31
|
+
reduxFormSearchInput: "",
|
|
32
|
+
reduxFormSelectedEntityIdMap: {},
|
|
33
|
+
removeSingleFilter: noop,
|
|
34
|
+
resized: [],
|
|
35
|
+
resizePersist: noop,
|
|
36
|
+
setFilter: noop,
|
|
37
|
+
setOrder: noop,
|
|
38
|
+
setPage: noop,
|
|
39
|
+
setPageSize: noop,
|
|
40
|
+
setSearchTerm: noop,
|
|
41
|
+
showCount: false,
|
|
42
|
+
style: {},
|
|
43
|
+
withCheckboxes: false,
|
|
44
|
+
withSort: true
|
|
45
|
+
};
|
package/src/DataTable/index.js
CHANGED
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
import { arrayMove } from "@dnd-kit/sortable";
|
|
44
44
|
import classNames from "classnames";
|
|
45
45
|
import scrollIntoView from "dom-scroll-into-view";
|
|
46
|
-
import ReactTable from "@teselagen/react-table";
|
|
46
|
+
import ReactTable, { VIRTUALIZE_CUTOFF_LENGTH } from "@teselagen/react-table";
|
|
47
47
|
import immer, { produceWithPatches, enablePatches, applyPatches } from "immer";
|
|
48
48
|
import papaparse from "papaparse";
|
|
49
49
|
import { useDispatch, useSelector } from "react-redux";
|
|
@@ -141,6 +141,7 @@ const DataTable = ({
|
|
|
141
141
|
);
|
|
142
142
|
const tableRef = useRef();
|
|
143
143
|
const alreadySelected = useRef(false);
|
|
144
|
+
const [noVirtual, setNoVirtual] = useState(false);
|
|
144
145
|
const [onlyShowRowsWErrors, setOnlyShowRowsWErrors] = useState(false);
|
|
145
146
|
const [entitiesUndoRedoStack, setEntitiesUndoRedoStack] = useState({
|
|
146
147
|
currentVersion: 0
|
|
@@ -1145,70 +1146,94 @@ const DataTable = ({
|
|
|
1145
1146
|
updateValidation
|
|
1146
1147
|
]);
|
|
1147
1148
|
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
let firstRowIndex;
|
|
1157
|
-
let firstCellIndex;
|
|
1158
|
-
Object.keys(selectedCells).forEach(key => {
|
|
1159
|
-
const [rowId, path] = key.split(":");
|
|
1160
|
-
const eInfo = entityIdToEntity[rowId];
|
|
1161
|
-
if (eInfo) {
|
|
1162
|
-
if (firstRowIndex === undefined || eInfo.i < firstRowIndex) {
|
|
1163
|
-
firstRowIndex = eInfo.i;
|
|
1164
|
-
}
|
|
1165
|
-
if (!selectionGrid[eInfo.i]) {
|
|
1166
|
-
selectionGrid[eInfo.i] = [];
|
|
1167
|
-
}
|
|
1168
|
-
const cellIndex = pathToIndex[path];
|
|
1169
|
-
if (firstCellIndex === undefined || cellIndex < firstCellIndex) {
|
|
1170
|
-
firstCellIndex = cellIndex;
|
|
1171
|
-
}
|
|
1172
|
-
selectionGrid[eInfo.i][cellIndex] = true;
|
|
1149
|
+
const waitUntilAllRowsAreRendered = useCallback(() => {
|
|
1150
|
+
return new Promise(resolve => {
|
|
1151
|
+
const interval = setInterval(() => {
|
|
1152
|
+
const allRowEls =
|
|
1153
|
+
tableRef.current?.tableRef?.querySelectorAll(".rt-tr-group");
|
|
1154
|
+
if (allRowEls?.length === entities.length) {
|
|
1155
|
+
clearInterval(interval);
|
|
1156
|
+
resolve();
|
|
1173
1157
|
}
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1158
|
+
}, 50);
|
|
1159
|
+
});
|
|
1160
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1161
|
+
}, []);
|
|
1162
|
+
|
|
1163
|
+
const handleCopySelectedCells = useCallback(async () => {
|
|
1164
|
+
// if the current selection is consecutive cells then copy with
|
|
1165
|
+
// tabs between. if not then just select primary selected cell
|
|
1166
|
+
if (isEmpty(selectedCells)) return;
|
|
1167
|
+
|
|
1168
|
+
// Temporarily disable virtualization for large tables
|
|
1169
|
+
if (entities.length > VIRTUALIZE_CUTOFF_LENGTH) {
|
|
1170
|
+
setNoVirtual(true);
|
|
1171
|
+
await waitUntilAllRowsAreRendered();
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
1175
|
+
const entityIdToEntity = getEntityIdToEntity(entities);
|
|
1176
|
+
const selectionGrid = [];
|
|
1177
|
+
let firstRowIndex;
|
|
1178
|
+
let firstCellIndex;
|
|
1179
|
+
Object.keys(selectedCells).forEach(key => {
|
|
1180
|
+
const [rowId, path] = key.split(":");
|
|
1181
|
+
const eInfo = entityIdToEntity[rowId];
|
|
1182
|
+
if (eInfo) {
|
|
1183
|
+
if (firstRowIndex === undefined || eInfo.i < firstRowIndex) {
|
|
1184
|
+
firstRowIndex = eInfo.i;
|
|
1183
1185
|
}
|
|
1184
|
-
if (!
|
|
1185
|
-
|
|
1186
|
-
} else {
|
|
1187
|
-
const jsonRow = [];
|
|
1188
|
-
// ignore header
|
|
1189
|
-
let [rowCopyText, json] = getRowCopyText(allRows[i + 1]);
|
|
1190
|
-
rowCopyText = rowCopyText.split("\t");
|
|
1191
|
-
times(row.length, i => {
|
|
1192
|
-
const cell = row[i];
|
|
1193
|
-
if (cell) {
|
|
1194
|
-
fullCellText += rowCopyText[i];
|
|
1195
|
-
jsonRow.push(json[i]);
|
|
1196
|
-
}
|
|
1197
|
-
if (i !== row.length - 1 && i >= firstCellIndex)
|
|
1198
|
-
fullCellText += "\t";
|
|
1199
|
-
});
|
|
1200
|
-
fullJson.push(jsonRow);
|
|
1186
|
+
if (!selectionGrid[eInfo.i]) {
|
|
1187
|
+
selectionGrid[eInfo.i] = [];
|
|
1201
1188
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1189
|
+
const cellIndex = pathToIndex[path];
|
|
1190
|
+
if (firstCellIndex === undefined || cellIndex < firstCellIndex) {
|
|
1191
|
+
firstCellIndex = cellIndex;
|
|
1192
|
+
}
|
|
1193
|
+
selectionGrid[eInfo.i][cellIndex] = true;
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
if (firstRowIndex === undefined) return;
|
|
1197
|
+
const allRows = getAllRows(tableRef);
|
|
1198
|
+
let fullCellText = "";
|
|
1199
|
+
const fullJson = [];
|
|
1200
|
+
times(selectionGrid.length, i => {
|
|
1201
|
+
const row = selectionGrid[i];
|
|
1202
|
+
if (fullCellText) {
|
|
1203
|
+
fullCellText += "\n";
|
|
1204
|
+
}
|
|
1205
|
+
if (!row) {
|
|
1206
|
+
return;
|
|
1207
|
+
} else {
|
|
1208
|
+
const jsonRow = [];
|
|
1209
|
+
// ignore header
|
|
1210
|
+
let [rowCopyText, json] = getRowCopyText(allRows[i + 1]);
|
|
1211
|
+
rowCopyText = rowCopyText.split("\t");
|
|
1212
|
+
times(row.length, i => {
|
|
1213
|
+
const cell = row[i];
|
|
1214
|
+
if (cell) {
|
|
1215
|
+
fullCellText += rowCopyText[i];
|
|
1216
|
+
jsonRow.push(json[i]);
|
|
1217
|
+
}
|
|
1218
|
+
if (i !== row.length - 1 && i >= firstCellIndex) fullCellText += "\t";
|
|
1219
|
+
});
|
|
1220
|
+
fullJson.push(jsonRow);
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
if (!fullCellText) return window.toastr.warning("No text to copy");
|
|
1204
1224
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1225
|
+
handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
|
|
1226
|
+
|
|
1227
|
+
// Re-enable virtualization if it was disabled
|
|
1228
|
+
setNoVirtual(false);
|
|
1229
|
+
}, [entities, selectedCells, schema, waitUntilAllRowsAreRendered]);
|
|
1209
1230
|
|
|
1210
1231
|
const handleCopySelectedRows = useCallback(
|
|
1211
|
-
|
|
1232
|
+
async selectedRecords => {
|
|
1233
|
+
if (entities.length > VIRTUALIZE_CUTOFF_LENGTH) {
|
|
1234
|
+
setNoVirtual(true);
|
|
1235
|
+
await waitUntilAllRowsAreRendered();
|
|
1236
|
+
}
|
|
1212
1237
|
const idToIndex = entities.reduce((acc, e, i) => {
|
|
1213
1238
|
acc[e.id || e.code] = i;
|
|
1214
1239
|
return acc;
|
|
@@ -1223,10 +1248,10 @@ const DataTable = ({
|
|
|
1223
1248
|
if (!rowNumbersToCopy.length) return;
|
|
1224
1249
|
rowNumbersToCopy.unshift(0); //add in the header row
|
|
1225
1250
|
try {
|
|
1226
|
-
const allRowEls = getAllRows(
|
|
1251
|
+
const allRowEls = getAllRows(tableRef);
|
|
1227
1252
|
if (!allRowEls) return;
|
|
1228
1253
|
const rowEls = rowNumbersToCopy.map(i => allRowEls[i]);
|
|
1229
|
-
|
|
1254
|
+
if (window.Cypress) window.Cypress.__copiedRowsLength = rowEls.length;
|
|
1230
1255
|
handleCopyRows(rowEls, {
|
|
1231
1256
|
onFinishMsg: "Selected rows copied"
|
|
1232
1257
|
});
|
|
@@ -1234,8 +1259,9 @@ const DataTable = ({
|
|
|
1234
1259
|
console.error(`error:`, error);
|
|
1235
1260
|
window.toastr.error("Error copying rows.");
|
|
1236
1261
|
}
|
|
1262
|
+
setNoVirtual(false);
|
|
1237
1263
|
},
|
|
1238
|
-
[entities]
|
|
1264
|
+
[entities, waitUntilAllRowsAreRendered]
|
|
1239
1265
|
);
|
|
1240
1266
|
|
|
1241
1267
|
const handleCopyHotkey = useCallback(
|
|
@@ -2096,7 +2122,7 @@ const DataTable = ({
|
|
|
2096
2122
|
<MenuItem
|
|
2097
2123
|
key="copyColumn"
|
|
2098
2124
|
onClick={() => {
|
|
2099
|
-
handleCopyColumn(
|
|
2125
|
+
handleCopyColumn(tableRef, cellWrapper);
|
|
2100
2126
|
}}
|
|
2101
2127
|
text="Column"
|
|
2102
2128
|
/>
|
|
@@ -2106,7 +2132,7 @@ const DataTable = ({
|
|
|
2106
2132
|
<MenuItem
|
|
2107
2133
|
key="copyColumnSelected"
|
|
2108
2134
|
onClick={() => {
|
|
2109
|
-
handleCopyColumn(
|
|
2135
|
+
handleCopyColumn(tableRef, cellWrapper, selectedRecords);
|
|
2110
2136
|
}}
|
|
2111
2137
|
text="Column (Selected)"
|
|
2112
2138
|
/>
|
|
@@ -2146,7 +2172,7 @@ const DataTable = ({
|
|
|
2146
2172
|
<MenuItem
|
|
2147
2173
|
key="copyFullTableRows"
|
|
2148
2174
|
onClick={() => {
|
|
2149
|
-
handleCopyTable(
|
|
2175
|
+
handleCopyTable(tableRef);
|
|
2150
2176
|
// loop through each cell in the row
|
|
2151
2177
|
}}
|
|
2152
2178
|
text="Table"
|
|
@@ -2754,6 +2780,7 @@ const DataTable = ({
|
|
|
2754
2780
|
<ReactTable
|
|
2755
2781
|
data={filteredEnts}
|
|
2756
2782
|
ref={tableRef}
|
|
2783
|
+
noVirtual={noVirtual}
|
|
2757
2784
|
className={classNames({
|
|
2758
2785
|
isCellEditable,
|
|
2759
2786
|
"tg-table-loading": isLoading,
|
|
@@ -2835,7 +2862,8 @@ const DataTable = ({
|
|
|
2835
2862
|
resizePersist,
|
|
2836
2863
|
resized,
|
|
2837
2864
|
rowsToShow,
|
|
2838
|
-
style
|
|
2865
|
+
style,
|
|
2866
|
+
noVirtual
|
|
2839
2867
|
]
|
|
2840
2868
|
);
|
|
2841
2869
|
|
|
@@ -3152,8 +3180,8 @@ const DataTable = ({
|
|
|
3152
3180
|
)}
|
|
3153
3181
|
</div>
|
|
3154
3182
|
<Button
|
|
3155
|
-
onClick={
|
|
3156
|
-
handleCopyTable(
|
|
3183
|
+
onClick={() => {
|
|
3184
|
+
handleCopyTable(tableRef, { isDownload: true });
|
|
3157
3185
|
}}
|
|
3158
3186
|
data-tip="Download Table as CSV"
|
|
3159
3187
|
minimal
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { omitBy, isNil } from "lodash-es";
|
|
2
|
+
//we use this to make adding preset prop groups simpler
|
|
3
|
+
export default function computePresets(props = {}) {
|
|
4
|
+
const { isSimple } = props;
|
|
5
|
+
let toReturn = omitBy(props, isNil);
|
|
6
|
+
toReturn.pageSize = toReturn.controlled_pageSize || toReturn.pageSize;
|
|
7
|
+
if (isSimple) {
|
|
8
|
+
//isSimplePreset
|
|
9
|
+
toReturn = {
|
|
10
|
+
noHeader: true,
|
|
11
|
+
noFooter: !props.withPaging,
|
|
12
|
+
noPadding: true,
|
|
13
|
+
noFullscreenButton: true,
|
|
14
|
+
hidePageSizeWhenPossible: !props.withPaging,
|
|
15
|
+
isInfinite: !props.withPaging,
|
|
16
|
+
hideSelectedCount: true,
|
|
17
|
+
withTitle: false,
|
|
18
|
+
withSearch: false,
|
|
19
|
+
compact: true,
|
|
20
|
+
withPaging: false,
|
|
21
|
+
withFilter: false,
|
|
22
|
+
...toReturn
|
|
23
|
+
};
|
|
24
|
+
} else {
|
|
25
|
+
toReturn = {
|
|
26
|
+
// the usual defaults:
|
|
27
|
+
noFooter: false,
|
|
28
|
+
noPadding: false,
|
|
29
|
+
compact: true,
|
|
30
|
+
noFullscreenButton: false,
|
|
31
|
+
hidePageSizeWhenPossible: false,
|
|
32
|
+
isInfinite: false,
|
|
33
|
+
hideSelectedCount: false,
|
|
34
|
+
withTitle: true,
|
|
35
|
+
withSearch: true,
|
|
36
|
+
withPaging: true,
|
|
37
|
+
withFilter: true,
|
|
38
|
+
...toReturn
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return toReturn || {};
|
|
42
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { camelCase } from "lodash-es";
|
|
2
|
+
import { startCase, keyBy, map } from "lodash-es";
|
|
3
|
+
|
|
4
|
+
type Field = {
|
|
5
|
+
type?: string;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
filterDisabled?: boolean;
|
|
9
|
+
sortDisabled?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type Schema = { fields: (Field | string)[] };
|
|
13
|
+
|
|
14
|
+
type CompleteField = Field & {
|
|
15
|
+
type: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
path: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type CompleteSchema = {
|
|
21
|
+
fields: CompleteField[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const convertSchema = (schema: Schema): CompleteSchema => {
|
|
25
|
+
let schemaToUse = schema;
|
|
26
|
+
if (!schemaToUse.fields && Array.isArray(schema)) {
|
|
27
|
+
schemaToUse = {
|
|
28
|
+
fields: schema
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
schemaToUse = {
|
|
32
|
+
...schemaToUse
|
|
33
|
+
};
|
|
34
|
+
schemaToUse.fields = schemaToUse.fields.map((field, i) => {
|
|
35
|
+
if (typeof field === "string") {
|
|
36
|
+
return {
|
|
37
|
+
displayName: startCase(camelCase(field)),
|
|
38
|
+
path: field,
|
|
39
|
+
type: "string"
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
const fieldToUse = { ...(field as Field) };
|
|
43
|
+
fieldToUse.type = fieldToUse.type || "string";
|
|
44
|
+
fieldToUse.displayName =
|
|
45
|
+
fieldToUse.displayName || startCase(camelCase(fieldToUse.path || ""));
|
|
46
|
+
// paths are needed for column resizing
|
|
47
|
+
if (!fieldToUse.path) {
|
|
48
|
+
fieldToUse.filterDisabled = true;
|
|
49
|
+
fieldToUse.sortDisabled = true;
|
|
50
|
+
fieldToUse.path = "fake-path" + i;
|
|
51
|
+
}
|
|
52
|
+
return fieldToUse as CompleteField;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return schemaToUse as CompleteSchema;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function mergeSchemas(_originalSchema: Schema, _overrideSchema: Schema) {
|
|
59
|
+
const originalSchema = convertSchema(_originalSchema);
|
|
60
|
+
const overrideSchema = convertSchema(_overrideSchema);
|
|
61
|
+
|
|
62
|
+
const overridesByKey = keyBy(overrideSchema.fields, "path");
|
|
63
|
+
return {
|
|
64
|
+
...originalSchema,
|
|
65
|
+
...overrideSchema,
|
|
66
|
+
fields: originalSchema.fields
|
|
67
|
+
.map(f => {
|
|
68
|
+
const override = overridesByKey[f.path];
|
|
69
|
+
if (override) {
|
|
70
|
+
delete overridesByKey[f.path];
|
|
71
|
+
return override;
|
|
72
|
+
}
|
|
73
|
+
return f;
|
|
74
|
+
})
|
|
75
|
+
.concat(map(overridesByKey))
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default convertSchema;
|