@teselagen/ui 0.8.8 → 0.9.3

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 (43) hide show
  1. package/DataTable/utils/filterLocalEntitiesToHasura.d.ts +5 -0
  2. package/DataTable/utils/getAllRows.d.ts +1 -1
  3. package/DataTable/utils/getRowCopyText.d.ts +1 -3
  4. package/DataTable/utils/handleCopyColumn.d.ts +1 -1
  5. package/DataTable/utils/handleCopyRows.d.ts +1 -5
  6. package/DataTable/utils/handleCopyTable.d.ts +1 -1
  7. package/DataTable/utils/index.d.ts +0 -1
  8. package/DataTable/utils/initializeHasuraWhereAndFilter.d.ts +1 -0
  9. package/DataTable/utils/queryParams.d.ts +16 -12
  10. package/DataTable/utils/rowClick.d.ts +1 -1
  11. package/DataTable/utils/tableQueryParamsToHasuraClauses.d.ts +26 -0
  12. package/FormComponents/Uploader.d.ts +1 -3
  13. package/FormComponents/tryToMatchSchemas.d.ts +1 -1
  14. package/MenuBar/index.d.ts +1 -3
  15. package/README.md +1 -1
  16. package/ResizableDraggableDialog/index.d.ts +1 -3
  17. package/TagSelect/index.d.ts +1 -1
  18. package/index.cjs.js +40098 -36878
  19. package/index.d.ts +2 -0
  20. package/index.es.js +39112 -35892
  21. package/package.json +2 -4
  22. package/src/DataTable/Columns.js +2 -2
  23. package/src/DataTable/DisplayOptions.js +1 -1
  24. package/src/DataTable/FilterAndSortMenu.js +27 -30
  25. package/src/DataTable/index.js +113 -90
  26. package/src/DataTable/style.css +1 -1
  27. package/src/DataTable/utils/filterLocalEntitiesToHasura.js +356 -0
  28. package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +1285 -0
  29. package/src/DataTable/utils/getAllRows.js +2 -6
  30. package/src/DataTable/utils/handleCopyColumn.js +2 -2
  31. package/src/DataTable/utils/handleCopyTable.js +2 -2
  32. package/src/DataTable/utils/initializeHasuraWhereAndFilter.js +15 -0
  33. package/src/DataTable/utils/queryParams.js +153 -770
  34. package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +277 -0
  35. package/src/DataTable/utils/tableQueryParamsToHasuraClauses.test.js +245 -0
  36. package/src/DataTable/utils/withTableParams.js +3 -16
  37. package/src/FormComponents/index.js +2 -2
  38. package/src/TgSelect/index.js +15 -0
  39. package/src/index.js +2 -0
  40. package/src/utils/determineBlackOrWhiteTextColor.js +8 -1
  41. package/ui.css +10537 -0
  42. package/utils/determineBlackOrWhiteTextColor.d.ts +1 -2
  43. package/utils/hotkeyUtils.d.ts +1 -3
