@firecms/core 3.0.0-canary.168 → 3.0.0-canary.169
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/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 +4 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/index.d.ts +1 -1
- 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/EntityEditView.d.ts +25 -7
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/index.es.js +2344 -1305
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2344 -1306
- package/dist/index.umd.js.map +1 -1
- 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 +18 -6
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +3 -7
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +1 -6
- package/dist/types/navigation.d.ts +28 -12
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/side_entity_controller.d.ts +1 -1
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/navigation_from_path.d.ts +5 -1
- package/dist/util/navigation_utils.d.ts +12 -1
- package/package.json +5 -5
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +1 -1
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
- 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/EntityCollectionView/EntityCollectionView.tsx +79 -38
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -5
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -2
- 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 +24 -10
- package/src/components/common/index.ts +1 -1
- package/src/components/common/{useDataSourceEntityCollectionTableController.tsx → useDataSourceTableController.tsx} +122 -13
- 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 +2 -3
- package/src/core/DefaultAppBar.tsx +28 -4
- package/src/core/EntityEditView.tsx +457 -296
- package/src/core/EntitySidePanel.tsx +72 -6
- package/src/core/FireCMS.tsx +7 -4
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +15 -28
- package/src/core/SideDialogs.tsx +10 -5
- package/src/core/index.tsx +4 -2
- package/src/hooks/data/save.ts +1 -1
- 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/useBuildNavigationController.tsx +59 -21
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildSideDialogsController.tsx +4 -2
- package/src/internal/useBuildSideEntityController.tsx +157 -77
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +230 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +19 -6
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +3 -7
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +1 -7
- package/src/types/navigation.ts +35 -16
- package/src/types/plugins.tsx +1 -0
- package/src/types/side_entity_controller.tsx +1 -1
- package/src/util/entity_cache.ts +196 -0
- package/src/util/navigation_from_path.ts +10 -2
- package/src/util/navigation_utils.ts +53 -1
- package/src/util/parent_references_from_path.ts +3 -3
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -0,0 +1,196 @@
|
|
|
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, value: any): any {
|
|
14
|
+
// Handle Date objects
|
|
15
|
+
if (value instanceof Date) {
|
|
16
|
+
return { __type: "Date", value: value.toISOString() };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Handle EntityReference
|
|
20
|
+
if (value instanceof EntityReference) {
|
|
21
|
+
return { __type: "EntityReference", id: value.id, path: value.path };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle GeoPoint
|
|
25
|
+
if (value instanceof GeoPoint) {
|
|
26
|
+
return { __type: "GeoPoint", latitude: value.latitude, longitude: value.longitude };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Handle Vector
|
|
30
|
+
if (value instanceof Vector) {
|
|
31
|
+
return { __type: "Vector", value: value.value };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Define custom reviver for JSON.parse
|
|
38
|
+
function customReviver(key: string, value: any): any {
|
|
39
|
+
if (value && typeof value === "object" && "__type" in value) {
|
|
40
|
+
switch (value.__type) {
|
|
41
|
+
case "Date":
|
|
42
|
+
return new Date(value.value);
|
|
43
|
+
case "EntityReference":
|
|
44
|
+
return new EntityReference(value.id, value.path);
|
|
45
|
+
case "GeoPoint":
|
|
46
|
+
return new GeoPoint(value.latitude, value.longitude);
|
|
47
|
+
case "Vector":
|
|
48
|
+
return new Vector(value.value);
|
|
49
|
+
default:
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Initialize the in-memory cache by loading entities from `localStorage`
|
|
57
|
+
if (isLocalStorageAvailable) {
|
|
58
|
+
try {
|
|
59
|
+
// Iterate over all keys in localStorage to find those with the specified prefix
|
|
60
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
61
|
+
const fullKey = localStorage.key(i);
|
|
62
|
+
if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
|
|
63
|
+
const path = fullKey.substring(LOCAL_STORAGE_PREFIX.length);
|
|
64
|
+
const entityString = localStorage.getItem(fullKey);
|
|
65
|
+
if (entityString) {
|
|
66
|
+
try {
|
|
67
|
+
const entity: object = JSON.parse(entityString, customReviver);
|
|
68
|
+
entityCache.set(path, entity);
|
|
69
|
+
} catch (parseError) {
|
|
70
|
+
console.error(
|
|
71
|
+
`Failed to parse entity for path "${path}" from localStorage:`,
|
|
72
|
+
parseError
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Error accessing localStorage during initialization:", error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Saves data to the in-memory cache and persists it individually in `localStorage`.
|
|
85
|
+
* @param path - The unique path/key for the data.
|
|
86
|
+
* @param data - The data to cache and persist.
|
|
87
|
+
*/
|
|
88
|
+
export function saveEntityToCache(path: string, data: object): void {
|
|
89
|
+
// Update the in-memory cache
|
|
90
|
+
entityCache.set(path, data);
|
|
91
|
+
|
|
92
|
+
// Persist the data individually in localStorage
|
|
93
|
+
if (isLocalStorageAvailable) {
|
|
94
|
+
try {
|
|
95
|
+
const key = LOCAL_STORAGE_PREFIX + path;
|
|
96
|
+
const entityString = JSON.stringify(data, customReplacer);
|
|
97
|
+
localStorage.setItem(key, entityString);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(
|
|
100
|
+
`Failed to save entity for path "${path}" to localStorage:`,
|
|
101
|
+
error
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Retrieves an entity from the in-memory cache or `localStorage`.
|
|
109
|
+
* If the entity is not in the cache but exists in `localStorage`, it loads it into the cache.
|
|
110
|
+
* @param path - The unique path/key for the entity.
|
|
111
|
+
* @returns The cached entity or `undefined` if not found.
|
|
112
|
+
*/
|
|
113
|
+
export function getEntityFromCache(path: string): object | undefined {
|
|
114
|
+
|
|
115
|
+
// Attempt to retrieve the entity from the in-memory cache
|
|
116
|
+
if (entityCache.has(path)) {
|
|
117
|
+
return entityCache.get(path);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// If not in the cache, attempt to load it from localStorage
|
|
121
|
+
if (isLocalStorageAvailable) {
|
|
122
|
+
try {
|
|
123
|
+
const key = LOCAL_STORAGE_PREFIX + path;
|
|
124
|
+
const entityString = localStorage.getItem(key);
|
|
125
|
+
if (entityString) {
|
|
126
|
+
const entity: object = JSON.parse(entityString, customReviver);
|
|
127
|
+
entityCache.set(path, entity); // Update the cache
|
|
128
|
+
return entity;
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(
|
|
132
|
+
`Failed to load entity for path "${path}" from localStorage:`,
|
|
133
|
+
error
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Entity not found
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function hasEntityInCache(path: string): boolean {
|
|
143
|
+
return entityCache.has(path);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Removes an entity from both the in-memory cache and `localStorage`.
|
|
148
|
+
* @param path - The unique path/key for the entity to remove.
|
|
149
|
+
*/
|
|
150
|
+
export function removeEntityFromCache(path: string): void {
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
console.log("Removing entity from cache", path);
|
|
154
|
+
|
|
155
|
+
// Remove from the in-memory cache
|
|
156
|
+
entityCache.delete(path);
|
|
157
|
+
|
|
158
|
+
// Remove from localStorage
|
|
159
|
+
if (isLocalStorageAvailable) {
|
|
160
|
+
try {
|
|
161
|
+
const key = LOCAL_STORAGE_PREFIX + path;
|
|
162
|
+
localStorage.removeItem(key);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(
|
|
165
|
+
`Failed to remove entity for path "${path}" from localStorage:`,
|
|
166
|
+
error
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Clears the entire in-memory cache and removes all related entities from `localStorage`.
|
|
174
|
+
*/
|
|
175
|
+
export function clearEntityCache(): void {
|
|
176
|
+
// Clear the in-memory cache
|
|
177
|
+
entityCache.clear();
|
|
178
|
+
|
|
179
|
+
// Remove all entities with the specified prefix from localStorage
|
|
180
|
+
if (isLocalStorageAvailable) {
|
|
181
|
+
try {
|
|
182
|
+
const keysToRemove: string[] = [];
|
|
183
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
184
|
+
const fullKey = localStorage.key(i);
|
|
185
|
+
if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
|
|
186
|
+
keysToRemove.push(fullKey);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Remove the keys after collecting them to avoid issues while iterating
|
|
191
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error("Failed to clear entity cache from localStorage:", error);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -11,22 +11,26 @@ 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;
|
|
26
30
|
view: EntityCustomView<M>;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
export function
|
|
33
|
+
export function getNavigationEntriesFromPath(props: {
|
|
30
34
|
path: string,
|
|
31
35
|
collections: EntityCollection[] | undefined,
|
|
32
36
|
currentFullPath?: string,
|
|
@@ -56,7 +60,9 @@ export function getNavigationEntriesFromPathInternal(props: {
|
|
|
56
60
|
|
|
57
61
|
result.push({
|
|
58
62
|
type: "collection",
|
|
63
|
+
id: collection.id,
|
|
59
64
|
path: collectionPath,
|
|
65
|
+
fullPath: collectionPath,
|
|
60
66
|
collection
|
|
61
67
|
});
|
|
62
68
|
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
@@ -68,6 +74,7 @@ export function getNavigationEntriesFromPathInternal(props: {
|
|
|
68
74
|
type: "entity",
|
|
69
75
|
entityId,
|
|
70
76
|
path: collectionPath,
|
|
77
|
+
fullPath: fullPath,
|
|
71
78
|
parentCollection: collection
|
|
72
79
|
});
|
|
73
80
|
if (nextSegments.length > 1) {
|
|
@@ -87,10 +94,11 @@ export function getNavigationEntriesFromPathInternal(props: {
|
|
|
87
94
|
result.push({
|
|
88
95
|
type: "custom_view",
|
|
89
96
|
path,
|
|
97
|
+
fullPath: fullPath + "/" + path,
|
|
90
98
|
view: customView
|
|
91
99
|
});
|
|
92
100
|
} else if (collection.subcollections) {
|
|
93
|
-
result.push(...
|
|
101
|
+
result.push(...getNavigationEntriesFromPath({
|
|
94
102
|
path: newPath,
|
|
95
103
|
collections: collection.subcollections,
|
|
96
104
|
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));
|
|
@@ -109,3 +109,55 @@ export function getCollectionPathsCombinations(subpaths: string[]): string[] {
|
|
|
109
109
|
return result;
|
|
110
110
|
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
export function navigateToEntity({
|
|
114
|
+
openEntityMode,
|
|
115
|
+
collection,
|
|
116
|
+
entityId,
|
|
117
|
+
copy,
|
|
118
|
+
path,
|
|
119
|
+
selectedTab,
|
|
120
|
+
sideEntityController,
|
|
121
|
+
onClose,
|
|
122
|
+
navigation
|
|
123
|
+
}:
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
openEntityMode: "side_panel" | "full_screen";
|
|
127
|
+
collection?: EntityCollection;
|
|
128
|
+
entityId?: string;
|
|
129
|
+
selectedTab?: string;
|
|
130
|
+
copy?: boolean;
|
|
131
|
+
path: string;
|
|
132
|
+
sideEntityController: SideEntityController;
|
|
133
|
+
onClose?: () => void;
|
|
134
|
+
navigation: NavigationController
|
|
135
|
+
}) {
|
|
136
|
+
|
|
137
|
+
if (openEntityMode === "side_panel") {
|
|
138
|
+
|
|
139
|
+
sideEntityController.open({
|
|
140
|
+
entityId,
|
|
141
|
+
path,
|
|
142
|
+
copy,
|
|
143
|
+
selectedTab,
|
|
144
|
+
collection,
|
|
145
|
+
updateUrl: true,
|
|
146
|
+
onClose
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
} else {
|
|
150
|
+
let to = navigation.buildUrlCollectionPath(entityId ? `${path}/${entityId}` : path);
|
|
151
|
+
if (entityId && selectedTab) {
|
|
152
|
+
to += `/${selectedTab}`;
|
|
153
|
+
}
|
|
154
|
+
if (!entityId) {
|
|
155
|
+
to += "#new";
|
|
156
|
+
}
|
|
157
|
+
if (copy) {
|
|
158
|
+
to += "#copy";
|
|
159
|
+
}
|
|
160
|
+
navigation.navigate(to);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
}
|
|
@@ -23,10 +23,10 @@ export function getParentReferencesFromPath(props: {
|
|
|
23
23
|
const collection: EntityCollection<any> | undefined = collections && collections.find((entry) => entry.id === subpathCombination || entry.path === subpathCombination);
|
|
24
24
|
|
|
25
25
|
if (collection) {
|
|
26
|
-
const
|
|
26
|
+
const path = collection.path;
|
|
27
27
|
const collectionPath = currentFullPath && currentFullPath.length > 0
|
|
28
|
-
? (currentFullPath + "/" +
|
|
29
|
-
:
|
|
28
|
+
? (currentFullPath + "/" + path)
|
|
29
|
+
: path;
|
|
30
30
|
|
|
31
31
|
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
32
32
|
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
File without changes
|