@firecms/core 3.0.0-beta.10 → 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 (267) hide show
  1. package/dist/components/ArrayContainer.d.ts +22 -6
  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 +17 -3
  5. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  6. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +6 -3
  7. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  8. package/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
  9. package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
  10. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
  11. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  12. package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
  13. package/dist/components/common/default_entity_actions.d.ts +0 -2
  14. package/dist/components/common/index.d.ts +1 -1
  15. package/dist/components/common/useColumnsIds.d.ts +1 -0
  16. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
  17. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  18. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  19. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  20. package/dist/core/EntityEditView.d.ts +30 -22
  21. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  22. package/dist/core/FireCMS.d.ts +1 -1
  23. package/dist/core/FireCMSRouter.d.ts +4 -0
  24. package/dist/core/NavigationRoutes.d.ts +0 -1
  25. package/dist/core/SideDialogs.d.ts +4 -2
  26. package/dist/core/index.d.ts +2 -1
  27. package/dist/form/EntityForm.d.ts +45 -0
  28. package/dist/form/EntityFormActions.d.ts +17 -0
  29. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  30. package/dist/form/components/FormEntry.d.ts +6 -0
  31. package/dist/form/components/FormLayout.d.ts +5 -0
  32. package/dist/form/components/index.d.ts +2 -1
  33. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  34. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  35. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  36. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  37. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  38. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  39. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  40. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
  41. package/dist/form/index.d.ts +16 -16
  42. package/dist/hooks/data/delete.d.ts +4 -4
  43. package/dist/hooks/data/save.d.ts +3 -3
  44. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  45. package/dist/hooks/data/useEntityFetch.d.ts +4 -3
  46. package/dist/hooks/useAuthController.d.ts +1 -1
  47. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  48. package/dist/hooks/useBuildNavigationController.d.ts +7 -4
  49. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  50. package/dist/hooks/useModeController.d.ts +1 -2
  51. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  52. package/dist/hooks/useValidateAuthenticator.d.ts +3 -3
  53. package/dist/index.es.js +17032 -12909
  54. package/dist/index.es.js.map +1 -1
  55. package/dist/index.umd.js +17019 -12898
  56. package/dist/index.umd.js.map +1 -1
  57. package/dist/internal/useBuildSideEntityController.d.ts +2 -2
  58. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  59. package/dist/preview/PropertyPreviewProps.d.ts +1 -1
  60. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  61. package/dist/preview/util.d.ts +3 -3
  62. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  63. package/dist/routes/FireCMSRoute.d.ts +1 -0
  64. package/dist/routes/HomePageRoute.d.ts +3 -0
  65. package/dist/types/auth.d.ts +7 -9
  66. package/dist/types/collections.d.ts +44 -23
  67. package/dist/types/datasource.d.ts +2 -6
  68. package/dist/types/dialogs_controller.d.ts +7 -3
  69. package/dist/types/entities.d.ts +1 -1
  70. package/dist/types/entity_actions.d.ts +14 -4
  71. package/dist/types/entity_callbacks.d.ts +16 -16
  72. package/dist/types/export_import.d.ts +4 -4
  73. package/dist/types/fields.d.ts +27 -4
  74. package/dist/types/firecms.d.ts +2 -2
  75. package/dist/types/firecms_context.d.ts +1 -1
  76. package/dist/types/navigation.d.ts +29 -12
  77. package/dist/types/permissions.d.ts +4 -4
  78. package/dist/types/plugins.d.ts +9 -8
  79. package/dist/types/properties.d.ts +18 -3
  80. package/dist/types/property_config.d.ts +0 -2
  81. package/dist/types/side_entity_controller.d.ts +6 -1
  82. package/dist/types/user.d.ts +1 -0
  83. package/dist/util/builders.d.ts +3 -3
  84. package/dist/util/entities.d.ts +1 -1
  85. package/dist/util/entity_actions.d.ts +2 -0
  86. package/dist/util/entity_cache.d.ts +23 -0
  87. package/dist/util/icon_synonyms.d.ts +0 -1
  88. package/dist/util/icons.d.ts +3 -1
  89. package/dist/util/index.d.ts +1 -0
  90. package/dist/util/navigation_from_path.d.ts +6 -1
  91. package/dist/util/navigation_utils.d.ts +12 -1
  92. package/dist/util/objects.d.ts +1 -0
  93. package/dist/util/permissions.d.ts +4 -4
  94. package/dist/util/property_utils.d.ts +2 -2
  95. package/dist/util/resolutions.d.ts +12 -7
  96. package/package.json +129 -125
  97. package/src/app/Scaffold.tsx +4 -4
  98. package/src/components/ArrayContainer.tsx +115 -34
  99. package/src/components/CircularProgressCenter.tsx +1 -1
  100. package/src/components/ConfirmationDialog.tsx +2 -2
  101. package/src/components/DeleteEntityDialog.tsx +10 -19
  102. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +44 -20
  103. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +23 -17
  104. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +20 -3
  105. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +6 -4
  106. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +8 -9
  107. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +5 -11
  108. package/src/components/EntityCollectionTable/index.tsx +1 -1
  109. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
  110. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +5 -5
  111. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +2 -2
  112. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +77 -73
  113. package/src/components/EntityCollectionView/EntityCollectionView.tsx +90 -49
  114. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
  115. package/src/components/EntityCollectionView/useSelectionController.tsx +5 -4
  116. package/src/components/EntityPreview.tsx +39 -30
  117. package/src/components/EntityView.tsx +5 -5
  118. package/src/components/ErrorView.tsx +3 -3
  119. package/src/components/HomePage/DefaultHomePage.tsx +1 -0
  120. package/src/components/HomePage/FavouritesView.tsx +6 -11
  121. package/src/components/HomePage/NavigationCard.tsx +1 -1
  122. package/src/components/HomePage/NavigationCardBinding.tsx +4 -9
  123. package/src/components/HomePage/NavigationGroup.tsx +1 -1
  124. package/src/components/NotFoundPage.tsx +2 -2
  125. package/src/components/PropertyConfigBadge.tsx +1 -1
  126. package/src/components/PropertyIdCopyTooltip.tsx +3 -4
  127. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +13 -8
  128. package/src/components/SelectableTable/SelectableTable.tsx +20 -5
  129. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +4 -2
  130. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +10 -8
  131. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +7 -5
  132. package/src/components/UnsavedChangesDialog.tsx +42 -0
  133. package/src/components/VirtualTable/VirtualTable.tsx +19 -2
  134. package/src/components/VirtualTable/VirtualTableHeader.tsx +8 -8
  135. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +1 -1
  136. package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
  137. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  138. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  139. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
  140. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +2 -0
  141. package/src/components/common/default_entity_actions.tsx +26 -37
  142. package/src/components/common/index.ts +1 -1
  143. package/src/components/common/useColumnsIds.tsx +1 -1
  144. package/src/components/common/useDataSourceTableController.tsx +420 -0
  145. package/src/components/common/useDebouncedCallback.tsx +20 -0
  146. package/src/components/common/useScrollRestoration.tsx +68 -0
  147. package/src/contexts/BreacrumbsContext.tsx +38 -0
  148. package/src/contexts/DialogsProvider.tsx +3 -2
  149. package/src/contexts/ModeController.tsx +1 -3
  150. package/src/contexts/SnackbarProvider.tsx +2 -0
  151. package/src/core/DefaultAppBar.tsx +56 -25
  152. package/src/core/DefaultDrawer.tsx +11 -9
  153. package/src/core/DrawerNavigationItem.tsx +4 -4
  154. package/src/core/EntityEditView.tsx +282 -1006
  155. package/src/core/EntityEditViewFormActions.tsx +199 -0
  156. package/src/core/EntitySidePanel.tsx +85 -15
  157. package/src/core/FireCMS.tsx +12 -9
  158. package/src/core/FireCMSRouter.tsx +17 -0
  159. package/src/core/NavigationRoutes.tsx +23 -32
  160. package/src/core/SideDialogs.tsx +21 -12
  161. package/src/core/field_configs.tsx +4 -4
  162. package/src/core/index.tsx +4 -2
  163. package/src/form/EntityForm.tsx +774 -0
  164. package/src/form/EntityFormActions.tsx +169 -0
  165. package/src/form/PropertyFieldBinding.tsx +39 -30
  166. package/src/form/components/CustomIdField.tsx +2 -0
  167. package/src/form/components/FormEntry.tsx +22 -0
  168. package/src/form/components/FormLayout.tsx +16 -0
  169. package/src/form/components/LabelWithIcon.tsx +6 -3
  170. package/src/form/components/StorageItemPreview.tsx +3 -3
  171. package/src/form/components/StorageUploadProgress.tsx +1 -1
  172. package/src/form/components/index.tsx +2 -1
  173. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +7 -6
  174. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +20 -12
  175. package/src/form/field_bindings/BlockFieldBinding.tsx +45 -25
  176. package/src/form/field_bindings/DateTimeFieldBinding.tsx +2 -1
  177. package/src/form/field_bindings/KeyValueFieldBinding.tsx +10 -7
  178. package/src/form/field_bindings/MapFieldBinding.tsx +69 -52
  179. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +9 -6
  180. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
  181. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
  182. package/src/form/field_bindings/ReferenceFieldBinding.tsx +5 -5
  183. package/src/form/field_bindings/RepeatFieldBinding.tsx +30 -22
  184. package/src/form/field_bindings/SelectFieldBinding.tsx +6 -5
  185. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +7 -5
  186. package/src/form/field_bindings/SwitchFieldBinding.tsx +26 -25
  187. package/src/form/field_bindings/TextFieldBinding.tsx +4 -3
  188. package/src/form/index.tsx +16 -32
  189. package/src/form/validation.ts +12 -6
  190. package/src/hooks/data/delete.ts +4 -4
  191. package/src/hooks/data/save.ts +8 -9
  192. package/src/hooks/data/useCollectionFetch.tsx +3 -3
  193. package/src/hooks/data/useEntityFetch.tsx +10 -6
  194. package/src/hooks/useAuthController.tsx +1 -1
  195. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  196. package/src/hooks/useBuildModeController.tsx +15 -28
  197. package/src/hooks/useBuildNavigationController.tsx +74 -29
  198. package/src/hooks/useFireCMSContext.tsx +5 -5
  199. package/src/hooks/useLargeLayout.tsx +0 -35
  200. package/src/hooks/useModeController.tsx +1 -2
  201. package/src/hooks/useResolvedNavigationFrom.tsx +9 -11
  202. package/src/hooks/useValidateAuthenticator.tsx +3 -3
  203. package/src/internal/useBuildDataSource.ts +1 -1
  204. package/src/internal/useBuildSideDialogsController.tsx +3 -2
  205. package/src/internal/useBuildSideEntityController.tsx +124 -83
  206. package/src/internal/useUnsavedChangesDialog.tsx +127 -93
  207. package/src/preview/PropertyPreview.tsx +11 -11
  208. package/src/preview/PropertyPreviewProps.tsx +1 -1
  209. package/src/preview/components/BooleanPreview.tsx +1 -1
  210. package/src/preview/components/EmptyValue.tsx +1 -1
  211. package/src/preview/components/EnumValuesChip.tsx +1 -1
  212. package/src/preview/components/ImagePreview.tsx +5 -6
  213. package/src/preview/components/UrlComponentPreview.tsx +20 -21
  214. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
  215. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +2 -2
  216. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +2 -2
  217. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -1
  218. package/src/preview/property_previews/ArrayOneOfPreview.tsx +2 -2
  219. package/src/preview/property_previews/ArrayPropertyPreview.tsx +2 -2
  220. package/src/preview/property_previews/MapPropertyPreview.tsx +7 -6
  221. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +12 -12
  222. package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
  223. package/src/preview/util.ts +10 -10
  224. package/src/routes/CustomCMSRoute.tsx +21 -0
  225. package/src/routes/FireCMSRoute.tsx +235 -0
  226. package/src/routes/HomePageRoute.tsx +17 -0
  227. package/src/types/auth.tsx +8 -12
  228. package/src/types/collections.ts +52 -26
  229. package/src/types/datasource.ts +2 -6
  230. package/src/types/dialogs_controller.tsx +7 -3
  231. package/src/types/entities.ts +1 -1
  232. package/src/types/entity_actions.tsx +16 -4
  233. package/src/types/entity_callbacks.ts +18 -18
  234. package/src/types/export_import.ts +4 -4
  235. package/src/types/fields.tsx +33 -4
  236. package/src/types/firecms.tsx +2 -2
  237. package/src/types/firecms_context.tsx +1 -1
  238. package/src/types/navigation.ts +36 -16
  239. package/src/types/permissions.ts +5 -5
  240. package/src/types/plugins.tsx +9 -8
  241. package/src/types/properties.ts +21 -3
  242. package/src/types/property_config.tsx +0 -2
  243. package/src/types/side_dialogs_controller.tsx +2 -0
  244. package/src/types/side_entity_controller.tsx +6 -1
  245. package/src/types/user.ts +2 -0
  246. package/src/util/builders.ts +10 -8
  247. package/src/util/entities.ts +3 -1
  248. package/src/util/entity_actions.ts +28 -0
  249. package/src/util/entity_cache.ts +204 -0
  250. package/src/util/icon_list.ts +1 -0
  251. package/src/util/icon_synonyms.ts +0 -1
  252. package/src/util/icons.tsx +12 -8
  253. package/src/util/index.ts +1 -0
  254. package/src/util/join_collections.ts +3 -0
  255. package/src/util/navigation_from_path.ts +13 -6
  256. package/src/util/navigation_utils.ts +53 -1
  257. package/src/util/objects.ts +24 -1
  258. package/src/util/parent_references_from_path.ts +3 -3
  259. package/src/util/permissions.ts +8 -8
  260. package/src/util/property_utils.tsx +10 -3
  261. package/src/util/references.ts +8 -2
  262. package/src/util/resolutions.ts +34 -13
  263. package/src/util/useStorageUploadController.tsx +40 -22
  264. package/dist/form/components/FormikArrayContainer.d.ts +0 -19
  265. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
  266. package/src/form/components/FormikArrayContainer.tsx +0 -47
  267. /package/src/util/{common.tsx → common.ts} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
