@firecms/core 3.0.0-canary.170 → 3.0.0-canary.172
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/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
- package/dist/index.es.js +82 -14
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +82 -14
- package/dist/index.umd.js.map +1 -1
- package/package.json +5 -5
- package/src/components/ArrayContainer.tsx +1 -1
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +2 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +7 -7
- package/src/components/common/useDataSourceTableController.tsx +83 -8
- package/src/routes/FireCMSRoute.tsx +2 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.0-canary.
|
|
4
|
+
"version": "3.0.0-canary.172",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"./package.json": "./package.json"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@firecms/editor": "^3.0.0-canary.
|
|
54
|
-
"@firecms/formex": "^3.0.0-canary.
|
|
55
|
-
"@firecms/ui": "^3.0.0-canary.
|
|
53
|
+
"@firecms/editor": "^3.0.0-canary.172",
|
|
54
|
+
"@firecms/formex": "^3.0.0-canary.172",
|
|
55
|
+
"@firecms/ui": "^3.0.0-canary.172",
|
|
56
56
|
"@hello-pangea/dnd": "^17.0.0",
|
|
57
57
|
"@radix-ui/react-portal": "^1.1.2",
|
|
58
58
|
"clsx": "^2.1.1",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"dist",
|
|
105
105
|
"src"
|
|
106
106
|
],
|
|
107
|
-
"gitHead": "
|
|
107
|
+
"gitHead": "538f682b9f89bfd08db9891b57cb71f618f4764b",
|
|
108
108
|
"publishConfig": {
|
|
109
109
|
"access": "public"
|
|
110
110
|
},
|
|
@@ -239,7 +239,7 @@ export function ArrayContainer<T>({
|
|
|
239
239
|
variant={"text"}
|
|
240
240
|
size={size === "small" ? "small" : "medium"}
|
|
241
241
|
color="primary"
|
|
242
|
-
disabled={disabled || value
|
|
242
|
+
disabled={disabled || value?.length >= max}
|
|
243
243
|
startIcon={<AddIcon/>}
|
|
244
244
|
onClick={insertInEnd}>
|
|
245
245
|
{addLabel ?? "Add"}
|
|
@@ -35,12 +35,10 @@ const operationLabels = {
|
|
|
35
35
|
const multipleSelectOperations = ["array-contains-any", "in", "not-in"];
|
|
36
36
|
|
|
37
37
|
export function ReferenceFilterField({
|
|
38
|
-
name,
|
|
39
38
|
value,
|
|
40
39
|
setValue,
|
|
41
40
|
isArray,
|
|
42
41
|
path,
|
|
43
|
-
title,
|
|
44
42
|
includeId = true,
|
|
45
43
|
previewProperties,
|
|
46
44
|
setHidden
|
|
@@ -50,9 +48,11 @@ export function ReferenceFilterField({
|
|
|
50
48
|
? ["array-contains"]
|
|
51
49
|
: ["==", "!=", ">", "<", ">=", "<="];
|
|
52
50
|
|
|
53
|
-
isArray
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
if (isArray) {
|
|
52
|
+
possibleOperations.push("array-contains-any");
|
|
53
|
+
} else {
|
|
54
|
+
possibleOperations.push("in", "not-in");
|
|
55
|
+
}
|
|
56
56
|
|
|
57
57
|
const [fieldOperation, fieldValue] = value || [possibleOperations[0], undefined];
|
|
58
58
|
const [operation, setOperation] = useState<VirtualTableWhereFilterOp>(fieldOperation);
|
|
@@ -144,7 +144,7 @@ export function ReferenceFilterField({
|
|
|
144
144
|
|
|
145
145
|
return (
|
|
146
146
|
|
|
147
|
-
<div className="flex w-[
|
|
147
|
+
<div className="flex w-[480px] flex-row">
|
|
148
148
|
<div className="w-[140px]">
|
|
149
149
|
<Select value={operation}
|
|
150
150
|
size={"large"}
|
|
@@ -161,7 +161,7 @@ export function ReferenceFilterField({
|
|
|
161
161
|
</Select>
|
|
162
162
|
</div>
|
|
163
163
|
|
|
164
|
-
<div className="flex-grow ml-2 h-full gap-2 flex flex-col">
|
|
164
|
+
<div className="flex-grow ml-2 h-full gap-2 flex flex-col w-[340px]">
|
|
165
165
|
|
|
166
166
|
{internalValue && Array.isArray(internalValue) && <div>
|
|
167
167
|
{internalValue.map((ref, index) => buildEntry(ref))}
|
|
@@ -5,6 +5,7 @@ import { useDataOrder } from "../../hooks/data/useDataOrder";
|
|
|
5
5
|
import {
|
|
6
6
|
Entity,
|
|
7
7
|
EntityCollection,
|
|
8
|
+
EntityReference,
|
|
8
9
|
EntityTableController,
|
|
9
10
|
FilterValues,
|
|
10
11
|
FireCMSContext,
|
|
@@ -128,11 +129,11 @@ export function useDataSourceTableController<M extends Record<string, any> = any
|
|
|
128
129
|
}, [initialSort, forceFilter]);
|
|
129
130
|
|
|
130
131
|
const {
|
|
131
|
-
filterValues:
|
|
132
|
+
filterValues: initialFilterUrl,
|
|
132
133
|
sortBy: initialSortUrl,
|
|
133
134
|
} = parseFilterAndSort(window.location.search);
|
|
134
135
|
|
|
135
|
-
const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ??
|
|
136
|
+
const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? (updateUrl ? initialFilterUrl : undefined) ?? initialFilter ?? undefined);
|
|
136
137
|
const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>(initialSortUrl ?? initialSortInternal);
|
|
137
138
|
|
|
138
139
|
useUpdateUrl(filterValues, sortBy, searchString, updateUrl);
|
|
@@ -302,7 +303,7 @@ function useUpdateUrl<M extends Record<string, any> = any>(
|
|
|
302
303
|
}, [filterValues, sortBy, searchString, updateUrl]);
|
|
303
304
|
}
|
|
304
305
|
|
|
305
|
-
function encodeFilterAndSort(filterValues?: FilterValues<string
|
|
306
|
+
function encodeFilterAndSort(filterValues?: FilterValues<string>, sortBy?: [string, "asc" | "desc"] | undefined) {
|
|
306
307
|
const entries: Record<string, string> = {};
|
|
307
308
|
if (sortBy) {
|
|
308
309
|
entries["__sort"] = encodeURIComponent(sortBy[0]);
|
|
@@ -311,8 +312,30 @@ function encodeFilterAndSort(filterValues?: FilterValues<string> | undefined, so
|
|
|
311
312
|
if (filterValues) {
|
|
312
313
|
Object.entries(filterValues).forEach(([key, value]) => {
|
|
313
314
|
if (value) {
|
|
314
|
-
|
|
315
|
-
|
|
315
|
+
const [op, val] = value;
|
|
316
|
+
let encodedValue: any = val;
|
|
317
|
+
try {
|
|
318
|
+
if (typeof val === "object") {
|
|
319
|
+
if (val instanceof Date) {
|
|
320
|
+
encodedValue = val.toISOString();
|
|
321
|
+
} else if (Array.isArray(val)) {
|
|
322
|
+
encodedValue = JSON.stringify(val, (key, value) => {
|
|
323
|
+
if (value instanceof EntityReference) {
|
|
324
|
+
return encodeRef(value);
|
|
325
|
+
}
|
|
326
|
+
return value;
|
|
327
|
+
});
|
|
328
|
+
} else if (val instanceof EntityReference) {
|
|
329
|
+
encodedValue = encodeRef(val);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} catch (e) {
|
|
333
|
+
encodedValue = val;
|
|
334
|
+
}
|
|
335
|
+
if (encodedValue !== undefined) {
|
|
336
|
+
entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
|
|
337
|
+
entries[encodeURIComponent(`${key}_value`)] = encodeURIComponent(encodedValue.toString());
|
|
338
|
+
}
|
|
316
339
|
}
|
|
317
340
|
});
|
|
318
341
|
}
|
|
@@ -331,10 +354,14 @@ function parseFilterAndSort<M>(search: string): {
|
|
|
331
354
|
let sortBy: [string, "asc" | "desc"] | undefined = undefined;
|
|
332
355
|
entries.forEach((value, key) => {
|
|
333
356
|
if (key === "__sort") {
|
|
334
|
-
sortBy = [value, entries.get("__sort_order") as "asc" | "desc"];
|
|
357
|
+
sortBy = [decodeURIComponent(value), entries.get("__sort_order") as "asc" | "desc"];
|
|
335
358
|
} else if (key.endsWith("_op")) {
|
|
336
|
-
const
|
|
337
|
-
|
|
359
|
+
const field = key.replace("_op", "");
|
|
360
|
+
const filterOp = decodeURIComponent(value) as WhereFilterOp;
|
|
361
|
+
const filterValStr = entries.get(`${field}_value`);
|
|
362
|
+
if (filterValStr !== null) {
|
|
363
|
+
filterValues[field] = [filterOp, decodeString(filterValStr)];
|
|
364
|
+
}
|
|
338
365
|
}
|
|
339
366
|
});
|
|
340
367
|
|
|
@@ -343,3 +370,51 @@ function parseFilterAndSort<M>(search: string): {
|
|
|
343
370
|
sortBy
|
|
344
371
|
}
|
|
345
372
|
}
|
|
373
|
+
|
|
374
|
+
function isDate(dateString: string): boolean {
|
|
375
|
+
// Define a regex pattern that matches the exact date format: 2025-01-07T23:00:00.000Z
|
|
376
|
+
const regexPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
377
|
+
|
|
378
|
+
// Test the dateString against the regex pattern
|
|
379
|
+
if (!regexPattern.test(dateString)) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// If the regex matches, further validate if it is a valid UTC date
|
|
384
|
+
const date = new Date(dateString);
|
|
385
|
+
return date.toISOString() === dateString;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function encodeRef(val: EntityReference) {
|
|
389
|
+
return `ref::${val.path}/${val.id}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function decodeString(val: string): EntityReference | Date | string {
|
|
393
|
+
let parsedFilterVal: any = val;
|
|
394
|
+
if (isDate(val)) {
|
|
395
|
+
try {
|
|
396
|
+
parsedFilterVal = new Date(val);
|
|
397
|
+
} catch (e) {
|
|
398
|
+
// ignore
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (typeof parsedFilterVal === "string") {
|
|
402
|
+
try {
|
|
403
|
+
parsedFilterVal = JSON.parse(parsedFilterVal, (key, value) => {
|
|
404
|
+
if (typeof value === "string" && value.startsWith("ref::")) {
|
|
405
|
+
const [path, id] = value.substring(5).split("/");
|
|
406
|
+
return new EntityReference(id, path);
|
|
407
|
+
}
|
|
408
|
+
return value;
|
|
409
|
+
});
|
|
410
|
+
} catch (e) {
|
|
411
|
+
// ignore
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (typeof parsedFilterVal === "string" && parsedFilterVal.startsWith("ref::")) {
|
|
416
|
+
const [path, id] = parsedFilterVal.substring(5).split("/");
|
|
417
|
+
return new EntityReference(id, path);
|
|
418
|
+
}
|
|
419
|
+
return parsedFilterVal;
|
|
420
|
+
}
|
|
@@ -211,9 +211,11 @@ function EntityFullScreenRoute({
|
|
|
211
211
|
selectedTab={selectedTab ?? undefined}
|
|
212
212
|
onValuesModified={(modified) => blocked.current = modified}
|
|
213
213
|
onSaved={(params) => {
|
|
214
|
+
console.log("EntityEditView Saved", params);
|
|
214
215
|
updateUrl(params.entityId, params.selectedTab, true, params.path, isNew);
|
|
215
216
|
}}
|
|
216
217
|
onTabChange={(params) => {
|
|
218
|
+
console.log("EntityEditView onTabChange", params);
|
|
217
219
|
updateUrl(params.entityId, params.selectedTab, !isNew, params.path, isNew);
|
|
218
220
|
setSelectedTab(params.selectedTab);
|
|
219
221
|
}}
|