@@ -0,0 +1,356 @@
1
+ import {
2
+ isEmpty,
3
+ every,
4
+ some,
5
+ isEqual,
6
+ isString,
7
+ isNull,
8
+ isArray,
9
+ includes,
10
+ isObject,
11
+ has,
12
+ orderBy,
13
+ endsWith,
14
+ get,
15
+ forEach
16
+ } from "lodash-es";
17
+
18
+ export function filterLocalEntitiesToHasura(
19
+ records,
20
+ { where, order_by, limit, offset, isInfinite } = {}
21
+ ) {
22
+ let filteredRecords = [...records];
23
+
24
+ // Apply where clause if it exists
25
+ if (where) {
26
+ filteredRecords = applyWhereClause(filteredRecords, where);
27
+ }
28
+
29
+ // Apply order_by if it exists
30
+ if (order_by) {
31
+ filteredRecords = applyOrderBy(filteredRecords, order_by);
32
+ }
33
+ filteredRecords = restoreEntitiesFromLocalFilter(filteredRecords);
34
+
35
+ // Store the complete filtered and ordered records for pagination info
36
+ const allFilteredRecords = [...filteredRecords];
37
+
38
+ // Apply limit and offset
39
+ if (!isInfinite && offset !== undefined) {
40
+ filteredRecords = filteredRecords.slice(offset);
41
+ }
42
+
43
+ if (!isInfinite && limit !== undefined) {
44
+ filteredRecords = filteredRecords.slice(0, limit);
45
+ }
46
+
47
+ // For consistency, always return an object with entities, entitiesAcrossPages, and entityCount
48
+ return {
49
+ entities: filteredRecords,
50
+ entitiesAcrossPages: allFilteredRecords,
51
+ entityCount: allFilteredRecords.length
52
+ };
53
+ }
54
+
55
+ function applyWhereClause(records, where) {
56
+ function applyFilter(record, filter) {
57
+ if (isEmpty(filter)) {
58
+ return true; // No filter, all records pass
59
+ }
60
+
61
+ for (const key in filter) {
62
+ if (key === "_and") {
63
+ if (isEmpty(filter[key])) {
64
+ continue;
65
+ }
66
+ if (!every(filter[key], subFilter => applyFilter(record, subFilter))) {
67
+ return false;
68
+ }
69
+ } else if (key === "_or") {
70
+ if (isEmpty(filter[key])) {
71
+ continue;
72
+ }
73
+ if (!some(filter[key], subFilter => applyFilter(record, subFilter))) {
74
+ return false;
75
+ }
76
+ } else if (key === "_not") {
77
+ if (applyFilter(record, filter[key])) {
78
+ return false;
79
+ }
80
+ } else {
81
+ const value = get(record, key);
82
+ const conditions = filter[key];
83
+
84
+ // Handle nested object properties
85
+ if (
86
+ isObject(value) &&
87
+ isObject(conditions) &&
88
+ !hasOperator(conditions)
89
+ ) {
90
+ return applyFilter(value, conditions);
91
+ }
92
+
93
+ for (const operator in conditions) {
94
+ const conditionValue = conditions[operator];
95
+
96
+ // Handle range conditions (_gt/_lt or _gte/_lte combinations)
97
+ if (operator === "_gt" && conditions._lt) {
98
+ if (!(value > conditionValue && value < conditions._lt))
99
+ return false;
100
+ continue;
101
+ }
102
+ if (operator === "_gte" && conditions._lte) {
103
+ if (!(value >= conditionValue && value <= conditions._lte))
104
+ return false;
105
+ continue;
106
+ }
107
+
108
+ switch (operator) {
109
+ case "_eq":
110
+ if (!isEqual(value, conditionValue)) return false;
111
+ break;
112
+ case "_neq":
113
+ if (isEqual(value, conditionValue)) return false;
114
+ break;
115
+ case "_gt":
116
+ if (!(value > conditionValue)) return false;
117
+ break;
118
+ case "_gte":
119
+ if (!(value >= conditionValue)) return false;
120
+ break;
121
+ case "_lt":
122
+ if (!(value < conditionValue)) return false;
123
+ break;
124
+ case "_lte":
125
+ if (!(value <= conditionValue)) return false;
126
+ break;
127
+ case "_like":
128
+ if (
129
+ !isString(value) ||
130
+ !new RegExp(conditionValue.replace(/%/g, ".*")).test(value)
131
+ )
132
+ return false;
133
+ break;
134
+ case "_ilike":
135
+ if (
136
+ !isString(value) ||
137
+ !new RegExp(conditionValue.replace(/%/g, ".*"), "i").test(value)
138
+ )
139
+ return false;
140
+ break;
141
+ case "_nlike":
142
+ if (
143
+ !isString(value) ||
144
+ new RegExp(conditionValue.replace(/%/g, ".*")).test(value)
145
+ )
146
+ return false;
147
+ break;
148
+ case "_nilike":
149
+ if (
150
+ !isString(value) ||
151
+ new RegExp(conditionValue.replace(/%/g, ".*"), "i").test(value)
152
+ )
153
+ return false;
154
+ break;
155
+ case "_starts_with":
156
+ if (!isString(value) || !value.startsWith(conditionValue))
157
+ return false;
158
+ break;
159
+ case "_ends_with":
160
+ if (!isString(value) || !value.endsWith(conditionValue))
161
+ return false;
162
+ break;
163
+ case "_is_null":
164
+ if (
165
+ (conditionValue && !isNull(value)) ||
166
+ (!conditionValue && isNull(value))
167
+ )
168
+ return false;
169
+ break;
170
+ case "_contains":
171
+ if (
172
+ !isArray(value) ||
173
+ !every(conditionValue, item => includes(value, item))
174
+ )
175
+ return false;
176
+ break;
177
+ case "_contained_in":
178
+ if (
179
+ !isArray(value) ||
180
+ !every(value, item => includes(conditionValue, item))
181
+ )
182
+ return false;
183
+ break;
184
+ case "_has_key":
185
+ if (!isObject(value) || !has(value, conditionValue)) return false;
186
+ break;
187
+ case "_has_keys_any":
188
+ if (
189
+ !isObject(value) ||
190
+ !some(conditionValue, item => has(value, item))
191
+ )
192
+ return false;
193
+ break;
194
+ case "_has_keys_all":
195
+ if (
196
+ !isObject(value) ||
197
+ !every(conditionValue, item => has(value, item))
198
+ )
199
+ return false;
200
+ break;
201
+ case "_similar":
202
+ if (
203
+ !isString(value) ||
204
+ !new RegExp(conditionValue.replace(/%/g, ".*")).test(value)
205
+ )
206
+ return false;
207
+ break;
208
+ default:
209
+ if (operator.startsWith("_")) {
210
+ console.warn(`Unsupported operator: ${operator}`);
211
+ return false;
212
+ } else {
213
+ console.warn(`Unsupported operator: ${operator}`);
214
+ return false;
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ return true;
222
+ }
223
+
224
+ // Helper to check if an object contains any Hasura operators
225
+ function hasOperator(obj) {
226
+ return Object.keys(obj).some(key => key.startsWith("_"));
227
+ }
228
+
229
+ return records.filter(record => applyFilter(record, where));
230
+ }
231
+
232
+ // takes in an array of records and an order_by clause
233
+ // order_by looks like this: [{ some_field: "asc" }, { some_other_field: "desc" }] or {some_field: "asc"}
234
+ // returns the records sorted by the order_by clause
235
+ function applyOrderBy(records, _order_by) {
236
+ const order_by = isArray(_order_by)
237
+ ? _order_by
238
+ : isEmpty(_order_by)
239
+ ? []
240
+ : [_order_by];
241
+
242
+ if (order_by.length > 0) {
243
+ const orderFuncs = [];
244
+ const ascOrDescArray = [];
245
+
246
+ order_by.forEach(
247
+ ({ path, direction, type, sortFn, getValueToFilterOn, ownProps }) => {
248
+ // Default direction is "desc" if not specified
249
+ direction = direction || "desc";
250
+
251
+ if (sortFn) {
252
+ // Allow sortFn to be a function, a string, or an array of functions/strings
253
+ const sortFnArray = Array.isArray(sortFn) ? sortFn : [sortFn];
254
+
255
+ sortFnArray.forEach(fn => {
256
+ // If fn is a string, treat it as a path to get from the record
257
+ const getter = typeof fn === "function" ? fn : r => get(r, fn);
258
+
259
+ // First handle null check for this function's/string's values
260
+ orderFuncs.push(r => {
261
+ const val = getter(r);
262
+ return val !== null && val !== undefined ? 1 : 0;
263
+ });
264
+ ascOrDescArray.push("desc"); // Always push nulls to the bottom
265
+
266
+ // Then the actual sort function or path getter
267
+ orderFuncs.push(getter);
268
+ ascOrDescArray.push(direction);
269
+ });
270
+ } else if (getValueToFilterOn) {
271
+ // Custom getValue function
272
+ // First handle null check
273
+ orderFuncs.push(r => {
274
+ const val = getValueToFilterOn(r, ownProps);
275
+ return val !== null && val !== undefined ? 1 : 0;
276
+ });
277
+ ascOrDescArray.push("desc"); // Always push nulls to the bottom
278
+
279
+ // Then the actual value getter function
280
+ orderFuncs.push(r => getValueToFilterOn(r, ownProps));
281
+ ascOrDescArray.push(direction);
282
+ } else if (type === "timestamp") {
283
+ // Sort nulls/undefined to the bottom regardless of sort direction
284
+ orderFuncs.push(r => {
285
+ const val = get(r, path);
286
+ // First check if value exists, this ensures nulls go to the bottom
287
+ return val ? 1 : 0;
288
+ });
289
+ ascOrDescArray.push("desc"); // always put nulls at the bottom
290
+
291
+ // Then actual timestamp sorting
292
+ orderFuncs.push(r => {
293
+ const val = get(r, path);
294
+ return val ? new Date(val).getTime() : -Infinity;
295
+ });
296
+ ascOrDescArray.push(direction);
297
+ } else if (path && endsWith(path.toLowerCase(), "id")) {
298
+ // Handle ID fields - sort numerically
299
+ // First handle null check
300
+ orderFuncs.push(r => {
301
+ const val = get(r, path);
302
+ return val !== null && val !== undefined ? 1 : 0;
303
+ });
304
+ ascOrDescArray.push("desc"); // Always push nulls to the bottom
305
+
306
+ // Then the actual ID parsing
307
+ orderFuncs.push(o => {
308
+ const val = get(o, path);
309
+ if (val === null || val === undefined) return -Infinity;
310
+ return parseInt(val, 10) || 0;
311
+ });
312
+ ascOrDescArray.push(direction);
313
+ } else {
314
+ // Default sorting
315
+ // First sort by existence (non-nulls first)
316
+ orderFuncs.push(r => {
317
+ const val = get(r, path);
318
+ return val !== null && val !== undefined ? 1 : 0;
319
+ });
320
+ ascOrDescArray.push("desc"); // Always put nulls at the bottom
321
+
322
+ // Then sort by actual value
323
+ orderFuncs.push(r => {
324
+ const val = get(r, path);
325
+ if (val === null || val === undefined) return -Infinity;
326
+
327
+ // For string sorting, implement natural sort
328
+ if (isString(val)) {
329
+ return val.toLowerCase().replace(/(\d+)/g, num =>
330
+ // Pad numbers with leading zeros for proper natural sort
331
+ num.padStart(10, "0")
332
+ );
333
+ }
334
+ return val;
335
+ });
336
+ ascOrDescArray.push(direction);
337
+ }
338
+ }
339
+ );
340
+
341
+ records = orderBy(records, orderFuncs, ascOrDescArray);
342
+ }
343
+ return records;
344
+ }
345
+
346
+ function restoreEntitiesFromLocalFilter(ents) {
347
+ return ents.map(entity => {
348
+ forEach(entity, (val, key) => {
349
+ if (key.startsWith?.("___original___")) {
350
+ entity[key.slice("___original___".length)] = val;
351
+ delete entity[key];
352
+ }
353
+ });
354
+ return entity;
355
+ });
356
+ }