@teselagen/ui 0.9.6 → 0.9.8

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.
Files changed (61) hide show
  1. package/DataTable/EditabelCell.d.ts +7 -0
  2. package/DataTable/ReactTable.d.ts +78 -0
  3. package/DataTable/defaultProps.d.ts +43 -0
  4. package/DataTable/index.d.ts +3 -3
  5. package/DataTable/utils/computePresets.d.ts +1 -0
  6. package/DataTable/utils/types/Entity.d.ts +9 -0
  7. package/DataTable/utils/types/Field.d.ts +4 -0
  8. package/DataTable/utils/types/OrderBy.d.ts +11 -0
  9. package/DataTable/utils/types/Schema.d.ts +4 -0
  10. package/DataTable/utils/useDeepEqualMemo.d.ts +1 -0
  11. package/DataTable/utils/useHotKeysWrapper.d.ts +29 -0
  12. package/DataTable/utils/useTableParams.d.ts +49 -0
  13. package/index.cjs.js +22409 -22286
  14. package/index.es.js +22421 -22298
  15. package/package.json +1 -2
  16. package/src/DataTable/Columns.jsx +945 -0
  17. package/src/DataTable/EditabelCell.js +44 -0
  18. package/src/DataTable/EditabelCell.jsx +44 -0
  19. package/src/DataTable/PagingTool.js +2 -2
  20. package/src/DataTable/ReactTable.js +738 -0
  21. package/src/DataTable/RenderCell.jsx +191 -0
  22. package/src/DataTable/defaultProps.js +45 -0
  23. package/src/DataTable/index.js +217 -1203
  24. package/src/DataTable/utils/computePresets.js +42 -0
  25. package/src/DataTable/utils/convertSchema.ts +79 -0
  26. package/src/DataTable/utils/formatPasteData.ts +34 -0
  27. package/src/DataTable/utils/getAllRows.ts +11 -0
  28. package/src/DataTable/utils/getCellCopyText.ts +7 -0
  29. package/src/DataTable/utils/getCellInfo.ts +46 -0
  30. package/src/DataTable/utils/getFieldPathToField.ts +10 -0
  31. package/src/DataTable/utils/getIdOrCodeOrIndex.ts +14 -0
  32. package/src/DataTable/utils/getLastSelectedEntity.ts +15 -0
  33. package/src/DataTable/utils/getNewEntToSelect.ts +32 -0
  34. package/src/DataTable/utils/initializeHasuraWhereAndFilter.ts +35 -0
  35. package/src/DataTable/utils/isBottomRightCornerOfRectangle.ts +27 -0
  36. package/src/DataTable/utils/isEntityClean.ts +15 -0
  37. package/src/DataTable/utils/primarySelectedValue.ts +1 -0
  38. package/src/DataTable/utils/removeCleanRows.ts +26 -0
  39. package/src/DataTable/utils/selection.ts +11 -0
  40. package/src/DataTable/utils/types/Entity.ts +13 -0
  41. package/src/DataTable/utils/types/Field.ts +4 -0
  42. package/src/DataTable/utils/types/OrderBy.ts +15 -0
  43. package/src/DataTable/utils/types/Schema.ts +5 -0
  44. package/src/DataTable/utils/useDeepEqualMemo.js +10 -0
  45. package/src/DataTable/utils/useHotKeysWrapper.js +395 -0
  46. package/src/DataTable/utils/useTableEntities.ts +60 -0
  47. package/src/DataTable/utils/useTableParams.js +361 -0
  48. package/src/DataTable/utils/utils.ts +39 -0
  49. package/src/DataTable/utils/withTableParams.js +1 -1
  50. package/src/Timeline/TimelineEvent.tsx +36 -0
  51. package/src/Timeline/index.tsx +21 -0
  52. package/src/utils/browserUtils.ts +3 -0
  53. package/src/utils/determineBlackOrWhiteTextColor.ts +11 -0
  54. package/src/utils/getTextFromEl.ts +45 -0
  55. package/src/utils/handlerHelpers.ts +32 -0
  56. package/src/utils/hooks/index.ts +1 -0
  57. package/src/utils/hooks/useDeepEqualMemo.ts +10 -0
  58. package/src/utils/hooks/useStableReference.ts +9 -0
  59. package/src/utils/hotkeyUtils.tsx +155 -0
  60. package/src/utils/isBeingCalledExcessively.ts +37 -0
  61. package/style.css +10537 -0
