@firecms/core 3.0.0-beta.11 → 3.0.0-beta.12

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 (173) hide show
  1. package/dist/components/ArrayContainer.d.ts +7 -3
  2. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
  3. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  4. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
  5. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  6. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  7. package/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
  8. package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
  9. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
  10. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  11. package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
  12. package/dist/components/common/default_entity_actions.d.ts +0 -2
  13. package/dist/components/common/index.d.ts +1 -1
  14. package/dist/components/common/useColumnsIds.d.ts +1 -0
  15. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
  16. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  17. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  18. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  19. package/dist/core/EntityEditView.d.ts +29 -21
  20. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  21. package/dist/core/FireCMSRouter.d.ts +4 -0
  22. package/dist/core/NavigationRoutes.d.ts +0 -1
  23. package/dist/core/SideDialogs.d.ts +4 -2
  24. package/dist/core/index.d.ts +2 -1
  25. package/dist/form/EntityForm.d.ts +45 -0
  26. package/dist/form/EntityFormActions.d.ts +17 -0
  27. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  28. package/dist/form/components/FormEntry.d.ts +6 -0
  29. package/dist/form/components/FormLayout.d.ts +5 -0
  30. package/dist/form/components/index.d.ts +2 -0
  31. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  32. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  33. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  34. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  35. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  36. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  37. package/dist/form/index.d.ts +16 -16
  38. package/dist/hooks/data/useEntityFetch.d.ts +2 -1
  39. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  40. package/dist/hooks/useBuildNavigationController.d.ts +4 -1
  41. package/dist/hooks/useModeController.d.ts +1 -2
  42. package/dist/index.es.js +18213 -16819
  43. package/dist/index.es.js.map +1 -1
  44. package/dist/index.umd.js +18164 -16771
  45. package/dist/index.umd.js.map +1 -1
  46. package/dist/internal/useBuildSideEntityController.d.ts +2 -2
  47. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  48. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  49. package/dist/routes/FireCMSRoute.d.ts +1 -0
  50. package/dist/routes/HomePageRoute.d.ts +3 -0
  51. package/dist/types/collections.d.ts +30 -10
  52. package/dist/types/datasource.d.ts +2 -2
  53. package/dist/types/dialogs_controller.d.ts +7 -3
  54. package/dist/types/entities.d.ts +1 -1
  55. package/dist/types/entity_actions.d.ts +10 -0
  56. package/dist/types/fields.d.ts +9 -2
  57. package/dist/types/navigation.d.ts +29 -12
  58. package/dist/types/plugins.d.ts +1 -0
  59. package/dist/types/properties.d.ts +18 -3
  60. package/dist/types/side_entity_controller.d.ts +6 -1
  61. package/dist/util/builders.d.ts +1 -1
  62. package/dist/util/entity_actions.d.ts +2 -0
  63. package/dist/util/entity_cache.d.ts +23 -0
  64. package/dist/util/index.d.ts +1 -0
  65. package/dist/util/navigation_from_path.d.ts +6 -1
  66. package/dist/util/navigation_utils.d.ts +12 -1
  67. package/dist/util/resolutions.d.ts +6 -1
  68. package/package.json +19 -21
  69. package/src/components/ArrayContainer.tsx +48 -29
  70. package/src/components/CircularProgressCenter.tsx +1 -1
  71. package/src/components/DeleteEntityDialog.tsx +1 -1
  72. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
  73. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
  74. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
  75. package/src/components/EntityCollectionTable/index.tsx +1 -1
  76. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
  77. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -11
  78. package/src/components/EntityCollectionView/EntityCollectionView.tsx +83 -41
  79. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
  80. package/src/components/NotFoundPage.tsx +2 -2
  81. package/src/components/PropertyIdCopyTooltip.tsx +2 -3
  82. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +11 -6
  83. package/src/components/SelectableTable/SelectableTable.tsx +18 -3
  84. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +7 -7
  85. package/src/components/UnsavedChangesDialog.tsx +42 -0
  86. package/src/components/VirtualTable/VirtualTable.tsx +19 -2
  87. package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
  88. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
  89. package/src/components/common/default_entity_actions.tsx +26 -37
  90. package/src/components/common/index.ts +1 -1
  91. package/src/components/common/useColumnsIds.tsx +1 -1
  92. package/src/components/common/useDataSourceTableController.tsx +420 -0
  93. package/src/components/common/useDebouncedCallback.tsx +20 -0
  94. package/src/components/common/useScrollRestoration.tsx +68 -0
  95. package/src/contexts/BreacrumbsContext.tsx +38 -0
  96. package/src/contexts/DialogsProvider.tsx +3 -2
  97. package/src/contexts/ModeController.tsx +1 -3
  98. package/src/core/DefaultAppBar.tsx +44 -14
  99. package/src/core/DefaultDrawer.tsx +1 -1
  100. package/src/core/EntityEditView.tsx +278 -1000
  101. package/src/core/EntityEditViewFormActions.tsx +199 -0
  102. package/src/core/EntitySidePanel.tsx +85 -15
  103. package/src/core/FireCMS.tsx +7 -4
  104. package/src/core/FireCMSRouter.tsx +17 -0
  105. package/src/core/NavigationRoutes.tsx +23 -32
  106. package/src/core/SideDialogs.tsx +20 -11
  107. package/src/core/index.tsx +4 -2
  108. package/src/form/EntityForm.tsx +774 -0
  109. package/src/form/EntityFormActions.tsx +169 -0
  110. package/src/form/PropertyFieldBinding.tsx +13 -11
  111. package/src/form/components/FormEntry.tsx +22 -0
  112. package/src/form/components/FormLayout.tsx +16 -0
  113. package/src/form/components/LabelWithIcon.tsx +6 -3
  114. package/src/form/components/StorageUploadProgress.tsx +1 -1
  115. package/src/form/components/index.tsx +2 -0
  116. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -4
  117. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
  118. package/src/form/field_bindings/BlockFieldBinding.tsx +4 -2
  119. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -0
  120. package/src/form/field_bindings/MapFieldBinding.tsx +14 -16
  121. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +9 -6
  122. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
  123. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
  124. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
  125. package/src/form/field_bindings/RepeatFieldBinding.tsx +5 -2
  126. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  127. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +6 -4
  128. package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -0
  129. package/src/form/field_bindings/TextFieldBinding.tsx +1 -0
  130. package/src/form/index.tsx +16 -32
  131. package/src/form/validation.ts +12 -6
  132. package/src/hooks/data/save.ts +1 -1
  133. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  134. package/src/hooks/data/useEntityFetch.tsx +5 -1
  135. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  136. package/src/hooks/useBuildModeController.tsx +15 -28
  137. package/src/hooks/useBuildNavigationController.tsx +70 -24
  138. package/src/hooks/useLargeLayout.tsx +0 -35
  139. package/src/hooks/useModeController.tsx +1 -2
  140. package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
  141. package/src/internal/useBuildSideDialogsController.tsx +3 -2
  142. package/src/internal/useBuildSideEntityController.tsx +124 -83
  143. package/src/internal/useUnsavedChangesDialog.tsx +126 -92
  144. package/src/preview/PropertyPreview.tsx +8 -8
  145. package/src/preview/components/UrlComponentPreview.tsx +17 -18
  146. package/src/preview/property_previews/MapPropertyPreview.tsx +3 -2
  147. package/src/routes/CustomCMSRoute.tsx +21 -0
  148. package/src/routes/FireCMSRoute.tsx +235 -0
  149. package/src/routes/HomePageRoute.tsx +17 -0
  150. package/src/types/collections.ts +38 -13
  151. package/src/types/datasource.ts +2 -1
  152. package/src/types/dialogs_controller.tsx +7 -3
  153. package/src/types/entities.ts +1 -1
  154. package/src/types/entity_actions.tsx +12 -0
  155. package/src/types/fields.tsx +13 -2
  156. package/src/types/navigation.ts +36 -16
  157. package/src/types/plugins.tsx +1 -0
  158. package/src/types/properties.ts +21 -3
  159. package/src/types/side_dialogs_controller.tsx +2 -0
  160. package/src/types/side_entity_controller.tsx +6 -1
  161. package/src/util/builders.ts +5 -3
  162. package/src/util/entity_actions.ts +28 -0
  163. package/src/util/entity_cache.ts +204 -0
  164. package/src/util/index.ts +1 -0
  165. package/src/util/join_collections.ts +3 -0
  166. package/src/util/navigation_from_path.ts +13 -6
  167. package/src/util/navigation_utils.ts +53 -1
  168. package/src/util/objects.ts +4 -1
  169. package/src/util/parent_references_from_path.ts +3 -3
  170. package/src/util/resolutions.ts +26 -5
  171. package/src/util/useStorageUploadController.tsx +39 -21
  172. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
  173. /package/src/util/{common.tsx → common.ts} +0 -0