2
  import {
3
+ CustomizationController,
3
4
  EntityCollection,
4
5
  EntitySidePanelProps,
5
6
  NavigationController,
@@ -8,18 +9,30 @@ import {
8
9
  SideDialogsController,
9
10
  SideEntityController
10
11
  } from "../types";
11
- import { getNavigationEntriesFromPathInternal, NavigationViewInternal } from "../util/navigation_from_path";
12
+ import { getNavigationEntriesFromPath, NavigationViewInternal } from "../util/navigation_from_path";
12
13
  import { useLocation } from "react-router-dom";
13
- import { removeInitialAndTrailingSlashes, resolveCollection, resolveDefaultSelectedView } from "../util";
14
+ import {
15
+ removeInitialAndTrailingSlashes,
16
+ resolveCollection,
17
+ resolveDefaultSelectedView,
18
+ resolvedSelectedEntityView
19
+ } from "../util";
14
20
  import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, FORM_CONTAINER_WIDTH } from "./common";
15
- import { useLargeLayout } from "../hooks";
21
+ import { useCustomizationController, useLargeLayout } from "../hooks";
16
22
  import { EntitySidePanel } from "../core/EntitySidePanel";
17
23
 
18
- const NEW_URL_HASH = "new";
24
+ const NEW_URL_HASH = "new_side";
25
+ const SIDE_URL_HASH = "side";
19
26
 