@@ -0,0 +1,395 @@
1
+ import { isEmpty, omit, times } from "lodash-es";
2
+ import { VIRTUALIZE_CUTOFF_LENGTH } from "@teselagen/react-table";
3
+ import { applyPatches } from "immer";
4
+ import { editCellHelper } from "../editCellHelper";
5
+ import { useHotkeys } from "@blueprintjs/core";
6
+ import { getEntityIdToEntity, getFieldPathToIndex } from "./utils";
7
+ import { getAllRows } from "./getAllRows";
8
+ import { getRowCopyText } from "./getRowCopyText";
9
+ import { handleCopyHelper } from "./handleCopyHelper";
10
+ import { getRecordsFromIdMap } from "./withSelectedEntities";
11
+ import { getLastSelectedEntity } from "./getLastSelectedEntity";
12
+ import { getNewEntToSelect } from "./getNewEntToSelect";
13
+ import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex";
14
+ import { finalizeSelection } from "./rowClick";
15
+
16
+ const IS_LINUX = window.navigator.platform.toLowerCase().search("linux") > -1;
17
+
18
+ export const useHotKeysWrapper = ({
19
+ change,
20
+ entities,
21
+ entitiesUndoRedoStack,
22
+ formatAndValidateEntities,
23
+ handleCopySelectedRows,
24
+ isCellEditable,
25
+ isEntityDisabled,
26
+ isSingleSelect,
27
+ noDeselectAll,
28
+ noSelect,
29
+ onDeselect,
30
+ onMultiRowSelect,
31
+ onRowSelect,
32
+ onSingleRowSelect,
33
+ primarySelectedCellId,
34
+ reduxFormCellValidation,
35
+ reduxFormSelectedEntityIdMap,
36
+ schema,
37
+ selectedCells,
38
+ setEntitiesUndoRedoStack,
39
+ setNoVirtual,
40
+ setSelectedCells,
41
+ startCellEdit,
42
+ tableRef,
43
+ updateEntitiesHelper,
44
+ updateValidation,
45
+ waitUntilAllRowsAreRendered
46
+ }) => {
47
+ const flashTableBorder = () => {
48
+ try {
49
+ const table = tableRef.current.tableRef;
50
+ table.classList.add("tgBorderBlue");
51
+ setTimeout(() => {
52
+ table.classList.remove("tgBorderBlue");
53
+ }, 300);
54
+ } catch (e) {
55
+ console.error(`err when flashing table border:`, e);
56
+ }
57
+ };
58
+
59
+ const handleCopySelectedCells = async () => {
60
+ // if the current selection is consecutive cells then copy with
61
+ // tabs between. if not then just select primary selected cell
62
+ if (isEmpty(selectedCells)) return;
63
+
64
+ // Temporarily disable virtualization for large tables
65
+ if (entities.length > VIRTUALIZE_CUTOFF_LENGTH) {
66
+ setNoVirtual(true);
67
+ await waitUntilAllRowsAreRendered();
68
+ }
69
+
70
+ const pathToIndex = getFieldPathToIndex(schema);
71
+ const entityIdToEntity = getEntityIdToEntity(entities);
72
+ const selectionGrid = [];
73
+ let firstRowIndex;
74
+ let firstCellIndex;
75
+ Object.keys(selectedCells).forEach(key => {
76
+ const [rowId, path] = key.split(":");
77
+ const eInfo = entityIdToEntity[rowId];
78
+ if (eInfo) {
79
+ if (firstRowIndex === undefined || eInfo.i < firstRowIndex) {
80
+ firstRowIndex = eInfo.i;
81
+ }
82
+ if (!selectionGrid[eInfo.i]) {
83
+ selectionGrid[eInfo.i] = [];
84
+ }
85
+ const cellIndex = pathToIndex[path];
86
+ if (firstCellIndex === undefined || cellIndex < firstCellIndex) {
87
+ firstCellIndex = cellIndex;
88
+ }
89
+ selectionGrid[eInfo.i][cellIndex] = true;
90
+ }
91
+ });
92
+ if (firstRowIndex === undefined) return;
93
+ const allRows = getAllRows(tableRef);
94
+ let fullCellText = "";
95
+ const fullJson = [];
96
+ times(selectionGrid.length, i => {
97
+ const row = selectionGrid[i];
98
+ if (fullCellText) {
99
+ fullCellText += "\n";
100
+ }
101
+ if (!row) {
102
+ return;
103
+ } else {
104
+ const jsonRow = [];
105
+ // ignore header
106
+ let [rowCopyText, json] = getRowCopyText(allRows[i + 1]);
107
+ rowCopyText = rowCopyText.split("\t");
108
+ times(row.length, i => {
109
+ const cell = row[i];
110
+ if (cell) {
111
+ fullCellText += rowCopyText[i];
112
+ jsonRow.push(json[i]);
113
+ }
114
+ if (i !== row.length - 1 && i >= firstCellIndex) fullCellText += "\t";
115
+ });
116
+ fullJson.push(jsonRow);
117
+ }
118
+ });
119
+ if (!fullCellText) return window.toastr.warning("No text to copy");
120
+
121
+ handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
122
+
123
+ // Re-enable virtualization if it was disabled
124
+ setNoVirtual(false);
125
+ };
126
+
127
+ const handleCopyHotkey = e => {
128
+ if (isCellEditable) {
129
+ handleCopySelectedCells(e);
130
+ } else {
131
+ handleCopySelectedRows(
132
+ getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
133
+ e
134
+ );
135
+ }
136
+ };
137
+
138
+ const handleDeleteCell = () => {
139
+ const newCellValidate = {
140
+ ...reduxFormCellValidation
141
+ };
142
+ if (isEmpty(selectedCells)) return;
143
+ const rowIds = [];
144
+ updateEntitiesHelper(entities, entities => {
145
+ const entityIdToEntity = getEntityIdToEntity(entities);
146
+ Object.keys(selectedCells).forEach(cellId => {
147
+ const [rowId, path] = cellId.split(":");
148
+ rowIds.push(rowId);
149
+ const entity = entityIdToEntity[rowId].e;
150
+ delete entity._isClean;
151
+ const { error } = editCellHelper({
152
+ entity,
153
+ path,
154
+ schema,
155
+ newVal: ""
156
+ });
157
+ if (error) {
158
+ newCellValidate[cellId] = error;
159
+ } else {
160
+ delete newCellValidate[cellId];
161
+ }
162
+ });
163
+ updateValidation(entities, newCellValidate);
164
+ });
165
+ };
166
+
167
+ const handleRowMove = (type, shiftHeld) => e => {
168
+ e.preventDefault();
169
+ e.stopPropagation();
170
+ let newIdMap = {};
171
+ const lastSelectedEnt = getLastSelectedEntity(reduxFormSelectedEntityIdMap);
172
+
173
+ if (noSelect) return;
174
+ if (lastSelectedEnt) {
175
+ let lastSelectedIndex = entities.findIndex(
176
+ ent => ent === lastSelectedEnt
177
+ );
178
+ if (lastSelectedIndex === -1) {
179
+ if (lastSelectedEnt.id !== undefined) {
180
+ lastSelectedIndex = entities.findIndex(
181
+ ent => ent.id === lastSelectedEnt.id
182
+ );
183
+ } else if (lastSelectedEnt.code !== undefined) {
184
+ lastSelectedIndex = entities.findIndex(
185
+ ent => ent.code === lastSelectedEnt.code
186
+ );
187
+ }
188
+ }
189
+ if (lastSelectedIndex === -1) {
190
+ return;
191
+ }
192
+ const newEntToSelect = getNewEntToSelect({
193
+ type,
194
+ lastSelectedIndex,
195
+ entities,
196
+ isEntityDisabled
197
+ });
198
+
199
+ if (!newEntToSelect) return;
200
+ if (shiftHeld && !isSingleSelect) {
201
+ if (
202
+ reduxFormSelectedEntityIdMap[newEntToSelect.id || newEntToSelect.code]
203
+ ) {
204
+ //the entity being moved to has already been selected
205
+ newIdMap = omit(reduxFormSelectedEntityIdMap, [
206
+ lastSelectedEnt.id || lastSelectedEnt.code
207
+ ]);
208
+ newIdMap[newEntToSelect.id || newEntToSelect.code].time =
209
+ Date.now() + 1;
210
+ } else {
211
+ //the entity being moved to has NOT been selected yet
212
+ newIdMap = {
213
+ ...reduxFormSelectedEntityIdMap,
214
+ [newEntToSelect.id || newEntToSelect.code]: {
215
+ entity: newEntToSelect,
216
+ time: Date.now()
217
+ }
218
+ };
219
+ }
220
+ } else {
221
+ //no shiftHeld
222
+ newIdMap[newEntToSelect.id || newEntToSelect.code] = {
223
+ entity: newEntToSelect,
224
+ time: Date.now()
225
+ };
226
+ }
227
+ }
228
+
229
+ finalizeSelection({
230
+ idMap: newIdMap,
231
+ entities,
232
+ props: {
233
+ onDeselect,
234
+ onSingleRowSelect,
235
+ onMultiRowSelect,
236
+ noDeselectAll,
237
+ onRowSelect,
238
+ noSelect,
239
+ change
240
+ }
241
+ });
242
+ };
243
+
244
+ const handleSelectAllRows = e => {
245
+ if (isSingleSelect) return;
246
+ e.preventDefault();
247
+
248
+ if (isCellEditable) {
249
+ const schemaPaths = schema.fields.map(f => f.path);
250
+ const newSelectedCells = {};
251
+ entities.forEach((entity, i) => {
252
+ if (isEntityDisabled(entity)) return;
253
+ const entityId = getIdOrCodeOrIndex(entity, i);
254
+ schemaPaths.forEach(p => {
255
+ newSelectedCells[`${entityId}:${p}`] = true;
256
+ });
257
+ });
258
+ setSelectedCells(newSelectedCells);
259
+ } else {
260
+ const newIdMap = {};
261
+
262
+ entities.forEach((entity, i) => {
263
+ if (isEntityDisabled(entity)) return;
264
+ const entityId = getIdOrCodeOrIndex(entity, i);
265
+ newIdMap[entityId] = { entity };
266
+ });
267
+ finalizeSelection({
268
+ idMap: newIdMap,
269
+ entities,
270
+ props: {
271
+ onDeselect,
272
+ onSingleRowSelect,
273
+ onMultiRowSelect,
274
+ noDeselectAll,
275
+ onRowSelect,
276
+ noSelect,
277
+ change
278
+ }
279
+ });
280
+ }
281
+ };
282
+
283
+ return useHotkeys([
284
+ {
285
+ global: false,
286
+ combo: "up",
287
+ label: "Move Up a Row",
288
+ onKeyDown: handleRowMove("up")
289
+ },
290
+ {
291
+ global: false,
292
+ combo: "down",
293
+ label: "Move Down a Row",
294
+ onKeyDown: handleRowMove("down")
295
+ },
296
+ {
297
+ global: false,
298
+ combo: "up+shift",
299
+ label: "Move Up a Row",
300
+ onKeyDown: handleRowMove("up", true)
301
+ },
302
+ ...(isCellEditable
303
+ ? [
304
+ {
305
+ global: false,
306
+ combo: "enter",
307
+ label: "Enter -> Start Cell Edit",
308
+ onKeyDown: e => {
309
+ e.stopPropagation();
310
+ startCellEdit(primarySelectedCellId);
311
+ }
312
+ },
313
+ {
314
+ global: false,
315
+ combo: "mod+x",
316
+ label: "Cut",
317
+ onKeyDown: e => {
318
+ handleDeleteCell();
319
+ handleCopyHotkey(e);
320
+ }
321
+ },
322
+ {
323
+ global: false,
324
+ combo: IS_LINUX ? "alt+z" : "mod+z",
325
+ label: "Undo",
326
+ onKeyDown: () => {
327
+ if (entitiesUndoRedoStack.currentVersion > 0) {
328
+ flashTableBorder();
329
+ const nextState = applyPatches(
330
+ entities,
331
+ entitiesUndoRedoStack[entitiesUndoRedoStack.currentVersion]
332
+ .inversePatches
333
+ );
334
+ const { newEnts, validationErrors } =
335
+ formatAndValidateEntities(nextState);
336
+ setEntitiesUndoRedoStack(prev => ({
337
+ ...prev,
338
+ currentVersion: prev.currentVersion - 1
339
+ }));
340
+ updateValidation(newEnts, validationErrors);
341
+ change("reduxFormEntities", newEnts);
342
+ }
343
+ }
344
+ },
345
+ {
346
+ global: false,
347
+ combo: IS_LINUX ? "alt+shift+z" : "mod+shift+z",
348
+ label: "Redo",
349
+ onKeyDown: () => {
350
+ const nextV = entitiesUndoRedoStack.currentVersion + 1;
351
+ if (entitiesUndoRedoStack[nextV]) {
352
+ flashTableBorder();
353
+ const nextState = applyPatches(
354
+ entities,
355
+ entitiesUndoRedoStack[nextV].patches
356
+ );
357
+ const { newEnts, validationErrors } =
358
+ formatAndValidateEntities(nextState);
359
+ change("reduxFormEntities", newEnts);
360
+ updateValidation(newEnts, validationErrors);
361
+ setEntitiesUndoRedoStack(prev => ({
362
+ ...prev,
363
+ currentVersion: nextV
364
+ }));
365
+ }
366
+ }
367
+ },
368
+ {
369
+ global: false,
370
+ combo: "backspace",
371
+ label: "Delete Cell",
372
+ onKeyDown: handleDeleteCell
373
+ }
374
+ ]
375
+ : []),
376
+ {
377
+ global: false,
378
+ combo: "down+shift",
379
+ label: "Move Down a Row",
380
+ onKeyDown: handleRowMove("down", true)
381
+ },
382
+ {
383
+ global: false,
384
+ combo: "mod + c",
385
+ label: "Copy rows",
386
+ onKeyDown: handleCopyHotkey
387
+ },
388
+ {
389
+ global: false,
390
+ combo: "mod + a",
391
+ label: "Select rows",
392
+ onKeyDown: handleSelectAllRows
393
+ }
394
+ ]);
395
+ };
@@ -0,0 +1,60 @@
1
+ import { useCallback } from "react";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { change, initialize } from "redux-form";
4
+
5
+ type _Entity = { [key: string]: unknown } & { id: string };
6
+
7
+ type SelectedEntityIdMap<Entity extends _Entity> = Record<
8
+ string,
9
+ { entity: Entity; time: number; index?: number }
10
+ >;
11
+
12
+ export const useTableEntities = <Entity extends _Entity>(
13
+ tableFormName: string
14
+ ) => {
15
+ const dispatch = useDispatch();
16
+ const selectTableEntities = useCallback(
17
+ (entities: { id: string }[] = []) => {
18
+ initialize(tableFormName, {}, true, {
19
+ keepDirty: true,
20
+ updateUnregisteredFields: true,
21
+ keepValues: true
22
+ });
23
+ const selectedEntityIdMap: SelectedEntityIdMap<{ id: string }> = {};
24
+ entities.forEach(entity => {
25
+ selectedEntityIdMap[entity.id] = {
26
+ entity,
27
+ time: Date.now()
28
+ };
29
+ });
30
+ dispatch(
31
+ change(
32
+ tableFormName,
33
+ "reduxFormSelectedEntityIdMap",
34
+ selectedEntityIdMap
35
+ )
36
+ );
37
+ },
38
+ [dispatch, tableFormName]
39
+ );
40
+
41
+ const { allOrderedEntities, selectedEntities } = useSelector(
42
+ (state: {
43
+ form: Record<
44
+ string,
45
+ {
46
+ values?: {
47
+ allOrderedEntities?: Entity[];
48
+ reduxFormSelectedEntityIdMap?: SelectedEntityIdMap<Entity>;
49
+ };
50
+ }
51
+ >;
52
+ }) => ({
53
+ allOrderedEntities:
54
+ state.form?.[tableFormName]?.values?.allOrderedEntities,
55
+ selectedEntities:
56
+ state.form?.[tableFormName]?.values?.reduxFormSelectedEntityIdMap
57
+ })
58
+ );
59
+ return { selectTableEntities, allOrderedEntities, selectedEntities };
60
+ };