@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.
Files changed (96) hide show
  1. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
  2. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  3. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
  4. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  5. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  6. package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
  7. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  8. package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
  9. package/dist/components/common/index.d.ts +1 -1
  10. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
  11. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  12. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  13. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  14. package/dist/core/EntityEditView.d.ts +25 -7
  15. package/dist/core/FireCMSRouter.d.ts +4 -0
  16. package/dist/core/NavigationRoutes.d.ts +0 -1
  17. package/dist/core/index.d.ts +2 -1
  18. package/dist/hooks/data/useEntityFetch.d.ts +2 -1
  19. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  20. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  21. package/dist/index.es.js +2344 -1305
  22. package/dist/index.es.js.map +1 -1
  23. package/dist/index.umd.js +2344 -1306
  24. package/dist/index.umd.js.map +1 -1
  25. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  26. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  27. package/dist/routes/FireCMSRoute.d.ts +1 -0
  28. package/dist/routes/HomePageRoute.d.ts +3 -0
  29. package/dist/types/collections.d.ts +18 -6
  30. package/dist/types/datasource.d.ts +2 -2
  31. package/dist/types/dialogs_controller.d.ts +3 -7
  32. package/dist/types/entities.d.ts +1 -1
  33. package/dist/types/entity_actions.d.ts +1 -6
  34. package/dist/types/navigation.d.ts +28 -12
  35. package/dist/types/plugins.d.ts +1 -0
  36. package/dist/types/side_entity_controller.d.ts +1 -1
  37. package/dist/util/entity_cache.d.ts +23 -0
  38. package/dist/util/navigation_from_path.d.ts +5 -1
  39. package/dist/util/navigation_utils.d.ts +12 -1
  40. package/package.json +5 -5
  41. package/src/components/CircularProgressCenter.tsx +1 -1
  42. package/src/components/DeleteEntityDialog.tsx +1 -1
  43. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
  44. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
  45. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
  46. package/src/components/EntityCollectionTable/index.tsx +1 -1
  47. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
  48. package/src/components/EntityCollectionView/EntityCollectionView.tsx +79 -38
  49. package/src/components/NotFoundPage.tsx +2 -2
  50. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -5
  51. package/src/components/SelectableTable/SelectableTable.tsx +18 -3
  52. package/src/components/UnsavedChangesDialog.tsx +42 -0
  53. package/src/components/VirtualTable/VirtualTable.tsx +19 -2
  54. package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
  55. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
  56. package/src/components/common/default_entity_actions.tsx +24 -10
  57. package/src/components/common/index.ts +1 -1
  58. package/src/components/common/{useDataSourceEntityCollectionTableController.tsx → useDataSourceTableController.tsx} +122 -13
  59. package/src/components/common/useDebouncedCallback.tsx +20 -0
  60. package/src/components/common/useScrollRestoration.tsx +68 -0
  61. package/src/contexts/BreacrumbsContext.tsx +38 -0
  62. package/src/contexts/DialogsProvider.tsx +2 -3
  63. package/src/core/DefaultAppBar.tsx +28 -4
  64. package/src/core/EntityEditView.tsx +457 -296
  65. package/src/core/EntitySidePanel.tsx +72 -6
  66. package/src/core/FireCMS.tsx +7 -4
  67. package/src/core/FireCMSRouter.tsx +17 -0
  68. package/src/core/NavigationRoutes.tsx +15 -28
  69. package/src/core/SideDialogs.tsx +10 -5
  70. package/src/core/index.tsx +4 -2
  71. package/src/hooks/data/save.ts +1 -1
  72. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  73. package/src/hooks/data/useEntityFetch.tsx +5 -1
  74. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  75. package/src/hooks/useBuildNavigationController.tsx +59 -21
  76. package/src/hooks/useLargeLayout.tsx +0 -35
  77. package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
  78. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  79. package/src/internal/useBuildSideEntityController.tsx +157 -77
  80. package/src/internal/useUnsavedChangesDialog.tsx +126 -92
  81. package/src/routes/CustomCMSRoute.tsx +21 -0
  82. package/src/routes/FireCMSRoute.tsx +230 -0
  83. package/src/routes/HomePageRoute.tsx +17 -0
  84. package/src/types/collections.ts +19 -6
  85. package/src/types/datasource.ts +2 -1
  86. package/src/types/dialogs_controller.tsx +3 -7
  87. package/src/types/entities.ts +1 -1
  88. package/src/types/entity_actions.tsx +1 -7
  89. package/src/types/navigation.ts +35 -16
  90. package/src/types/plugins.tsx +1 -0
  91. package/src/types/side_entity_controller.tsx +1 -1
  92. package/src/util/entity_cache.ts +196 -0
  93. package/src/util/navigation_from_path.ts +10 -2
  94. package/src/util/navigation_utils.ts +53 -1
  95. package/src/util/parent_references_from_path.ts +3 -3
  96. /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 getNavigationEntriesFromPathInternal(props: {
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(...getNavigationEntriesFromPathInternal({
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 pathOrAlias = collection.id ?? collection.path;
26
+ const path = collection.path;
27
27
  const collectionPath = currentFullPath && currentFullPath.length > 0
28
- ? (currentFullPath + "/" + pathOrAlias)
29
- : pathOrAlias;
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