20
- export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: boolean): string {
27
+ export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: boolean, customizationController: CustomizationController): string {
21
28
  if (small) return CONTAINER_FULL_WIDTH;
22
- const mainViewSelected = !props.selectedSubPath;
29
+
30
+ const {
31
+ selectedSecondaryForm
32
+ } = resolvedSelectedEntityView(props.collection?.entityViews, customizationController, props.selectedTab);
33
+
34
+ const shouldUseSmallLayout = !props.selectedTab || Boolean(selectedSecondaryForm);
35
+
23
36
  let resolvedWidth: string | undefined;
24
37
  if (props.width) {
25
38
  resolvedWidth = typeof props.width === "number" ? `${props.width}px` : props.width;
@@ -27,7 +40,7 @@ export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: bool
27
40
  resolvedWidth = typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
28
41
  }
29
42
 
30
- if (!mainViewSelected) {
43
+ if (!shouldUseSmallLayout) {
31
44
  return `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedWidth ?? FORM_CONTAINER_WIDTH})`
32
45
  } else {
33
46
  if (resolvedWidth) {
@@ -88,37 +101,59 @@ export const useBuildSideEntityController = (navigation: NavigationController,
88
101
 
89
102
  const location = useLocation();
90
103
  const initialised = useRef<boolean>(false);
104
+ const customizationController = useCustomizationController();
91
105
 
92
106
  const smallLayout = !useLargeLayout();
93
107
 
94
108
  // only on initialisation, create panels from URL
95
109
  useEffect(() => {
96
- if (!navigation.loading && !initialised.current) {
97
- console.debug("Initialising side entity controller");
98
- if (navigation.isUrlCollectionPath(location.pathname)) {
99
- const newFlag = location.hash === `#${NEW_URL_HASH}`;
110
+
111
+ const newFlag = location.hash === `#${NEW_URL_HASH}`;
112
+ const sideFlag = location.hash === `#${SIDE_URL_HASH}`;
113
+
114
+ if (!navigation.loading) {
115
+ if ((newFlag || sideFlag) && navigation.isUrlCollectionPath(location.pathname)) {
100
116
  const entityOrCollectionPath = navigation.urlPathToDataPath(location.pathname);
101
117
  const panelsFromUrl = buildSidePanelsFromUrl(entityOrCollectionPath, navigation.collections ?? [], newFlag);
102
118
  for (let i = 0; i < panelsFromUrl.length; i++) {
103
119
  const props = panelsFromUrl[i];
104
- setTimeout(() => {
105
- if (i === 0)
106
- sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
107
- else
108
- sideDialogsController.open(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout))
109
- }, 1);
120
+ if (i === 0)
121
+ sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController));
122
+ else
123
+ sideDialogsController.open(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController))
110
124
  }
111
- } else {
112
- // console.warn("Location path is not a collection path");
113
125
  }
114
126
  initialised.current = true;
115
127
  }
116
- }, [location, navigation.loading, navigation.isUrlCollectionPath, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, sideDialogsController, smallLayout, navigation]);
128
+ }, [navigation.loading]);
117
129
 
130
+ // sync panels if URL changes with #side
131
+ const currentPanelKeys = sideDialogsController.sidePanels.map(p => p.key);
132
+ useEffect(() => {
133
+ if (initialised.current) {
134
+ const sideFlag = location.hash === `#${SIDE_URL_HASH}`;
135
+ if (sideFlag) {
136
+ const entityOrCollectionPath = navigation.urlPathToDataPath(location.pathname);
137
+ const panelsFromUrl = buildSidePanelsFromUrl(entityOrCollectionPath, navigation.collections ?? [], false);
138
+ // if we have more panels than determined by the url, we ignore the url. We might have references open
139
+ if (panelsFromUrl.length <= currentPanelKeys.length) {
140
+ return;
141
+ }
142
+ const lastPanel = panelsFromUrl[panelsFromUrl.length - 1];
143
+ const panelProps = propsToSidePanel(lastPanel, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController);
144
+ const lastCurrentPanel = currentPanelKeys.length > 0 ? currentPanelKeys[currentPanelKeys.length - 1] : undefined;
145
+ if (!lastCurrentPanel || lastCurrentPanel !== panelProps.key) {
146
+ sideDialogsController.replace(panelProps);
147
+ }
148
+ }
149
+ }
150
+ }, [location.pathname, location.hash, currentPanelKeys]);
151
+
152
+ // update side panels to match browser size
118
153
  useEffect(() => {
119
154
  const updatedSidePanels = sideDialogsController.sidePanels.map(sidePanelProps => {
120
155
  if (sidePanelProps.additional) {
121
- return propsToSidePanel(sidePanelProps.additional, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout);
156
+ return propsToSidePanel(sidePanelProps.additional, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController);
122
157
  }
123
158
  return sidePanelProps;
124
159
  });
@@ -143,12 +178,18 @@ export const useBuildSideEntityController = (navigation: NavigationController,
143
178
  }
144
179
  );
145
180
 
146
- sideDialogsController.open(propsToSidePanel({
147
- selectedSubPath: defaultSelectedView,
148
- ...props,
149
- }, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
181
+ sideDialogsController.open(
182
+ propsToSidePanel({
183
+ selectedTab: defaultSelectedView,
184
+ ...props
185
+ },
186
+ navigation.buildUrlCollectionPath,
187
+ navigation.resolveIdsFrom,
188
+ smallLayout,
189
+ customizationController
190
+ ));
150
191
 
151
- }, [sideDialogsController, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout]);
192
+ }, [sideDialogsController, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout]);
152
193
 