@@ -28,7 +28,7 @@ import {
28
28
  import { useClearRestoreValue } from "../useClearRestoreValue";
29
29
 
30
30
  const dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent min-h-[254px] outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
31
- const disabledClasses = "border-dotted-gray"
31
+ const disabledClasses = fieldBackgroundDisabledMixin;
32
32
  const nonActiveDropClasses = fieldBackgroundHoverMixin
33
33
  const activeDropClasses = "pt-0 border-2 border-solid"
34
34
  const acceptDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-green-500"
@@ -103,7 +103,7 @@ export function StorageUploadFieldBinding({
103
103
  icon={getIconForProperty(property, "small")}
104
104
  required={property.validation?.required}
105
105
  title={property.name}
106
- className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
106
+ className={"h-8text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
107
107
 
108
108
  <StorageUpload
109
109
  value={internalValue}
@@ -206,6 +206,7 @@ function FileDropComponent({
206
206
  className={cls(
207
207
  fieldBackgroundMixin,
208
208
  disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
209
+ disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : "",
209
210
  dropZoneClasses,
210
211
  multipleFilesSupported && internalValue.length ? "" : "flex",
211
212
  {
@@ -248,7 +249,7 @@ function FileDropComponent({
248
249
  metadata={metadata}
249
250
  storagePath={storagePathBuilder(entry.file)}
250
251
  onFileUploadComplete={onFileUploadComplete}
251
- imageSize={size === "medium" ? 220 : 118}
252
+ imageSize={size === "large" ? 220 : 118}
252
253
  simple={false}
253
254
  />
254
255
  );
@@ -285,7 +286,8 @@ function FileDropComponent({
285
286
  <div
286
287
  className="flex-grow min-h-[38px] box-border m-2 text-center">
287
288
  <Typography align={"center"}
288
- variant={"label"}>
289
+ variant={"label"}
290
+ className={disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : ""}>
289
291
  {helpText}
290
292
  </Typography>
291
293
  </div>
@@ -43,6 +43,7 @@ export const SwitchFieldBinding = function SwitchFieldBinding({
43
43
  value={value}
44
44
  onValueChange={(v) => setValue(v)}
45
45
  error={showError}
46
+ className={property.widthPercentage !== undefined ? "mt-8" : undefined}
46
47
  label={<LabelWithIcon
47
48
  icon={getIconForProperty(property, "small")}
48
49
  required={property.validation?.required}
@@ -75,6 +75,7 @@ export function TextFieldBinding<T extends string | number>({
75
75
  value={value}
76
76
  onChange={onChange}
77
77
  autoFocus={autoFocus}
78
+ className={property.widthPercentage !== undefined ? "mt-8" : undefined}
78
79
  label={<LabelWithIcon
79
80
  icon={getIconForProperty(property, "small")}
80
81
  required={property.validation?.required}
@@ -1,36 +1,20 @@
1
- import { SelectFieldBinding } from "./field_bindings/SelectFieldBinding";
2
- import { MultiSelectFieldBinding } from "./field_bindings/MultiSelectFieldBinding";
3
- import { ArrayOfReferencesFieldBinding } from "./field_bindings/ArrayOfReferencesFieldBinding";
4
- import { StorageUploadFieldBinding } from "./field_bindings/StorageUploadFieldBinding";
5
- import { TextFieldBinding } from "./field_bindings/TextFieldBinding";
6
- import { SwitchFieldBinding } from "./field_bindings/SwitchFieldBinding";
7
- import { DateTimeFieldBinding } from "./field_bindings/DateTimeFieldBinding";
8
- import { ReferenceFieldBinding } from "./field_bindings/ReferenceFieldBinding";
9
- import { MapFieldBinding } from "./field_bindings/MapFieldBinding";
10
- import { KeyValueFieldBinding } from "./field_bindings/KeyValueFieldBinding";
11
- import { RepeatFieldBinding } from "./field_bindings/RepeatFieldBinding";
12
- import { BlockFieldBinding } from "./field_bindings/BlockFieldBinding";
13
- import { ReadOnlyFieldBinding } from "./field_bindings/ReadOnlyFieldBinding";
14
- import { MarkdownEditorFieldBinding } from "./field_bindings/MarkdownEditorFieldBinding";
15
- import { ArrayCustomShapedFieldBinding } from "./field_bindings/ArrayCustomShapedFieldBinding";
1
+ export * from "./EntityForm";
16
2
 
17
- export {
18
- ArrayCustomShapedFieldBinding,
19
- RepeatFieldBinding,
20
- MultiSelectFieldBinding,
21
- ArrayOfReferencesFieldBinding,
22
- BlockFieldBinding,
23
- DateTimeFieldBinding,
24
- ReadOnlyFieldBinding,
25
- MapFieldBinding,
26
- KeyValueFieldBinding,
27
- ReferenceFieldBinding,
28
- SelectFieldBinding,
29
- StorageUploadFieldBinding,
30
- SwitchFieldBinding,
31
- MarkdownEditorFieldBinding,
32
- TextFieldBinding
33
- };
3
+ export { SelectFieldBinding } from "./field_bindings/SelectFieldBinding";
4
+ export { MultiSelectFieldBinding } from "./field_bindings/MultiSelectFieldBinding";
5
+ export { ArrayOfReferencesFieldBinding } from "./field_bindings/ArrayOfReferencesFieldBinding";
6
+ export { StorageUploadFieldBinding } from "./field_bindings/StorageUploadFieldBinding";
7
+ export { TextFieldBinding } from "./field_bindings/TextFieldBinding";
8
+ export { SwitchFieldBinding } from "./field_bindings/SwitchFieldBinding";
9
+ export { DateTimeFieldBinding } from "./field_bindings/DateTimeFieldBinding";
10
+ export { ReferenceFieldBinding } from "./field_bindings/ReferenceFieldBinding";
11
+ export { MapFieldBinding } from "./field_bindings/MapFieldBinding";
12
+ export { KeyValueFieldBinding } from "./field_bindings/KeyValueFieldBinding";
13
+ export { RepeatFieldBinding } from "./field_bindings/RepeatFieldBinding";
14
+ export { BlockFieldBinding } from "./field_bindings/BlockFieldBinding";
15
+ export { ReadOnlyFieldBinding } from "./field_bindings/ReadOnlyFieldBinding";
16
+ export { MarkdownEditorFieldBinding } from "./field_bindings/MarkdownEditorFieldBinding";
17
+ export { ArrayCustomShapedFieldBinding } from "./field_bindings/ArrayCustomShapedFieldBinding";
34
18
 
35
19
  export * from "./components";
36
20
 
@@ -90,11 +90,11 @@ export function mapPropertyToYup<T extends CMSType>(propertyContext: PropertyCon
90
90
  }
91
91
 
92
92
  export function getYupMapObjectSchema({
93
- property,
94
- entityId,
95
- customFieldValidator,
96
- name
97
- }: PropertyContext<Record<string, any>>): ObjectSchema<any> {
93
+ property,
94
+ entityId,
95
+ customFieldValidator,
96
+ name
97
+ }: PropertyContext<Record<string, any>>): ObjectSchema<any> {
98
98
  const objectSchema: any = {};
99
99
  const validation = property.validation;
100
100
  if (property.properties)
@@ -159,7 +159,13 @@ function getYupStringSchema({
159
159
  if (validation.lowercase) collection = collection.lowercase();
160
160
  if (validation.uppercase) collection = collection.uppercase();
161
161
  if (property.email) collection = collection.email(`${property.name} must be an email`);
162
- if (property.url) collection = collection.url(`${property.name} must be a url`);
162
+ if (property.url) {
163
+ if (!property.storage || property.storage?.storeUrl) {
164
+ collection = collection.url(`${property.name} must be a url`);
165
+ } else {
166
+ console.warn(`Property ${property.name} has a url validation but its storage configuration is not set to store urls`);
167
+ }
168
+ }
163
169
  } else {
164
170
  collection = collection.notRequired().nullable(true);
165
171
  }
@@ -75,7 +75,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
75
75
 
76
76
  const customizationController = context.customizationController;
77
77
 
78
- const resolvedPath = context.navigation.resolveAliasesFrom(path);
78
+ const resolvedPath = context.navigation.resolveIdsFrom(path);
79
79
 
80
80
  const callbacks = collection.callbacks;
81
81
  if (callbacks?.onPreSave) {
@@ -73,7 +73,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
73
73
  const dataSource = useDataSource(collection);
74
74
  const navigationController = useNavigationController();
75
75
 
76
- const path = navigationController.resolveAliasesFrom(inputPath);
76
+ const path = navigationController.resolveIdsFrom(inputPath);
77
77
 
78
78
  const sortByProperty = sortBy ? sortBy[0] : undefined;
79
79
  const currentSort = sortBy ? sortBy[1] : undefined;
@@ -10,6 +10,7 @@ import { useFireCMSContext } from "../useFireCMSContext";
10
10
  export interface EntityFetchProps<M extends Record<string, any>, USER extends User = User> {
11
11
  path: string;
12
12
  entityId?: string;
13
+ databaseId?: string;
13
14
  collection: EntityCollection<M, USER>;
14
15
  useCache?: boolean;
15
16
  }
@@ -40,13 +41,14 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
40
41
  path: inputPath,
41
42
  entityId,
42
43
  collection,
44
+ databaseId,
43
45
  useCache = false
44
46
  }: EntityFetchProps<M, USER>): EntityFetchResult<M> {
45
47
 
46
48
  const dataSource = useDataSource(collection);
47
49
  const navigationController = useNavigationController();
48
50
 
49
- const path = navigationController.resolveAliasesFrom(inputPath);
51
+ const path = navigationController.resolveIdsFrom(inputPath);
50
52
 
51
53
  const context: FireCMSContext<USER> = useFireCMSContext();
52
54
 
@@ -96,6 +98,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
96
98
  return dataSource.listenEntity<M>({
97
99
  path,
98
100
  entityId,
101
+ databaseId,
99
102
  collection,
100
103
  onUpdate: onEntityUpdate,
101
104
  onError
@@ -104,6 +107,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
104
107
  dataSource.fetchEntity<M>({
105
108
  path,
106
109
  entityId,
110
+ databaseId,
107
111
  collection
108
112
  })
109
113
  .then(onEntityUpdate)
@@ -0,0 +1,31 @@
1
+ import { useContext } from "react";
2
+ import { BreadcrumbContext } from "../contexts/BreacrumbsContext";
3
+
4
+ /**
5
+ * @group Hooks and utilities
6
+ */
7
+ export interface BreadcrumbsController {
8
+ breadcrumbs: BreadcrumbEntry[];
9
+ set: (props: {
10
+ breadcrumbs: BreadcrumbEntry[];
11
+ }) => void;
12
+ }
13
+
14
+ /**
15
+ * @group Hooks and utilities
16
+ */
17
+ export interface BreadcrumbEntry {
18
+ title: string;
19
+ url: string;
20
+ }
21
+
22
+ /**
23
+ * Hook to retrieve the BreadcrumbsController.
24
+ *
25
+ * Consider that in order to use this hook you need to have a parent
26
+ * `FireCMS`
27
+ *
28
+ * @see BreadcrumbsController
29
+ * @group Hooks and utilities
30
+ */
31
+ export const useBreadcrumbsController = (): BreadcrumbsController => useContext(BreadcrumbContext);
@@ -16,7 +16,7 @@ export function useBuildModeController(): ModeController {
16
16
  }, []);
17
17
 
18
18
  const prefersDarkModeStorage: boolean | null = localStorage.getItem("prefers-dark-mode") != null ? localStorage.getItem("prefers-dark-mode") === "true" : null;
19
- const prefersDarkMode = prefersDarkModeStorage ?? prefersDarkModeQuery;
19
+ const prefersDarkMode = prefersDarkModeStorage ?? prefersDarkModeQuery();
20
20
  const [mode, setMode] = useState<"light" | "dark">(prefersDarkMode ? "dark" : "light");
21
21
 
22
22
  useEffect(() => {
@@ -24,43 +24,30 @@ export function useBuildModeController(): ModeController {
24
24
  setDocumentMode(prefersDarkMode ? "dark" : "light");
25
25
  }, [prefersDarkMode]);
26
26
 
27
- // color-scheme: dark;
28
- const setDarkMode = useCallback(() => {
29
- setMode("dark");
30
- setDocumentMode("dark");
31
- }, []);
32
-
33
- const setLightMode = useCallback(() => {
34
- setMode("light");
35
- setDocumentMode("light");
36
- }, []);
37
-
38
27
  const setDocumentMode = (mode: "light" | "dark") => {
39
28
  document.body.style.setProperty("color-scheme", mode);
40
29
  document.documentElement.dataset.theme = mode;
41
30
  };
42
31
 
43
- const toggleMode = useCallback(() => {
44
-
45
- const prefersDarkModeQueryResult = prefersDarkModeQuery();
32
+ const setModeInternal = useCallback((mode: "light" | "dark" | "system") => {
46
33
  if (mode === "light") {
47
- if (!prefersDarkModeQueryResult)
48
- localStorage.setItem("prefers-dark-mode", "true");
49
- else
50
- localStorage.removeItem("prefers-dark-mode");
51
- setDarkMode();
34
+ setDocumentMode("light");
35
+ localStorage.setItem("prefers-dark-mode", "false");
36
+ setMode("light");
37
+ } else if (mode === "dark") {
38
+ setDocumentMode("dark");
39
+ localStorage.setItem("prefers-dark-mode", "true");
40
+ setMode("dark");
52
41
  } else {
53
- if (prefersDarkModeQueryResult)
54
- localStorage.setItem("prefers-dark-mode", "false");
55
- else
56
- localStorage.removeItem("prefers-dark-mode");
57
- setLightMode();
42
+ const preferredMode = prefersDarkModeQuery() ? "dark" : "light";
43
+ setDocumentMode(preferredMode);
44
+ localStorage.removeItem("prefers-dark-mode");
45
+ setMode(preferredMode);
58
46
  }
59
- }, [mode, prefersDarkModeQuery]);
47
+ }, [prefersDarkModeQuery]);
60
48
 
61
49
  return {
62
50
  mode,
63
- setMode,
64
- toggleMode
51
+ setMode: setModeInternal
65
52
  };
66
53
  }
@@ -9,6 +9,7 @@ import {
9
9
  EntityCollection,
10
10
  EntityCollectionsBuilder,
11
11
  EntityReference,
12
+ NavigationBlocker,
12
13
  NavigationController,
13
14
  PermissionsBuilder,
14
15
  TopNavigationEntry,
@@ -26,6 +27,7 @@ import {
26
27
  resolvePermissions
27
28
  } from "../util";
28
29
  import { getParentReferencesFromPath } from "../util/parent_references_from_path";
30
+ import { useBlocker, useNavigate } from "react-router-dom";
29
31
 
30
32
  const DEFAULT_BASE_PATH = "/";
31
33
  const DEFAULT_COLLECTION_PATH = "/c";
@@ -49,6 +51,11 @@ export type BuildNavigationContextProps<EC extends EntityCollection, USER extend
49
51
  * @param collections
50
52
  */
51
53
  injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
54
+
55
+ /**
56
+ * If true, the navigation logic will not be updated until this flag is false
57
+ */
58
+ disabled?: boolean;
52
59
  };
53
60
 
54
61
  export function useBuildNavigationController<EC extends EntityCollection, USER extends User>(props: BuildNavigationContextProps<EC, USER>): NavigationController {
@@ -63,9 +70,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
63
70
  viewsOrder,
64
71
  userConfigPersistence,
65
72
  dataSourceDelegate,
66
- injectCollections
73
+ injectCollections,
74
+ disabled
67
75
  } = props;
68
76
 
77
+ const navigate = useNavigate();
78
+
69
79
  const collectionsRef = useRef<EntityCollection[] | undefined>();
70
80
  const viewsRef = useRef<CMSView[] | undefined>();
71
81
  const adminViewsRef = useRef<CMSView[] | undefined>();
@@ -185,7 +195,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
185
195
 
186
196
  const refreshNavigation = useCallback(async () => {
187
197
 
188
- if (authController.initialLoading)
198
+ if (disabled || authController.initialLoading)
189
199
  return;
190
200
 
191
201
  console.debug("Refreshing navigation");
@@ -202,7 +212,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
202
212
  let shouldUpdateTopLevelNav = false;
203
213
  if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
204
214
  collectionsRef.current = resolvedCollections;
205
- console.log("Collections have changed", resolvedCollections);
215
+ console.debug("Collections have changed", resolvedCollections);
206
216
  shouldUpdateTopLevelNav = true;
207
217
  }
208
218
  if (collectionsRef.current === undefined) {
@@ -237,6 +247,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
237
247
  collectionPermissions,
238
248
  authController.user,
239
249
  authController.initialLoading,
250
+ disabled,
240
251
  viewsProp,
241
252
  adminViewsProp,
242
253
  computeTopNavigation,
@@ -332,16 +343,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
332
343
  throw Error("Expected path starting with " + fullCollectionPath);
333
344
  }, [fullCollectionPath]);
334
345
 
335
- const buildUrlEditCollectionPath = useCallback(({
336
- path
337
- }: {
338
- path: string
339
- }): string => {
340
- return `s/edit/${encodePath(path)}`;
341
- },
342
- []);
343
-
344
- const resolveAliasesFrom = useCallback((path: string): string => {
346
+ const resolveIdsFrom = useCallback((path: string): string => {
345
347
  const collections = collectionsRef.current ?? [];
346
348
  return resolveCollectionPathIds(path, collections);
347
349
  }, []);
@@ -401,24 +403,16 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
401
403
  isUrlCollectionPath,
402
404
  urlPathToDataPath,
403
405
  buildUrlCollectionPath,
404
- buildUrlEditCollectionPath,
405
- buildCMSUrlPath,
406
- resolveAliasesFrom,
406
+ resolveIdsFrom,
407
407
  topLevelNavigation,
408
408
  refreshNavigation,
409
409
  getParentReferencesFromPath: getAllParentReferencesForPath,
410
410
  getParentCollectionIds,
411
- convertIdsToPaths
411
+ convertIdsToPaths,
412
+ navigate,
412
413
  };
413
414
  }
414
415
 
415
- export function getSidePanelKey(path: string, entityId?: string) {
416
- if (entityId)
417
- return `${removeInitialAndTrailingSlashes(path)}/${removeInitialAndTrailingSlashes(entityId)}`;
418
- else
419
- return removeInitialAndTrailingSlashes(path);
420
- }
421
-
422
416
  function encodePath(input: string) {
423
417
  return encodeURIComponent(removeInitialAndTrailingSlashes(input))
424
418
  .replaceAll("%2F", "/")
@@ -427,6 +421,7 @@ function encodePath(input: string) {
427
421
 
428
422
  function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
429
423
  return resolvedCollections
424
+ .filter((c) => Boolean(c.path))
430
425
  .filter((c) => {
431
426
  if (!c.permissions) return true;
432
427
  const resolvedPermissions = resolvePermissions(c, authController, c.path, null);
@@ -513,3 +508,54 @@ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
513
508
  }
514
509
  return equal(removeFunctions(restA), removeFunctions(restB));
515
510
  }
511
+
512
+ function useCustomBlocker(): NavigationBlocker {
513
+ const [blockListeners, setBlockListeners] = useState<Record<string, {
514
+ block: boolean,
515
+ basePath?: string
516
+ }>>({});
517
+
518
+ const shouldBlock = Object.values(blockListeners).some(b => b.block);
519
+
520
+ let blocker: any;
521
+ try {
522
+ blocker = useBlocker(({
523
+ nextLocation
524
+ }) => {
525
+ const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
526
+ if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
527
+ return false;
528
+ return shouldBlock;
529
+ });
530
+ } catch (e) {
531
+ console.warn("Blocker not available, navigation will not be blocked");
532
+ }
533
+
534
+ const updateBlockListener = (path: string, block: boolean, basePath?: string) => {
535
+ setBlockListeners(prev => ({
536
+ ...prev,
537
+ [path]: {
538
+ block,
539
+ basePath
540
+ }
541
+ }));
542
+ return () => setBlockListeners(prev => {
543
+ const {
544
+ [path]: removed,
545
+ ...rest
546
+ } = prev;
547
+ return rest;
548
+ })
549
+ };
550
+
551
+ const isBlocked = (path: string) => {
552
+ return (blockListeners[path]?.block ?? false) && blocker?.state === "blocked";
553
+ }
554
+
555
+ return {
556
+ updateBlockListener,
557
+ isBlocked,
558
+ proceed: blocker?.proceed,
559
+ reset: blocker?.reset
560
+ }
561
+ }
@@ -63,38 +63,3 @@ function checkLargeLayout(breakpoint: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" |
63
63
  return false;
64
64
  return window.matchMedia(`(min-width: ${breakpoints[breakpoint] + 1}px)`).matches;
65
65
  }
66
-
67
- // import { useEffect, useState } from "react";
68
- //
69
- // const breakpoints = {
70
- // xs: 0,
71
- // sm: 640,
72
- // md: 768,
73
- // lg: 1024,
74
- // xl: 1280,
75
- // "2xl": 1536,
76
- // "3xl": 1920
77
- // }
78
- // export const useLargeLayout = (breakpoint: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" = "lg"): boolean => {
79
- // const [isLargeLayout, setIsLargeLayout] = useState<boolean>(false);
80
- //
81
- // useEffect(() => {
82
- // const handleResize = () => {
83
- // const matched = window.matchMedia(`(min-width: ${breakpoints[breakpoint] + 1}px)`).matches;
84
- // setIsLargeLayout(matched);
85
- // };
86
- //
87
- // // Set initial state
88
- // handleResize();
89
- //
90
- // // Set up event listener for resize events
91
- // window.addEventListener("resize", handleResize);
92
- //
93
- // // Clean up event listener when component unmounts
94
- // return () => {
95
- // window.removeEventListener("resize", handleResize);
96
- // };
97
- // }, []);
98
- //
99
- // return isLargeLayout;
100
- // };
@@ -7,8 +7,7 @@ import { useContext } from "react";
7
7
  */
8
8
  export interface ModeController {
9
9
  mode: "light" | "dark";
10
- setMode: (mode: "light" | "dark") => void;
11
- toggleMode: () => void;
10
+ setMode: (mode: "light" | "dark" | "system") => void;
12
11
  }
13
12
 
14
13
  /**
@@ -1,6 +1,6 @@
1
1
  import { Entity, EntityCollection, EntityCustomView, FireCMSContext, User } from "../types";
2
2
  import { useEffect, useState } from "react";
3
- import { getNavigationEntriesFromPathInternal } from "../util/navigation_from_path";
3
+ import { getNavigationEntriesFromPath } from "../util/navigation_from_path";
4
4
  import { useFireCMSContext } from "./useFireCMSContext";
5
5
 
6
6
  /**
@@ -67,10 +67,10 @@ export function resolveNavigationFrom<M extends Record<string, any>, USER extend
67
67
  const navigation = context.navigation;
68
68
 
69
69
  if (!navigation) {
70
- throw Error("Calling getNavigationFrom, but main navigation has not yet been initialised");
70
+ throw Error("Calling resolveNavigationFrom, but main navigation has not yet been initialised");
71
71
  }
72
72
 
73
- const navigationEntries = getNavigationEntriesFromPathInternal({
73
+ const navigationEntries = getNavigationEntriesFromPath({
74
74
  path,
75
75
  collections: navigation.collections ?? []
76
76
  });
@@ -143,9 +143,7 @@ export function useResolvedNavigationFrom<M extends Record<string, any>, USER ex
143
143
  setDataLoading(true);
144
144
  setDataLoadingError(undefined);
145
145
  resolveNavigationFrom<M, USER>({ path, context })
146
- .then((res) => {
147
- setData(res);
148
- })
146
+ .then(setData)
149
147
  .catch((e) => setDataLoadingError(e))
150
148
  .finally(() => setDataLoading(false));
151
149
  }
@@ -25,8 +25,9 @@ export function useBuildSideDialogsController(): SideDialogsController {
25
25
  const newPanels = panelKeys
26
26
  .map(key => routesStore.current[key])
27
27
  .filter(p => Boolean(p)) as SideDialogPanelProps[];
28
- if (!equal(sidePanelsRef.current.map(p => p.key), newPanels.map(p => p.key)))
28
+ if (!equal(sidePanelsRef.current.map(p => p.key), newPanels.map(p => p.key))) {
29
29
  updateSidePanels(newPanels);
30
+ }
30
31
  }, [location]);
31
32
 
32
33
  const close = useCallback(() => {
@@ -39,7 +40,7 @@ export function useBuildSideDialogsController(): SideDialogsController {
39
40
  updateSidePanels(updatedPanels);
40
41
 
41
42
  if (routesCount.current > 0) {
42
- if (lastSidePanel.urlPath)
43
+ if (lastSidePanel.urlPath) // if it has a url path, we need to navigate back, don't remove this code
43
44
  navigate(-1);
44
45
  routesCount.current--;
45
46
  } else if (lastSidePanel.parentUrlPath) {