@firecms/core 3.0.0-beta.11 → 3.0.0-beta.13
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/app/Scaffold.d.ts +4 -0
- package/dist/components/ArrayContainer.d.ts +14 -3
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
- package/dist/components/EntityJsonPreview.d.ts +3 -0
- package/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/default_entity_actions.d.ts +0 -2
- package/dist/components/common/index.d.ts +1 -1
- package/dist/components/common/useColumnsIds.d.ts +1 -0
- package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
- package/dist/components/common/useDebouncedCallback.d.ts +1 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/contexts/BreacrumbsContext.d.ts +8 -0
- package/dist/core/DefaultAppBar.d.ts +8 -2
- package/dist/core/EntityEditView.d.ts +36 -21
- package/dist/core/EntityEditViewFormActions.d.ts +2 -0
- package/dist/core/FireCMS.d.ts +2 -2
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/SideDialogs.d.ts +4 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/form/EntityForm.d.ts +49 -0
- package/dist/form/EntityFormActions.d.ts +17 -0
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/FormEntry.d.ts +6 -0
- package/dist/form/components/FormLayout.d.ts +5 -0
- package/dist/form/components/index.d.ts +2 -0
- package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
- package/dist/form/index.d.ts +16 -16
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +4 -1
- package/dist/hooks/useModeController.d.ts +1 -2
- package/dist/index.es.js +15842 -14396
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +15833 -14389
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +3 -2
- package/dist/internal/useBuildSideEntityController.d.ts +3 -3
- package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
- package/dist/routes/CustomCMSRoute.d.ts +4 -0
- package/dist/routes/FireCMSRoute.d.ts +1 -0
- package/dist/routes/HomePageRoute.d.ts +3 -0
- package/dist/types/collections.d.ts +36 -10
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +7 -3
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +10 -0
- package/dist/types/fields.d.ts +13 -3
- package/dist/types/firecms.d.ts +1 -1
- package/dist/types/navigation.d.ts +33 -12
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/properties.d.ts +40 -4
- package/dist/types/side_entity_controller.d.ts +6 -1
- package/dist/util/builders.d.ts +1 -1
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/navigation_from_path.d.ts +6 -1
- package/dist/util/navigation_utils.d.ts +13 -1
- package/dist/util/objects.d.ts +1 -1
- package/dist/util/property_utils.d.ts +2 -2
- package/dist/util/references.d.ts +2 -2
- package/dist/util/resolutions.d.ts +12 -2
- package/package.json +20 -21
- package/src/app/Scaffold.tsx +13 -3
- package/src/components/ArrayContainer.tsx +60 -24
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +5 -3
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +46 -23
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +10 -13
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +105 -47
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
- package/src/components/EntityJsonPreview.tsx +66 -0
- package/src/components/EntityPreview.tsx +10 -3
- package/src/components/EntityView.tsx +4 -1
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyIdCopyTooltip.tsx +2 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +14 -7
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +7 -7
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +48 -3
- package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
- package/src/components/common/default_entity_actions.tsx +37 -37
- package/src/components/common/index.ts +1 -1
- package/src/components/common/useColumnsIds.tsx +1 -1
- package/src/components/common/useDataSourceTableController.tsx +420 -0
- package/src/components/common/useDebouncedCallback.tsx +20 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +3 -2
- package/src/contexts/ModeController.tsx +1 -3
- package/src/core/DefaultAppBar.tsx +59 -17
- package/src/core/DefaultDrawer.tsx +1 -1
- package/src/core/EntityEditView.tsx +354 -1000
- package/src/core/EntityEditViewFormActions.tsx +199 -0
- package/src/core/EntitySidePanel.tsx +85 -15
- package/src/core/FireCMS.tsx +20 -16
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +23 -32
- package/src/core/SideDialogs.tsx +20 -11
- package/src/core/index.tsx +4 -2
- package/src/form/EntityForm.tsx +782 -0
- package/src/form/EntityFormActions.tsx +169 -0
- package/src/form/PropertyFieldBinding.tsx +21 -16
- package/src/form/components/FormEntry.tsx +22 -0
- package/src/form/components/FormLayout.tsx +16 -0
- package/src/form/components/LabelWithIcon.tsx +6 -3
- package/src/form/components/StorageUploadProgress.tsx +1 -1
- package/src/form/components/index.tsx +2 -0
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +8 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +5 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +5 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -0
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +14 -16
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +15 -8
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +3 -3
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RepeatFieldBinding.tsx +13 -4
- package/src/form/field_bindings/SelectFieldBinding.tsx +2 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +11 -6
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -0
- package/src/form/field_bindings/TextFieldBinding.tsx +1 -0
- package/src/form/index.tsx +16 -32
- package/src/form/validation.ts +12 -6
- package/src/hooks/data/save.ts +25 -30
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +5 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBuildModeController.tsx +15 -28
- package/src/hooks/useBuildNavigationController.tsx +81 -24
- package/src/hooks/useFireCMSContext.tsx +0 -30
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useModeController.tsx +1 -2
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildDataSource.ts +9 -5
- package/src/internal/useBuildSideDialogsController.tsx +3 -2
- package/src/internal/useBuildSideEntityController.tsx +135 -88
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/preview/PropertyPreview.tsx +12 -10
- package/src/preview/components/UrlComponentPreview.tsx +17 -18
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
- package/src/preview/property_previews/MapPropertyPreview.tsx +3 -2
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +246 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +45 -13
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +7 -3
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +12 -0
- package/src/types/fields.tsx +18 -3
- package/src/types/firecms.tsx +1 -1
- package/src/types/navigation.ts +41 -16
- package/src/types/plugins.tsx +2 -0
- package/src/types/properties.ts +48 -4
- package/src/types/side_dialogs_controller.tsx +2 -0
- package/src/types/side_entity_controller.tsx +11 -1
- package/src/util/builders.ts +5 -3
- package/src/util/createFormexStub.tsx +62 -0
- package/src/util/entity_actions.ts +28 -0
- package/src/util/entity_cache.ts +204 -0
- package/src/util/index.ts +1 -0
- package/src/util/join_collections.ts +6 -1
- package/src/util/navigation_from_path.ts +18 -7
- package/src/util/navigation_utils.ts +115 -14
- package/src/util/objects.ts +9 -2
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/property_utils.tsx +7 -3
- package/src/util/references.ts +8 -6
- package/src/util/resolutions.ts +41 -12
- package/src/util/useStorageUploadController.tsx +60 -23
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { EntityReference, GeoPoint, Vector } from "../types";
|
|
2
|
+
|
|
3
|
+
// Define a unique prefix for entity keys in localStorage to avoid key collisions
|
|
4
|
+
const LOCAL_STORAGE_PREFIX = "entity_cache::";
|
|
5
|
+
|
|
6
|
+
// In-memory cache to store entities for quick access
|
|
7
|
+
const entityCache: Map<string, object> = new Map();
|
|
8
|
+
|
|
9
|
+
// Check `localStorage` availability once during initialization
|
|
10
|
+
const isLocalStorageAvailable = typeof localStorage !== "undefined";
|
|
11
|
+
|
|
12
|
+
// Define custom replacer for JSON.stringify
|
|
13
|
+
function customReplacer(key: string): any {
|
|
14
|
+
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
const value = this[key];
|
|
17
|
+
|
|
18
|
+
// Handle Date objects
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
if (value instanceof Date) {
|
|
21
|
+
return { __type: "Date", value: value.toISOString() };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle EntityReference
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
if (value instanceof EntityReference) {
|
|
27
|
+
return { __type: "EntityReference", id: value.id, path: value.path };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Handle GeoPoint
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
if (value instanceof GeoPoint) {
|
|
33
|
+
return { __type: "GeoPoint", latitude: value.latitude, longitude: value.longitude };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Handle Vector
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
if (value instanceof Vector) {
|
|
39
|
+
return { __type: "Vector", value: value.value };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Define custom reviver for JSON.parse
|
|
46
|
+
function customReviver(key: string, value: any): any {
|
|
47
|
+
if (value && typeof value === "object" && "__type" in value) {
|
|
48
|
+
switch (value.__type) {
|
|
49
|
+
case "Date":
|
|
50
|
+
return new Date(value.value);
|
|
51
|
+
case "EntityReference":
|
|
52
|
+
return new EntityReference(value.id, value.path);
|
|
53
|
+
case "GeoPoint":
|
|
54
|
+
return new GeoPoint(value.latitude, value.longitude);
|
|
55
|
+
case "Vector":
|
|
56
|
+
return new Vector(value.value);
|
|
57
|
+
default:
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Initialize the in-memory cache by loading entities from `localStorage`
|
|
65
|
+
if (isLocalStorageAvailable) {
|
|
66
|
+
try {
|
|
67
|
+
// Iterate over all keys in localStorage to find those with the specified prefix
|
|
68
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
69
|
+
const fullKey = localStorage.key(i);
|
|
70
|
+
if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
|
|
71
|
+
const path = fullKey.substring(LOCAL_STORAGE_PREFIX.length);
|
|
72
|
+
const entityString = localStorage.getItem(fullKey);
|
|
73
|
+
if (entityString) {
|
|
74
|
+
try {
|
|
75
|
+
const entity: object = JSON.parse(entityString, customReviver);
|
|
76
|
+
entityCache.set(path, entity);
|
|
77
|
+
} catch (parseError) {
|
|
78
|
+
console.error(
|
|
79
|
+
`Failed to parse entity for path "${path}" from localStorage:`,
|
|
80
|
+
parseError
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error("Error accessing localStorage during initialization:", error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Saves data to the in-memory cache and persists it individually in `localStorage`.
|
|
93
|
+
* @param path - The unique path/key for the data.
|
|
94
|
+
* @param data - The data to cache and persist.
|
|
95
|
+
*/
|
|
96
|
+
export function saveEntityToCache(path: string, data: object): void {
|
|
97
|
+
// Update the in-memory cache
|
|
98
|
+
entityCache.set(path, data);
|
|
99
|
+
|
|
100
|
+
// Persist the data individually in localStorage
|
|
101
|
+
if (isLocalStorageAvailable) {
|
|
102
|
+
try {
|
|
103
|
+
const key = LOCAL_STORAGE_PREFIX + path;
|
|
104
|
+
const entityString = JSON.stringify(data, customReplacer);
|
|
105
|
+
localStorage.setItem(key, entityString);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(
|
|
108
|
+
`Failed to save entity for path "${path}" to localStorage:`,
|
|
109
|
+
error
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Retrieves an entity from the in-memory cache or `localStorage`.
|
|
117
|
+
* If the entity is not in the cache but exists in `localStorage`, it loads it into the cache.
|
|
118
|
+
* @param path - The unique path/key for the entity.
|
|
119
|
+
* @returns The cached entity or `undefined` if not found.
|
|
120
|
+
*/
|
|
121
|
+
export function getEntityFromCache(path: string): object | undefined {
|
|
122
|
+
|
|
123
|
+
// Attempt to retrieve the entity from the in-memory cache
|
|
124
|
+
if (entityCache.has(path)) {
|
|
125
|
+
return entityCache.get(path);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If not in the cache, attempt to load it from localStorage
|
|
129
|
+
if (isLocalStorageAvailable) {
|
|
130
|
+
try {
|
|
131
|
+
const key = LOCAL_STORAGE_PREFIX + path;
|
|
132
|
+
const entityString = localStorage.getItem(key);
|
|
133
|
+
if (entityString) {
|
|
134
|
+
const entity: object = JSON.parse(entityString, customReviver);
|
|
135
|
+
entityCache.set(path, entity); // Update the cache
|
|
136
|
+
return entity;
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(
|
|
140
|
+
`Failed to load entity for path "${path}" from localStorage:`,
|
|
141
|
+
error
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Entity not found
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function hasEntityInCache(path: string): boolean {
|
|
151
|
+
return entityCache.has(path);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Removes an entity from both the in-memory cache and `localStorage`.
|
|
156
|
+
* @param path - The unique path/key for the entity to remove.
|
|
157
|
+
*/
|
|
158
|
+
export function removeEntityFromCache(path: string): void {
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
console.debug("Removing entity from cache", path);
|
|
162
|
+
|
|
163
|
+
// Remove from the in-memory cache
|
|
164
|
+
entityCache.delete(path);
|
|
165
|
+
|
|
166
|
+
// Remove from localStorage
|
|
167
|
+
if (isLocalStorageAvailable) {
|
|
168
|
+
try {
|
|
169
|
+
const key = LOCAL_STORAGE_PREFIX + path;
|
|
170
|
+
localStorage.removeItem(key);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error(
|
|
173
|
+
`Failed to remove entity for path "${path}" from localStorage:`,
|
|
174
|
+
error
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Clears the entire in-memory cache and removes all related entities from `localStorage`.
|
|
182
|
+
*/
|
|
183
|
+
export function clearEntityCache(): void {
|
|
184
|
+
// Clear the in-memory cache
|
|
185
|
+
entityCache.clear();
|
|
186
|
+
|
|
187
|
+
// Remove all entities with the specified prefix from localStorage
|
|
188
|
+
if (isLocalStorageAvailable) {
|
|
189
|
+
try {
|
|
190
|
+
const keysToRemove: string[] = [];
|
|
191
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
192
|
+
const fullKey = localStorage.key(i);
|
|
193
|
+
if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
|
|
194
|
+
keysToRemove.push(fullKey);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Remove the keys after collecting them to avoid issues while iterating
|
|
199
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error("Failed to clear entity cache from localStorage:", error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
package/src/util/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./objects";
|
|
|
7
7
|
export * from "./paths";
|
|
8
8
|
export * from "./regexp";
|
|
9
9
|
export * from "./navigation_utils";
|
|
10
|
+
export * from "./entity_actions";
|
|
10
11
|
export * from "./useDebouncedCallback";
|
|
11
12
|
export * from "./property_utils";
|
|
12
13
|
export * from "./resolutions";
|
|
@@ -78,6 +78,7 @@ export function mergeCollection(target: EntityCollection,
|
|
|
78
78
|
modifyCollection?: (props: ModifyCollectionProps) => EntityCollection | void
|
|
79
79
|
): EntityCollection {
|
|
80
80
|
|
|
81
|
+
|
|
81
82
|
const subcollectionsMerged = joinCollectionLists(
|
|
82
83
|
target?.subcollections ?? [],
|
|
83
84
|
source?.subcollections ?? [],
|
|
@@ -94,7 +95,7 @@ export function mergeCollection(target: EntityCollection,
|
|
|
94
95
|
propertiesMerged[key] = source.properties[key] as PropertyOrBuilder;
|
|
95
96
|
});
|
|
96
97
|
|
|
97
|
-
const mergedCollection = mergeDeep(target, source);
|
|
98
|
+
const mergedCollection = mergeDeep(target, source, true);
|
|
98
99
|
const targetPropertiesOrder = getCollectionKeys(target);
|
|
99
100
|
const sourcePropertiesOrder = getCollectionKeys(source);
|
|
100
101
|
const mergedPropertiesOrder = [...new Set([...sourcePropertiesOrder, ...targetPropertiesOrder])];
|
|
@@ -115,6 +116,10 @@ export function mergeCollection(target: EntityCollection,
|
|
|
115
116
|
if (modifiedCollection)
|
|
116
117
|
resultCollection = modifiedCollection;
|
|
117
118
|
}
|
|
119
|
+
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
resultCollection["merged"] = true;
|
|
122
|
+
|
|
118
123
|
return resultCollection
|
|
119
124
|
}
|
|
120
125
|
|
|
@@ -11,22 +11,27 @@ export interface NavigationViewEntityInternal<M extends Record<string, any>> {
|
|
|
11
11
|
type: "entity";
|
|
12
12
|
entityId: string;
|
|
13
13
|
path: string;
|
|
14
|
+
fullPath: string;
|
|
14
15
|
parentCollection: EntityCollection<M>;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface NavigationViewCollectionInternal<M extends Record<string, any>> {
|
|
18
19
|
type: "collection";
|
|
20
|
+
id: string;
|
|
19
21
|
path: string;
|
|
22
|
+
fullPath: string;
|
|
20
23
|
collection: EntityCollection<M>;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export interface NavigationViewEntityCustomInternal<M extends Record<string, any>> {
|
|
24
27
|
type: "custom_view";
|
|
25
28
|
path: string;
|
|
29
|
+
fullPath: string;
|
|
30
|
+
entityId: string;
|
|
26
31
|
view: EntityCustomView<M>;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
export function
|
|
34
|
+
export function getNavigationEntriesFromPath(props: {
|
|
30
35
|
path: string,
|
|
31
36
|
collections: EntityCollection[] | undefined,
|
|
32
37
|
currentFullPath?: string,
|
|
@@ -46,7 +51,11 @@ export function getNavigationEntriesFromPathInternal(props: {
|
|
|
46
51
|
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
47
52
|
const subpathCombination = subpathCombinations[i];
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
let collection: EntityCollection<any> | undefined;
|
|
55
|
+
collection = collections && collections.find((entry) => entry.id === subpathCombination);
|
|
56
|
+
if (!collection) {
|
|
57
|
+
collection = collections && collections.find((entry) => entry.path === subpathCombination);
|
|
58
|
+
}
|
|
50
59
|
|
|
51
60
|
if (collection) {
|
|
52
61
|
const pathOrAlias = collection.id ?? collection.path;
|
|
@@ -56,7 +65,9 @@ export function getNavigationEntriesFromPathInternal(props: {
|
|
|
56
65
|
|
|
57
66
|
result.push({
|
|
58
67
|
type: "collection",
|
|
68
|
+
id: collection.id,
|
|
59
69
|
path: collectionPath,
|
|
70
|
+
fullPath: collectionPath,
|
|
60
71
|
collection
|
|
61
72
|
});
|
|
62
73
|
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
@@ -68,6 +79,7 @@ export function getNavigationEntriesFromPathInternal(props: {
|
|
|
68
79
|
type: "entity",
|
|
69
80
|
entityId,
|
|
70
81
|
path: collectionPath,
|
|
82
|
+
fullPath: fullPath,
|
|
71
83
|
parentCollection: collection
|
|
72
84
|
});
|
|
73
85
|
if (nextSegments.length > 1) {
|
|
@@ -81,16 +93,15 @@ export function getNavigationEntriesFromPathInternal(props: {
|
|
|
81
93
|
.filter(Boolean)
|
|
82
94
|
.find((entry) => entry!.key === newPath);
|
|
83
95
|
if (customView) {
|
|
84
|
-
const path = currentFullPath && currentFullPath.length > 0
|
|
85
|
-
? (currentFullPath + "/" + customView.key)
|
|
86
|
-
: customView.key;
|
|
87
96
|
result.push({
|
|
88
97
|
type: "custom_view",
|
|
89
|
-
path,
|
|
98
|
+
path: collectionPath,
|
|
99
|
+
entityId: entityId,
|
|
100
|
+
fullPath: fullPath + "/" + customView.key,
|
|
90
101
|
view: customView
|
|
91
102
|
});
|
|
92
103
|
} else if (collection.subcollections) {
|
|
93
|
-
result.push(...
|
|
104
|
+
result.push(...getNavigationEntriesFromPath({
|
|
94
105
|
path: newPath,
|
|
95
106
|
collections: collection.subcollections,
|
|
96
107
|
currentFullPath: fullPath,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityCollection } from "../types";
|
|
1
|
+
import { EntityCollection, NavigationController, SideEntityController } from "../types";
|
|
2
2
|
|
|
3
3
|
export function removeInitialAndTrailingSlashes(s: string): string {
|
|
4
4
|
return removeInitialSlash(removeTrailingSlash(s));
|
|
@@ -32,29 +32,75 @@ export function getLastSegment(path: string) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export function resolveCollectionPathIds(path: string, allCollections: EntityCollection[]): string {
|
|
35
|
-
|
|
36
35
|
const cleanPath = removeInitialAndTrailingSlashes(path);
|
|
37
36
|
const subpaths = cleanPath.split("/");
|
|
37
|
+
|
|
38
38
|
if (subpaths.length % 2 === 0) {
|
|
39
|
-
throw Error(`
|
|
39
|
+
throw Error(`resolveCollectionPathIds: Collection paths must have an odd number of segments: ${path}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check if the path exactly matches a collection path
|
|
43
|
+
const exactMatch = allCollections.find(col => col.path === cleanPath);
|
|
44
|
+
if (exactMatch) {
|
|
45
|
+
return exactMatch.path;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
if (subpaths.length === 1) {
|
|
49
|
+
// Find collection by ID and return its path
|
|
50
|
+
const aliasedCollection = allCollections.find((col) => col.id === subpaths[0]);
|
|
51
|
+
return aliasedCollection?.path ?? subpaths[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Try to match a multi-segment collection path
|
|
55
|
+
let matchingCollection: EntityCollection | undefined;
|
|
56
|
+
let entityIndex = 1;
|
|
57
|
+
|
|
58
|
+
// Check if the path starts with a multi-segment collection path
|
|
59
|
+
for (const collection of allCollections) {
|
|
60
|
+
const pathSegments = collection.path.split("/");
|
|
61
|
+
if (pathSegments.length > 1 &&
|
|
62
|
+
subpaths.slice(0, pathSegments.length).join("/") === collection.path) {
|
|
63
|
+
matchingCollection = collection;
|
|
64
|
+
entityIndex = pathSegments.length;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
46
67
|
}
|
|
47
68
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
// If no multi-segment match, fall back to single segment matching
|
|
70
|
+
if (!matchingCollection) {
|
|
71
|
+
const matchingCollections = allCollections.filter(col =>
|
|
72
|
+
col.id === subpaths[0] || col.path === subpaths[0]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (!matchingCollections.length) {
|
|
51
76
|
return cleanPath;
|
|
52
77
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else {
|
|
56
|
-
return resolvedAliased ?? cleanPath;
|
|
78
|
+
|
|
79
|
+
matchingCollection = matchingCollections[0];
|
|
57
80
|
}
|
|
81
|
+
|
|
82
|
+
const entityId = subpaths[entityIndex];
|
|
83
|
+
const remainingPath = subpaths.slice(entityIndex + 1);
|
|
84
|
+
|
|
85
|
+
// If we have a subcollection ID, try to resolve it
|
|
86
|
+
if (remainingPath.length > 0) {
|
|
87
|
+
const subcollectionId = remainingPath[0];
|
|
88
|
+
const subcollection = matchingCollection.subcollections?.find(
|
|
89
|
+
subcol => subcol.id === subcollectionId
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (subcollection) {
|
|
93
|
+
return `${matchingCollection.path}/${entityId}/${subcollection.path}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If there are no remaining path segments, just return the collection path with entity ID
|
|
98
|
+
if (remainingPath.length === 0) {
|
|
99
|
+
return `${matchingCollection.path}/${entityId}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Default case - couldn't match subcollection
|
|
103
|
+
return `${matchingCollection.path}/${entityId}/${remainingPath.join("/")}`;
|
|
58
104
|
}
|
|
59
105
|
|
|
60
106
|
/**
|
|
@@ -109,3 +155,58 @@ export function getCollectionPathsCombinations(subpaths: string[]): string[] {
|
|
|
109
155
|
return result;
|
|
110
156
|
|
|
111
157
|
}
|
|
158
|
+
|
|
159
|
+
export function navigateToEntity({
|
|
160
|
+
openEntityMode,
|
|
161
|
+
collection,
|
|
162
|
+
entityId,
|
|
163
|
+
copy,
|
|
164
|
+
path,
|
|
165
|
+
fullIdPath,
|
|
166
|
+
selectedTab,
|
|
167
|
+
sideEntityController,
|
|
168
|
+
onClose,
|
|
169
|
+
navigation
|
|
170
|
+
}:
|
|
171
|
+
|
|
172
|
+
{
|
|
173
|
+
openEntityMode: "side_panel" | "full_screen";
|
|
174
|
+
collection?: EntityCollection;
|
|
175
|
+
entityId?: string;
|
|
176
|
+
selectedTab?: string;
|
|
177
|
+
copy?: boolean;
|
|
178
|
+
path: string;
|
|
179
|
+
fullIdPath?: string;
|
|
180
|
+
sideEntityController: SideEntityController;
|
|
181
|
+
onClose?: () => void;
|
|
182
|
+
navigation: NavigationController
|
|
183
|
+
}) {
|
|
184
|
+
|
|
185
|
+
if (openEntityMode === "side_panel") {
|
|
186
|
+
|
|
187
|
+
sideEntityController.open({
|
|
188
|
+
entityId,
|
|
189
|
+
path: fullIdPath ?? path,
|
|
190
|
+
// fullIdPath,
|
|
191
|
+
copy,
|
|
192
|
+
selectedTab,
|
|
193
|
+
collection,
|
|
194
|
+
updateUrl: true,
|
|
195
|
+
onClose
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
} else {
|
|
199
|
+
let to = navigation.buildUrlCollectionPath(entityId ? `${fullIdPath ?? path}/${entityId}` : fullIdPath ?? path);
|
|
200
|
+
if (entityId && selectedTab) {
|
|
201
|
+
to += `/${selectedTab}`;
|
|
202
|
+
}
|
|
203
|
+
if (!entityId) {
|
|
204
|
+
to += "#new";
|
|
205
|
+
}
|
|
206
|
+
if (copy) {
|
|
207
|
+
to += "#copy";
|
|
208
|
+
}
|
|
209
|
+
navigation.navigate(to);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
}
|
package/src/util/objects.ts
CHANGED
|
@@ -12,13 +12,20 @@ export function isObject(item: any) {
|
|
|
12
12
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U): T & U {
|
|
15
|
+
export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U, ignoreUndefined: boolean = false): T & U {
|
|
16
16
|
const targetIsObject = isObject(target);
|
|
17
17
|
const output = targetIsObject ? { ...target } : target;
|
|
18
18
|
if (targetIsObject && isObject(source)) {
|
|
19
19
|
Object.keys(source).forEach(key => {
|
|
20
20
|
const sourceElement = source[key];
|
|
21
|
-
|
|
21
|
+
// Skip undefined values when ignoreUndefined is true
|
|
22
|
+
if (ignoreUndefined && sourceElement === undefined) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (sourceElement instanceof Date) {
|
|
26
|
+
// Assign a new Date instance with the same time value
|
|
27
|
+
Object.assign(output, { [key]: new Date(sourceElement.getTime()) });
|
|
28
|
+
} else if (isObject(sourceElement)) {
|
|
22
29
|
if (!(key in target))
|
|
23
30
|
Object.assign(output, { [key]: sourceElement });
|
|
24
31
|
else
|
|
@@ -22,11 +22,11 @@ export function getParentReferencesFromPath(props: {
|
|
|
22
22
|
|
|
23
23
|
const collection: EntityCollection<any> | undefined = collections && collections.find((entry) => entry.id === subpathCombination || entry.path === subpathCombination);
|
|
24
24
|
|
|
25
|
+
// If we find a collection, we add the reference and continue
|
|
25
26
|
if (collection) {
|
|
26
|
-
const pathOrAlias = collection.id ?? collection.path;
|
|
27
27
|
const collectionPath = currentFullPath && currentFullPath.length > 0
|
|
28
|
-
? (currentFullPath + "/" +
|
|
29
|
-
:
|
|
28
|
+
? (currentFullPath + "/" + collection.path)
|
|
29
|
+
: collection.path;
|
|
30
30
|
|
|
31
31
|
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
32
32
|
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
AuthController,
|
|
4
5
|
EntityCollection,
|
|
5
6
|
PropertiesOrBuilders,
|
|
6
7
|
PropertyConfig,
|
|
@@ -13,12 +14,15 @@ import { resolveProperty } from "./resolutions";
|
|
|
13
14
|
import { CircleIcon, FunctionsIcon } from "@firecms/ui";
|
|
14
15
|
import { getFieldConfig } from "../core";
|
|
15
16
|
|
|
16
|
-
export function isReferenceProperty(
|
|
17
|
-
|
|
17
|
+
export function isReferenceProperty(
|
|
18
|
+
authController: AuthController,
|
|
19
|
+
propertyOrBuilder: PropertyOrBuilder,
|
|
20
|
+
fields: Record<string, PropertyConfig>) {
|
|
18
21
|
const resolvedProperty = resolveProperty({
|
|
19
22
|
propertyKey: "ignore", // TODO
|
|
20
23
|
propertyOrBuilder,
|
|
21
|
-
propertyConfigs: fields
|
|
24
|
+
propertyConfigs: fields,
|
|
25
|
+
authController
|
|
22
26
|
});
|
|
23
27
|
if (!resolvedProperty) return null;
|
|
24
28
|
if (resolvedProperty.dataType === "reference") {
|
package/src/util/references.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { EntityCollection, PropertyConfig, ResolvedEntityCollection } from "../types";
|
|
1
|
+
import { AuthController, EntityCollection, PropertyConfig, ResolvedEntityCollection } from "../types";
|
|
2
2
|
import { isReferenceProperty } from "./property_utils";
|
|
3
3
|
import { isPropertyBuilder } from "./entities";
|
|
4
4
|
import { getFieldConfig } from "../core";
|
|
5
5
|
|
|
6
|
-
export function getEntityPreviewKeys(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
export function getEntityPreviewKeys(
|
|
7
|
+
authController: AuthController,
|
|
8
|
+
targetCollection: EntityCollection<any>,
|
|
9
|
+
fields: Record<string, PropertyConfig>,
|
|
10
|
+
previewProperties?: string[],
|
|
11
|
+
limit = 3) {
|
|
10
12
|
const allProperties = Object.keys(targetCollection.properties);
|
|
11
13
|
let listProperties = previewProperties?.filter(p => allProperties.includes(p as string));
|
|
12
14
|
if (!listProperties && targetCollection.previewProperties) {
|
|
@@ -18,7 +20,7 @@ export function getEntityPreviewKeys(targetCollection: EntityCollection<any>,
|
|
|
18
20
|
listProperties = allProperties;
|
|
19
21
|
return listProperties.filter(key => {
|
|
20
22
|
const propertyOrBuilder = targetCollection.properties[key];
|
|
21
|
-
return propertyOrBuilder && !isPropertyBuilder(propertyOrBuilder) && !isReferenceProperty(propertyOrBuilder, fields);
|
|
23
|
+
return propertyOrBuilder && !isPropertyBuilder(propertyOrBuilder) && !isReferenceProperty(authController, propertyOrBuilder, fields);
|
|
22
24
|
}).slice(0, limit);
|
|
23
25
|
}
|
|
24
26
|
}
|