153
194
  const replace = useCallback((props: EntitySidePanelProps<any>) => {
154
195
 
@@ -156,9 +197,9 @@ export const useBuildSideEntityController = (navigation: NavigationController,
156
197
  throw Error("If you want to copy an entity you need to provide an entityId");
157
198
  }
158
199
 
159
- sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
200
+ sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController));
160
201
 
161
- }, [navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, sideDialogsController, smallLayout]);
202
+ }, [navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, sideDialogsController, smallLayout]);
162
203
 
163
204
  return {
164
205
  close,
@@ -169,12 +210,12 @@ export const useBuildSideEntityController = (navigation: NavigationController,
169
210
 
170
211
  export function buildSidePanelsFromUrl(path: string, collections: EntityCollection[], newFlag: boolean): EntitySidePanelProps<any>[] {
171
212
 
172
- const navigationViewsForPath: NavigationViewInternal<any>[] = getNavigationEntriesFromPathInternal({
213
+ const navigationViewsForPath: NavigationViewInternal<any>[] = getNavigationEntriesFromPath({
173
214
  path,
174
215
  collections
175
216
  });
176
217
 
177
- const sidePanels: EntitySidePanelProps<any>[] = [];
218
+ let sidePanel: EntitySidePanelProps<any> | undefined = undefined;
178
219
  let lastCollectionPath = "";
179
220
  for (let i = 0; i < navigationViewsForPath.length; i++) {
180
221
  const navigationEntry = navigationViewsForPath[i];
@@ -183,70 +224,70 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
183
224
  lastCollectionPath = navigationEntry.path;
184
225
  }
185
226
 
186
- if (i > 0) { // the first collection is handled by the main navigation
187
- const previousEntry = navigationViewsForPath[i - 1];
188
- if (navigationEntry.type === "entity") {
189
- sidePanels.push({
190
- path: navigationEntry.path,
191
- entityId: navigationEntry.entityId,
192
- copy: false,
193
- width: navigationEntry.parentCollection?.sideDialogWidth
194
- }
195
- );
196
- } else if (navigationEntry.type === "custom_view") {
197
- if (previousEntry.type === "entity") {
198
- const lastSidePanel: EntitySidePanelProps<any> = sidePanels[sidePanels.length - 1];
199
- if (lastSidePanel)
200
- lastSidePanel.selectedSubPath = navigationEntry.view.key;
201
- }
202
- } else if (navigationEntry.type === "collection") {
203
- if (previousEntry.type === "entity") {
204
- const lastSidePanel: EntitySidePanelProps<any> = sidePanels[sidePanels.length - 1];
205
- if (lastSidePanel)
206
- lastSidePanel.selectedSubPath = navigationEntry.collection.id ?? navigationEntry.collection.path;
207
- }
227
+ const previousEntry = navigationViewsForPath[i - 1];
228
+ if (navigationEntry.type === "entity") {
229
+ sidePanel = {
230
+ path: navigationEntry.path,
231
+ entityId: navigationEntry.entityId,
232
+ copy: false,
233
+ width: navigationEntry.parentCollection?.sideDialogWidth
234
+ };
235
+ } else if (navigationEntry.type === "custom_view") {
236
+ if (previousEntry?.type === "entity") {
237
+ if (sidePanel)
238
+ sidePanel.selectedTab = navigationEntry.view.key;
239
+ }
240
+ } else if (navigationEntry.type === "collection") {
241
+ if (previousEntry?.type === "entity") {
242
+ if (sidePanel)
243
+ sidePanel.selectedTab = navigationEntry.collection.id ?? navigationEntry.collection.path;
208
244
  }
209
245
  }
210
246
 
211
247
  }
212
248
 
213
249
  if (newFlag) {
214
- sidePanels.push({
250
+ sidePanel = {
215
251
  path: lastCollectionPath,
216
252
  copy: false
217
- });
253
+ }
218
254
  }
219
255
 
220
- return sidePanels;
256
+ return sidePanel ? [sidePanel] : [];
221
257
  }
222
258
 
223
259
  const propsToSidePanel = (props: EntitySidePanelProps,
224
260
  buildUrlCollectionPath: (path: string) => string,
225
- resolveAliasesFrom: (pathWithAliases: string) => string,
226
- smallLayout: boolean): SideDialogPanelProps => {
227
-
228
- const collectionPath = removeInitialAndTrailingSlashes(props.path);
229
-
230
- const newPath = props.entityId
231
- ? buildUrlCollectionPath(`${collectionPath}/${props.entityId}/${props.selectedSubPath || ""}`)
232
- : buildUrlCollectionPath(`${collectionPath}#${NEW_URL_HASH}`);
233
- const resolvedPath = resolveAliasesFrom(props.path);
234
-
235
- const resolvedPanelProps: EntitySidePanelProps<any> = {
236
- ...props,
237
- path: resolvedPath,
238
- };
239
-
240
- const entityViewWidth = getEntityViewWidth(props, smallLayout);
241
-
242
- return {
243
- key: `${props.path}/${props.entityId}`,
244
- component: <EntitySidePanel {...resolvedPanelProps}/>,
245
- urlPath: newPath,
246
- parentUrlPath: buildUrlCollectionPath(collectionPath),
247
- width: entityViewWidth,
248
- onClose: props.onClose,
249
- additional: props
250
- };
251
- }
252
- ;
261
+ resolveIdsFrom: (pathWithAliases: string) => string,
262
+ smallLayout: boolean,
263
+ customizationController: CustomizationController): SideDialogPanelProps => {
264
+
265
+ const collectionPath = removeInitialAndTrailingSlashes(props.path);
266
+
267
+ const newPath = props.entityId
268
+ ? buildUrlCollectionPath(`${collectionPath}/${props.entityId}${props.selectedTab ? "/" + props.selectedTab : ""}#${SIDE_URL_HASH}`)
269
+ : buildUrlCollectionPath(`${collectionPath}#${NEW_URL_HASH}`);
270
+ const resolvedPath = resolveIdsFrom(props.path);
271
+
272
+ const resolvedPanelProps: EntitySidePanelProps<any> = {
273
+ ...props,
274
+ path: resolvedPath,
275
+ formProps: props.formProps
276
+ };
277
+
278
+ const entityViewWidth = getEntityViewWidth(props, smallLayout, customizationController);
279
+
280
+ return {
281
+ key: `${props.path}/${props.entityId}`,
282
+ component: <EntitySidePanel {...resolvedPanelProps}/>,
283
+ urlPath: newPath,
284
+ parentUrlPath: buildUrlCollectionPath(collectionPath),
285
+ width: entityViewWidth,
286
+ onClose: props.onClose,
287
+ additional: props
288
+ };
289
+ }
290
+
291
+ function isSideUrl(url: string): boolean {
292
+ return url.endsWith("#" + SIDE_URL_HASH) || url.endsWith("#" + NEW_URL_HASH);
293
+ }
@@ -1,108 +1,142 @@
1
- import React, { useCallback } from "react";
2
- import { Blocker, Transition } from "history";
3
- import { UNSAFE_NavigationContext, useNavigate } from "react-router-dom";
4
- import { Button, Dialog, DialogActions, DialogContent, Typography } from "@firecms/ui";
1
+ import React, { useCallback, useEffect, useState } from "react";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
5
3
 
6
- export function useNavigationUnsavedChangesDialog(when: boolean, onSuccess: () => void):
7
- {
8
- navigationWasBlocked: boolean,
9
- handleCancel: () => void,
10
- handleOk: () => void
11
- } {
12
-
13
- const [nextLocation, setNextLocation] = React.useState<any | undefined>();
14
- const { navigator } = React.useContext(UNSAFE_NavigationContext);
4
+ /**
5
+ * Type representing a pending navigation action.
6
+ */
7
+ type PendingNavigation =
8
+ | {
9
+ type: "popstate";
10
+ delta: number;
11
+ }
12
+ | {
13
+ type: "link";
14
+ href: string;
15
+ }
16
+ | null;
15
17
 
16
- const navigate = useNavigate();
18
+ /**
19
+ * Custom hook to handle navigation blocking when there are unsaved changes.
20
+ *
21
+ * @param when - Indicates whether to block navigation.
22
+ * @param onSuccess - Callback invoked when navigation is confirmed.
23
+ * @returns An object containing the state of navigation blocking and handlers.
24
+ */
25
+ export function useNavigationUnsavedChangesDialog(
26
+ when: boolean,
27
+ onSuccess: () => void
28
+ ): {
29
+ navigationWasBlocked: boolean;
30
+ handleCancel: () => void;
31
+ handleOk: () => void;
32
+ } {
33
+ const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
34
+ const [pendingNavigation, setPendingNavigation] = useState<PendingNavigation>(null);
17
35
 
18
- const handleCancel = () => {
19
- setNextLocation(undefined);
20
- };
36
+ /**
37
+ * Handler to cancel the navigation attempt.
38
+ */
39
+ const handleCancel = useCallback(() => {
40
+ setIsDialogOpen(false);
41
+ setPendingNavigation(null);
42
+ }, []);
21
43
 
22
- const handleOk = () => {
23
- onSuccess();
24
- setNextLocation(undefined);
25
- navigate(-1);
26
- };
44
+ /**
45
+ * Handler to confirm and proceed with the navigation.
46
+ */
47
+ const handleOk = useCallback(() => {
48
+ setIsDialogOpen(false);
49
+ if (pendingNavigation) {
50
+ onSuccess();
51
+ if (pendingNavigation.type === "popstate") {
52
+ window.history.go(pendingNavigation.delta);
53
+ } else if (pendingNavigation.type === "link") {
54
+ window.location.href = pendingNavigation.href;
55
+ }
56
+ setPendingNavigation(null);
57
+ }
58
+ }, [onSuccess, pendingNavigation]);
27
59
 
28
- const blocker: Blocker = useCallback(({
29
- action,
30
- location: nextLocation,
31
- retry
32
- }) => {
33
- switch (action) {
34
- case "REPLACE": {
35
- retry();
36
- return;
60
+ /**
61
+ * Event handler for beforeunload to handle page refresh or tab close.
62
+ */
63
+ const handleBeforeUnload = useCallback(
64
+ (e: BeforeUnloadEvent) => {
65
+ if (when) {
66
+ e.preventDefault();
67
+ e.returnValue = "";
37
68
  }
38
- case "POP": {
39
- setNextLocation(nextLocation);
69
+ },
70
+ [when]
71
+ );
72
+
73
+ /**
74
+ * Event handler for popstate to handle back and forward browser buttons.
75
+ */
76
+ const handlePopState = useCallback(
77
+ (e: PopStateEvent) => {
78
+ if (when) {
79
+ e.preventDefault();
80
+ // Assuming backward navigation; adjust delta as needed
81
+ setPendingNavigation({
82
+ type: "popstate",
83
+ delta: -1
84
+ });
85
+ setIsDialogOpen(true);
40
86
  }
41
- }
42
- }, []);
87
+ },
88
+ [when]
89
+ );
90
+
91
+ /**
92
+ * Event handler to intercept link clicks within the application.
93
+ */
94
+ const handleLinkClick = useCallback(
95
+ (e: MouseEvent) => {
96
+ if (!when) return;
43
97
 
44
- React.useEffect(() => {
45
- if (!when) return;
46
- if (nextLocation) return;
47
- if (!("block" in navigator)) return;
48
- const unblock = (navigator as any).block((tx: Transition) => {
49
- const autoUnblockingTx = {
50
- ...tx,
51
- retry() {
52
- unblock();
53
- tx.retry();
98
+ const target = e.target as HTMLElement;
99
+ const anchor = target.closest("a[href]") as HTMLAnchorElement | null;
100
+ if (anchor && anchor.host === window.location.host) {
101
+ e.preventDefault();
102
+ const href = anchor.getAttribute("href");
103
+ if (href) {
104
+ setPendingNavigation({
105
+ type: "link",
106
+ href
107
+ });
108
+ setIsDialogOpen(true);
54
109
  }
55
- };
56
- blocker(autoUnblockingTx);
57
- });
110
+ }
111
+ },
112
+ [when]
113
+ );
114
+
115
+ /**
116
+ * Effect hook to add and clean up event listeners based on the `when` condition.
117
+ */
118
+ useEffect(() => {
119
+ if (when) {
120
+ window.addEventListener("beforeunload", handleBeforeUnload);
121
+ window.addEventListener("popstate", handlePopState);
122
+ document.addEventListener("click", handleLinkClick);
123
+ } else {
124
+ window.removeEventListener("beforeunload", handleBeforeUnload);
125
+ window.removeEventListener("popstate", handlePopState);
126
+ document.removeEventListener("click", handleLinkClick);
127
+ }
58
128
 
59
- return unblock;
60
- }, [navigator, blocker, when, nextLocation]);
129
+ // Cleanup on unmount or when `when` changes
130
+ return () => {
131
+ window.removeEventListener("beforeunload", handleBeforeUnload);
132
+ window.removeEventListener("popstate", handlePopState);
133
+ document.removeEventListener("click", handleLinkClick);
134
+ };
135
+ }, [when, handleBeforeUnload, handlePopState, handleLinkClick]);
61
136
 
62
137
  return {
63
- navigationWasBlocked: Boolean(nextLocation),
138
+ navigationWasBlocked: isDialogOpen,
64
139
  handleCancel,
65
- handleOk
140
+ handleOk,
66
141
  };
67
142
  }
68
-
69
- export interface UnsavedChangesDialogProps {
70
- open: boolean;
71
- body?: React.ReactNode;
72
- title?: string;
73
- handleOk: () => void;
74
- handleCancel: () => void;
75
- }
76
-
77
- export function UnsavedChangesDialog({
78
- open,
79
- handleOk,
80
- handleCancel,
81
- body,
82
- title
83
- }: UnsavedChangesDialogProps) {
84
-
85
- return (
86
- <Dialog
87
- onEscapeKeyDown={() => {
88
- handleCancel();
89
- }}
90
- open={open}
91
- >
92
- <DialogContent>
93
- <Typography variant={"h6"}>{title}</Typography>
94
-
95
- {body}
96
-
97
- <Typography>
98
- Are you sure you want to leave this page?
99
- </Typography>
100
-
101
- </DialogContent>
102
- <DialogActions>
103
- <Button variant="text" onClick={handleCancel} autoFocus> Cancel </Button>
104
- <Button onClick={handleOk}> Ok </Button>
105
- </DialogActions>
106
- </Dialog>
107
- );
108
- }
@@ -53,10 +53,10 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
53
53
  const property = resolveProperty({
54
54
  propertyKey,
55
55
  propertyOrBuilder: inputProperty,
56
- fields: customizationController.propertyConfigs
56
+ propertyConfigs: customizationController.propertyConfigs
57
57
  });
58
58
 
59
- if (value === undefined || property === null) {
59
+ if (property === null) {
60
60
  content = <EmptyValue/>;
61
61
  } else if (property.Preview) {
62
62
  content = createElement(property.Preview as React.ComponentType<PropertyPreviewProps>,
@@ -70,12 +70,19 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
70
70
  // entity,
71
71
  customProps: property.customProps
72
72
  });
73
- } else if (value === null) {
73
+ } else if (value === undefined || value === null) {
74
74
  content = <EmptyValue/>;
75
75
  } else if (property.dataType === "string") {
76
76
  const stringProperty = property as ResolvedStringProperty;
77
77
  if (typeof value === "string") {
78
- if (stringProperty.url) {
78
+ if (stringProperty.storage) {
79
+ const filePath = stringProperty.storage.previewUrl ? stringProperty.storage.previewUrl(value) : value;
80
+ content = <StorageThumbnail
81
+ interactive={interactive}
82
+ storeUrl={property.storage?.storeUrl ?? false}
83
+ size={props.size}
84
+ storagePathOrDownloadUrl={filePath}/>;
85
+ } else if (stringProperty.url) {
79
86
  if (typeof stringProperty.url === "boolean")
80
87
  content =
81
88
  <UrlComponentPreview size={props.size}
@@ -88,13 +95,6 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
88
95
  previewType={stringProperty.url}/>;
89
96
  } else if (stringProperty.markdown) {
90
97
  content = <Markdown source={value} size={"small"}/>;
91
- } else if (stringProperty.storage) {
92
- const filePath = stringProperty.storage.previewUrl ? stringProperty.storage.previewUrl(value) : value;
93
- content = <StorageThumbnail
94
- interactive={interactive}
95
- storeUrl={property.storage?.storeUrl ?? false}
96
- size={props.size}
97
- storagePathOrDownloadUrl={filePath}/>;
98
98
  } else {
99
99
  content = <StringPropertyPreview {...props}
100
100
  property={stringProperty}
@@ -3,7 +3,7 @@ import { CMSType, Property, ResolvedProperty } from "../types";
3
3
  /**
4
4
  * @group Preview components
5
5
  */
6
- export type PreviewSize = "medium" | "small" | "smallest";
6
+ export type PreviewSize = "small" | "medium" | "large";
7
7
 
8
8
  /**
9
9
  * @group Preview components
@@ -21,6 +21,6 @@ export function BooleanPreview({
21
21
  size={size}
22
22
  color={"secondary"}/>
23
23
  {property.name && <span
24
- className={cls("text-text-secondary dark:text-text-secondary-dark", size === "smallest" ? "text-sm" : "")}>{property.name}</span>}
24
+ className={cls("text-text-secondary dark:text-text-secondary-dark", size === "small" ? "text-sm" : "")}>{property.name}</span>}
25
25
  </div>;
26
26
  }
@@ -6,5 +6,5 @@ import React from "react";
6
6
  export function EmptyValue() {
7
7
 
8
8
  return <div
9
- className="rounded-full bg-gray-200 bg-opacity-30 dark:bg-opacity-20 w-5 h-2 inline-block"/>;
9
+ className="rounded-full bg-surface-200 bg-opacity-30 dark:bg-opacity-20 w-5 h-2 inline-block"/>;
10
10
  }
@@ -6,7 +6,7 @@ import { buildEnumLabel, enumToObjectEntries, getColorScheme, getLabelOrConfigFr
6
6
  export interface EnumValuesChipProps {
7
7
  enumValues?: EnumValues;
8
8
  enumKey: string | number;
9
- size: "smallest" | "small" | "medium";
9
+ size: "small" | "medium" | "large";
10
10
  className?: string;
11
11
  children?: React.ReactNode;
12
12
  }
@@ -22,7 +22,7 @@ export function ImagePreview({
22
22
 
23
23
  const imageSize = useMemo(() => getThumbnailMeasure(size), [size]);
24
24
 
25
- if (size === "smallest") {
25
+ if (size === "small") {
26
26
  return (
27
27
  <img src={url}
28
28
  className={"rounded-md"}
@@ -56,7 +56,6 @@ export function ImagePreview({
56
56
  className={"rounded-md"}
57
57
  style={imageStyle}/>
58
58
 
59
-
60
59
  <div className={"flex flex-row gap-2 absolute bottom-[-4px] right-[-4px] invisible group-hover:visible"}>
61
60
  {navigator && <Tooltip
62
61
  asChild={true}
@@ -69,8 +68,8 @@ export function ImagePreview({
69
68
  e.preventDefault();
70
69
  return navigator.clipboard.writeText(url);
71
70
  }}>
72
- <ContentCopyIcon className={"text-gray-700 dark:text-gray-300"}
73
- size={"small"}/>
71
+ <ContentCopyIcon className={"text-surface-700 dark:text-surface-300"}
72
+ size={"smallest"}/>
74
73
  </IconButton>
75
74
  </Tooltip>}
76
75
 
@@ -85,8 +84,8 @@ export function ImagePreview({
85
84
  size={"small"}
86
85
  onClick={(e: any) => e.stopPropagation()}
87
86
  >
88
- <OpenInNewIcon className={"text-gray-700 dark:text-gray-300"}
89
- size={"small"}/>
87
+ <OpenInNewIcon className={"text-surface-700 dark:text-surface-300"}
88
+ size={"smallest"}/>
90
89
  </IconButton>
91
90
  </Tooltip>
92
91
  </div>