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

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 (120) hide show
  1. package/dist/app/Scaffold.d.ts +4 -0
  2. package/dist/components/ArrayContainer.d.ts +13 -11
  3. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  4. package/dist/components/EntityJsonPreview.d.ts +3 -0
  5. package/dist/components/EntityPreview.d.ts +4 -2
  6. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  7. package/dist/components/VirtualTable/VirtualTableProps.d.ts +0 -4
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/core/DefaultAppBar.d.ts +8 -2
  10. package/dist/core/EntityEditView.d.ts +8 -1
  11. package/dist/core/FireCMS.d.ts +2 -2
  12. package/dist/form/EntityForm.d.ts +5 -1
  13. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  14. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  15. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -9
  16. package/dist/hooks/data/save.d.ts +1 -1
  17. package/dist/hooks/useBuildNavigationController.d.ts +2 -9
  18. package/dist/index.es.js +9251 -8721
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/index.umd.js +16686 -16160
  21. package/dist/index.umd.js.map +1 -1
  22. package/dist/internal/useBuildDataSource.d.ts +3 -2
  23. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  24. package/dist/types/collections.d.ts +26 -2
  25. package/dist/types/fields.d.ts +4 -1
  26. package/dist/types/firecms.d.ts +3 -2
  27. package/dist/types/navigation.d.ts +9 -0
  28. package/dist/types/plugins.d.ts +13 -0
  29. package/dist/types/properties.d.ts +22 -1
  30. package/dist/types/side_entity_controller.d.ts +4 -0
  31. package/dist/util/callbacks.d.ts +2 -0
  32. package/dist/util/createFormexStub.d.ts +2 -0
  33. package/dist/util/index.d.ts +1 -0
  34. package/dist/util/navigation_utils.d.ts +2 -1
  35. package/dist/util/objects.d.ts +1 -1
  36. package/dist/util/property_utils.d.ts +2 -2
  37. package/dist/util/references.d.ts +2 -2
  38. package/dist/util/resolutions.d.ts +11 -6
  39. package/package.json +9 -6
  40. package/src/app/Scaffold.tsx +13 -3
  41. package/src/components/ArrayContainer.tsx +414 -282
  42. package/src/components/ClearFilterSortButton.tsx +1 -1
  43. package/src/components/ConfirmationDialog.tsx +9 -9
  44. package/src/components/DeleteEntityDialog.tsx +4 -2
  45. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
  46. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +1 -1
  47. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  48. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +13 -2
  49. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +58 -29
  50. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
  51. package/src/components/EntityCollectionView/EntityCollectionView.tsx +26 -6
  52. package/src/components/EntityJsonPreview.tsx +66 -0
  53. package/src/components/EntityPreview.tsx +27 -16
  54. package/src/components/EntityView.tsx +4 -1
  55. package/src/components/ErrorView.tsx +1 -1
  56. package/src/components/HomePage/DefaultHomePage.tsx +2 -1
  57. package/src/components/HomePage/NavigationCardBinding.tsx +3 -1
  58. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
  59. package/src/components/SelectableTable/SelectableTable.tsx +140 -143
  60. package/src/components/VirtualTable/VirtualTable.tsx +37 -31
  61. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -5
  62. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -1
  63. package/src/components/common/default_entity_actions.tsx +15 -4
  64. package/src/components/index.tsx +2 -0
  65. package/src/core/DefaultAppBar.tsx +17 -5
  66. package/src/core/EntityEditView.tsx +135 -47
  67. package/src/core/EntitySidePanel.tsx +15 -20
  68. package/src/core/FireCMS.tsx +19 -12
  69. package/src/form/EntityForm.tsx +32 -24
  70. package/src/form/PropertyFieldBinding.tsx +8 -6
  71. package/src/form/components/CustomIdField.tsx +3 -1
  72. package/src/form/components/LabelWithIcon.tsx +1 -1
  73. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
  74. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
  75. package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
  76. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  77. package/src/form/field_bindings/MapFieldBinding.tsx +5 -4
  78. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
  79. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +3 -3
  80. package/src/form/field_bindings/RepeatFieldBinding.tsx +9 -2
  81. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
  82. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +227 -156
  83. package/src/hooks/data/save.ts +24 -35
  84. package/src/hooks/useBuildNavigationController.tsx +42 -17
  85. package/src/hooks/useFireCMSContext.tsx +0 -30
  86. package/src/internal/useBuildDataSource.ts +9 -5
  87. package/src/internal/useBuildSideEntityController.tsx +26 -20
  88. package/src/preview/PropertyPreview.tsx +4 -2
  89. package/src/preview/components/ImagePreview.tsx +2 -2
  90. package/src/preview/components/ReferencePreview.tsx +1 -1
  91. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +5 -4
  92. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  93. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
  94. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  95. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
  96. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
  97. package/src/preview/property_previews/MapPropertyPreview.tsx +1 -1
  98. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +1 -1
  99. package/src/preview/property_previews/StringPropertyPreview.tsx +1 -1
  100. package/src/routes/FireCMSRoute.tsx +15 -4
  101. package/src/types/collections.ts +31 -2
  102. package/src/types/fields.tsx +5 -1
  103. package/src/types/firecms.tsx +3 -3
  104. package/src/types/navigation.ts +11 -0
  105. package/src/types/plugins.tsx +17 -0
  106. package/src/types/properties.ts +27 -1
  107. package/src/types/side_entity_controller.tsx +5 -0
  108. package/src/util/callbacks.ts +119 -0
  109. package/src/util/createFormexStub.tsx +62 -0
  110. package/src/util/index.ts +1 -0
  111. package/src/util/join_collections.ts +3 -1
  112. package/src/util/navigation_from_path.ts +5 -1
  113. package/src/util/navigation_utils.ts +84 -20
  114. package/src/util/objects.ts +54 -17
  115. package/src/util/property_utils.tsx +7 -3
  116. package/src/util/references.ts +8 -6
  117. package/src/util/resolutions.ts +17 -9
  118. package/src/util/useStorageUploadController.tsx +21 -2
  119. package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
  120. package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
@@ -66,7 +66,6 @@ export function VirtualTableInput(props: {
66
66
  width: "100%",
67
67
  color: "unset",
68
68
  fontWeight: "unset",
69
- lineHeight: 1.7142857,
70
69
  fontSize: "unset",
71
70
  fontFamily: "unset",
72
71
  background: "unset",
@@ -2,7 +2,7 @@ import { DeleteIcon, EditIcon, FileCopyIcon } from "@firecms/ui";
2
2
  import { EntityAction } from "../../types";
3
3
  import { DeleteEntityDialog } from "../DeleteEntityDialog";
4
4
  import { addRecentId } from "../EntityCollectionView/utils";
5
- import { navigateToEntity } from "../../util";
5
+ import { navigateToEntity, resolveDefaultSelectedView } from "../../util";
6
6
 
7
7
  export const editEntityAction: EntityAction = {
8
8
  icon: <EditIcon/>,
@@ -30,15 +30,24 @@ export const editEntityAction: EntityAction = {
30
30
  addRecentId(collection.id, entity.id);
31
31
  }
32
32
 
33
- const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path)
33
+ const path = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
34
+ const defaultSelectedView = resolveDefaultSelectedView(
35
+ collection ? collection.defaultSelectedView : undefined,
36
+ {
37
+ status: "existing",
38
+ entityId: entity.id,
39
+ }
40
+ );
34
41
  navigateToEntity({
35
42
  openEntityMode,
36
43
  collection,
37
44
  entityId: entity.id,
38
45
  path,
46
+ fullIdPath: path,
39
47
  sideEntityController: context.sideEntityController,
40
48
  onClose: () => unhighlightEntity?.(entity),
41
- navigation: context.navigation
49
+ navigation: context.navigation,
50
+ selectedTab: defaultSelectedView
42
51
  });
43
52
 
44
53
  return Promise.resolve(undefined);
@@ -63,12 +72,14 @@ export const copyEntityAction: EntityAction = {
63
72
  path: entity.path,
64
73
  entityId: entity.id
65
74
  });
66
- const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path)
75
+
76
+ const path = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
67
77
  navigateToEntity({
68
78
  openEntityMode,
69
79
  collection,
70
80
  entityId: entity.id,
71
81
  path,
82
+ fullIdPath: path,
72
83
  copy: true,
73
84
  sideEntityController: context.sideEntityController,
74
85
  onClose: () => unhighlightEntity?.(entity),
@@ -36,3 +36,5 @@ export * from "./ArrayContainer";
36
36
  export * from "./ReferenceWidget";
37
37
  export * from "./SearchIconsView";
38
38
  export * from "./FieldCaption";
39
+
40
+ export * from "./EntityPreview";
@@ -22,12 +22,19 @@ import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
22
22
 
23
23
  export type DefaultAppBarProps<ADDITIONAL_PROPS = object> = {
24
24
 
25
+ /**
26
+ * The content of the app bar, usually a title or logo. This includes a link to the home page.
27
+ */
25
28
  title?: React.ReactNode;
29
+
26
30
  /**
27
- * A component that gets rendered on the upper side of the main toolbar
31
+ * A component that gets rendered on the upper side to the end of the main toolbar
28
32
  */
29
33
  endAdornment?: React.ReactNode;
30
34
 
35
+ /**
36
+ * A component that gets rendered on the upper side to the start of the main toolbar
37
+ */
31
38
  startAdornment?: React.ReactNode;
32
39
 
33
40
  dropDownActions?: React.ReactNode;
@@ -57,14 +64,18 @@ export const DefaultAppBar = function DefaultAppBar({
57
64
  includeModeToggle = true,
58
65
  className,
59
66
  style,
60
- user: userProp
67
+ user: userProp,
68
+ logo: logoProp,
61
69
  }: DefaultAppBarProps) {
62
70
 
63
71
  const {
64
72
  hasDrawer,
65
73
  drawerOpen,
66
- logo
74
+ logo: appLogo
67
75
  } = useApp();
76
+
77
+ const logo = logoProp ?? appLogo;
78
+
68
79
  const navigation = useNavigationController();
69
80
 
70
81
  const breadcrumbs = useBreadcrumbsController();
@@ -134,7 +145,7 @@ export const DefaultAppBar = function DefaultAppBar({
134
145
  </Link>
135
146
  </div>}
136
147
 
137
- {breadcrumbs.breadcrumbs && <div className="mr-8 hidden lg:block">
148
+ {(breadcrumbs.breadcrumbs ?? []).length > 0 && <div className="mr-8 hidden lg:block">
138
149
  <div className={"flex flex-row gap-2"}>
139
150
  {breadcrumbs.breadcrumbs.map((breadcrumb, index) => {
140
151
  return <React.Fragment key={breadcrumb.url + "_" + index}>
@@ -176,7 +187,8 @@ export const DefaultAppBar = function DefaultAppBar({
176
187
  </IconButton>}>
177
188
  <MenuItem onClick={() => setMode("dark")}><DarkModeIcon size={"smallest"}/> Dark</MenuItem>
178
189
  <MenuItem onClick={() => setMode("light")}><LightModeIcon size={"smallest"}/> Light </MenuItem>
179
- <MenuItem onClick={() => setMode("system")}> <BrightnessMediumIcon size={"smallest"}/>System</MenuItem>
190
+ <MenuItem onClick={() => setMode("system")}> <BrightnessMediumIcon
191
+ size={"smallest"}/>System</MenuItem>
180
192
  </Menu>}
181
193
 
182
194
  <Menu trigger={avatarComponent}>
@@ -5,6 +5,7 @@ import { CircularProgressCenter, EntityCollectionView, EntityView, ErrorBoundary
5
5
  import {
6
6
  canEditEntity,
7
7
  removeInitialAndTrailingSlashes,
8
+ resolveCollection,
8
9
  resolveDefaultSelectedView,
9
10
  resolvedSelectedEntityView
10
11
  } from "../util";
@@ -16,12 +17,15 @@ import {
16
17
  useFireCMSContext,
17
18
  useLargeLayout
18
19
  } from "../hooks";
19
- import { CircularProgress, cls, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
20
+ import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
20
21
  import { getEntityFromCache } from "../util/entity_cache";
21
22
  import { EntityForm, EntityFormProps } from "../form";
22
23
  import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
24
+ import { EntityJsonPreview } from "../components/EntityJsonPreview";
25
+ import { createFormexStub } from "../util/createFormexStub";
23
26
 
24
- const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
27
+ export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
28
+ export const JSON_TAB_VALUE = "__json";
25
29
 
26
30
  export type OnUpdateParams = {
27
31
  entity: Entity<any>,
@@ -41,6 +45,10 @@ export type OnTabChangeParams<M extends Record<string, any>> = {
41
45
 
42
46
  export interface EntityEditViewProps<M extends Record<string, any>> {
43
47
  path: string;
48
+ /**
49
+ * The navigation path to the entity.
50
+ */
51
+ fullIdPath?: string;
44
52
  collection: EntityCollection<M>;
45
53
  entityId?: string;
46
54
  databaseId?: string;
@@ -52,7 +60,7 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
52
60
  onTabChange?: (props: OnTabChangeParams<M>) => void;
53
61
  layout?: "side_panel" | "full_screen";
54
62
  barActions?: React.ReactNode;
55
- formProps?: Partial<EntityFormProps<M>>
63
+ formProps?: Partial<EntityFormProps<M>>,
56
64
  }
57
65
 
58
66
  /**
@@ -102,19 +110,6 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
102
110
  console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
103
111
  }
104
112
 
105
- if (!canEdit) {
106
- return <div className={"flex flex-col"}>
107
- <Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
108
- {props.collection.singularName ?? props.collection.name}
109
- </Typography>
110
- <EntityView
111
- className={"px-8"}
112
- entity={entity as Entity<M>}
113
- path={props.path}
114
- collection={props.collection}/>
115
- </div>
116
- }
117
-
118
113
  return <EntityEditViewInner<M> {...props}
119
114
  entityId={entityId}
120
115
  entity={entity}
@@ -122,11 +117,13 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
122
117
  dataLoading={dataLoading}
123
118
  status={status}
124
119
  setStatus={setStatus}
120
+ canEdit={canEdit}
125
121
  />;
126
122
  }
127
123
 
128
124
  export function EntityEditViewInner<M extends Record<string, any>>({
129
125
  path,
126
+ fullIdPath,
130
127
  entityId,
131
128
  selectedTab: selectedTabProp,
132
129
  collection,
@@ -142,12 +139,14 @@ export function EntityEditViewInner<M extends Record<string, any>>({
142
139
  status,
143
140
  setStatus,
144
141
  formProps,
142
+ canEdit
145
143
  }: EntityEditViewProps<M> & {
146
144
  entity?: Entity<M>,
147
145
  cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
148
146
  dataLoading: boolean,
149
147
  status: EntityStatus,
150
148
  setStatus: (status: EntityStatus) => void,
149
+ canEdit?: boolean,
151
150
  }) {
152
151
 
153
152
  const context = useFireCMSContext();
@@ -175,40 +174,77 @@ export function EntityEditViewInner<M extends Record<string, any>>({
175
174
  ), []);
176
175
 
177
176
  const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
178
-
179
177
  useEffect(() => {
180
- if ((selectedTabProp ?? MAIN_TAB_VALUE) !== selectedTab) {
181
- setSelectedTab(selectedTabProp ?? MAIN_TAB_VALUE);
178
+ if ((selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE) !== selectedTab) {
179
+ setSelectedTab(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
182
180
  }
183
181
  }, [selectedTabProp]);
184
182
 
185
183
  const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
186
184
  const subcollectionsCount = subcollections?.length ?? 0;
187
- const customViews = collection.entityViews;
185
+ const customViews = collection.entityViews ?? [];
188
186
  const customViewsCount = customViews?.length ?? 0;
189
- const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
187
+ const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
188
+ const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
189
+
190
+ const plugins = customizationController.plugins;
190
191
 
191
192
  const {
192
193
  resolvedEntityViews,
193
194
  selectedEntityView,
194
195
  selectedSecondaryForm
195
- } = resolvedSelectedEntityView(customViews, customizationController, selectedTab);
196
+ } = resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit);
196
197
 
197
198
  const actionsAtTheBottom = !largeLayout || layout === "side_panel" || selectedEntityView?.includeActions === "bottom";
198
199
 
199
200
  const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
200
201
 
202
+ const authController = useAuthController();
203
+
201
204
  const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
202
205
  .filter(e => !e.includeActions)
203
206
  .map((customView) => {
207
+
204
208
  if (!customView)
205
209
  return null;
206
210
  const Builder = customView.Builder;
207
211
  if (!Builder) {
208
- console.error("customView.Builder is not defined");
212
+ console.error("INTERNAL: customView.Builder is not defined");
209
213
  return null;
210
214
  }
211
215
 
216
+ if (!entityId) {
217
+ return null;
218
+ }
219
+
220
+ const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
221
+ const usedFormContext: FormContext = formContext ?? {
222
+ entityId,
223
+ disabled: false,
224
+ openEntityMode: layout,
225
+ status: status,
226
+ values: usedEntity?.values ?? {},
227
+ setFieldValue: (key: string, value: any) => {
228
+ throw new Error("You can't update values in read only mode");
229
+ },
230
+ save: () => {
231
+ throw new Error("You can't save in read only mode");
232
+ },
233
+ collection: resolveCollection<M>({
234
+ collection,
235
+ path,
236
+ entityId,
237
+ values: usedEntity?.values ?? {},
238
+ previousValues: usedEntity?.values ?? {},
239
+ propertyConfigs: customizationController.propertyConfigs,
240
+ authController
241
+ }),
242
+ path,
243
+ entity: usedEntity,
244
+ savingError: undefined,
245
+ formex: formexStub
246
+ };
247
+
212
248
  return <div
213
249
  className={cls(defaultBorderMixin,
214
250
  "relative flex-1 w-full h-full overflow-auto",
@@ -217,11 +253,12 @@ export function EntityEditViewInner<M extends Record<string, any>>({
217
253
  key={`custom_view_${customView.key}`}
218
254
  role="tabpanel">
219
255
  <ErrorBoundary>
220
- {formContext && <Builder
256
+ {usedFormContext && <Builder
221
257
  collection={collection}
258
+ parentCollectionIds={parentCollectionIds}
222
259
  entity={usedEntity}
223
- modifiedValues={formContext.formex.values ?? usedEntity?.values}
224
- formContext={formContext}
260
+ modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
261
+ formContext={usedFormContext}
225
262
  />}
226
263
  </ErrorBoundary>
227
264
  </div>;
@@ -229,9 +266,22 @@ export function EntityEditViewInner<M extends Record<string, any>>({
229
266
 
230
267
  const globalLoading = dataLoading && !usedEntity;
231
268
 
269
+ const jsonView = <div
270
+ className={cls("relative flex-1 h-full overflow-auto w-full",
271
+ { "hidden": selectedTab !== JSON_TAB_VALUE })}
272
+ key={"json_view"}
273
+ role="tabpanel">
274
+ <ErrorBoundary>
275
+ <EntityJsonPreview
276
+ values={formContext?.values ?? entity?.values ?? {}}/>
277
+ </ErrorBoundary>
278
+ </div>;
279
+
232
280
  const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
233
281
  const subcollectionId = subcollection.id ?? subcollection.path;
234
- const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
282
+ const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
283
+ const newFullIdPath = fullIdPath ? `${fullIdPath}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
284
+
235
285
  if (selectedTab !== subcollectionId) return null;
236
286
  return (
237
287
  <div
@@ -242,9 +292,10 @@ export function EntityEditViewInner<M extends Record<string, any>>({
242
292
  {globalLoading && <CircularProgressCenter/>}
243
293
 
244
294
  {!globalLoading &&
245
- (usedEntity && fullPath
295
+ (usedEntity && newFullPath
246
296
  ? <EntityCollectionView
247
- fullPath={fullPath}
297
+ fullPath={newFullPath}
298
+ fullIdPath={newFullIdPath}
248
299
  parentCollectionIds={[...parentCollectionIds, collection.id]}
249
300
  isSubCollection={true}
250
301
  updateUrl={false}
@@ -266,15 +317,30 @@ export function EntityEditViewInner<M extends Record<string, any>>({
266
317
  setSelectedTab(value);
267
318
  if (status === "existing") {
268
319
  onTabChange?.({
269
- path,
320
+ path: fullIdPath ?? path,
270
321
  entityId,
271
322
  selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
272
- collection,
323
+ collection
273
324
  });
274
325
  }
275
326
  };
276
327
 
277
- // Render the main entity form view (or a read-only view if the user cannot edit)
328
+ const entityReadOnlyView = !canEdit && entity ? <div
329
+ className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", (canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : "")}>
330
+ <div
331
+ className={cls("relative flex flex-col max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
332
+ <Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
333
+ {collection.singularName ?? collection.name}
334
+ </Typography>
335
+ <EntityView
336
+ className={"px-8 h-full overflow-auto"}
337
+ entity={entity}
338
+ path={path}
339
+ collection={collection}/>
340
+ <div className="h-16"/>
341
+ </div>
342
+ </div> : null;
343
+
278
344
  const entityView = <EntityForm<M>
279
345
  collection={collection}
280
346
  path={path}
@@ -285,8 +351,9 @@ export function EntityEditViewInner<M extends Record<string, any>>({
285
351
  openEntityMode={layout}
286
352
  forceActionsAtTheBottom={actionsAtTheBottom}
287
353
  initialStatus={status}
288
- className={cls(!mainViewVisible ? "hidden" : "", formProps?.className)}
354
+ className={cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className)}
289
355
  EntityFormActionsComponent={EntityEditViewFormActions}
356
+ disabled={!canEdit}
290
357
  {...formProps}
291
358
  onEntityChange={(entity) => {
292
359
  setUsedEntity(entity);
@@ -303,7 +370,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
303
370
  onSaved={(params) => {
304
371
  const res = {
305
372
  ...params,
306
- selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
373
+ selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab
307
374
  };
308
375
  onSaved?.(res);
309
376
  formProps?.onSaved?.(res);
@@ -320,14 +387,24 @@ export function EntityEditViewInner<M extends Record<string, any>>({
320
387
  </Tab>
321
388
  );
322
389
 
323
- const customViewTabs = resolvedEntityViews.map((view) =>
324
- <Tab
325
- className="text-sm min-w-[120px]"
326
- value={view.key}
327
- key={`entity_detail_collection_tab_${view.name}`}>
328
- {view.name}
329
- </Tab>
330
- );
390
+ const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
391
+ .map((view) =>
392
+ <Tab
393
+ className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
394
+ value={view.key}
395
+ key={`entity_detail_collection_tab_${view.name}`}>
396
+ {view.tabComponent ?? view.name}
397
+ </Tab>
398
+ );
399
+ const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
400
+ .map((view) =>
401
+ <Tab
402
+ className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
403
+ value={view.key}
404
+ key={`entity_detail_collection_tab_${view.name}`}>
405
+ {view.tabComponent ?? view.name}
406
+ </Tab>
407
+ );
331
408
 
332
409
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
333
410
 
@@ -350,6 +427,15 @@ export function EntityEditViewInner<M extends Record<string, any>>({
350
427
  onSideTabClick(value);
351
428
  }}>
352
429
 
430
+ {includeJsonView && <Tab
431
+ disabled={!hasAdditionalViews}
432
+ value={JSON_TAB_VALUE}
433
+ className={"text-sm"}>
434
+ <CodeIcon size={"small"}/>
435
+ </Tab>}
436
+
437
+ {customViewTabsStart}
438
+
353
439
  <Tab
354
440
  disabled={!hasAdditionalViews}
355
441
  value={MAIN_TAB_VALUE}
@@ -357,7 +443,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
357
443
  {collection.singularName ?? collection.name}
358
444
  </Tab>
359
445
 
360
- {customViewTabs}
446
+
447
+ {customViewTabsEnd}
361
448
 
362
449
  {subcollectionTabs}
363
450
  </Tabs>}
@@ -367,9 +454,12 @@ export function EntityEditViewInner<M extends Record<string, any>>({
367
454
  ? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
368
455
  <CircularProgressCenter/>
369
456
  </div>
370
- : entityView}
457
+ : <>
458
+ {entityReadOnlyView}
459
+ {entityView}
460
+ </>}
371
461
 
372
- {/*{secondaryForms}*/}
462
+ {jsonView}
373
463
 
374
464
  {customViewsView}
375
465
 
@@ -377,8 +467,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
377
467
 
378
468
  </div>;
379
469
 
380
- const plugins = customizationController.plugins;
381
-
382
470
  if (plugins) {
383
471
  plugins.forEach((plugin: FireCMSPlugin) => {
384
472
  if (plugin.form?.provider) {
@@ -19,11 +19,18 @@ import { useLocation, useNavigate } from "react-router-dom";
19
19
  */
20
20
  export function EntitySidePanel(props: EntitySidePanelProps) {
21
21
 
22
+ const {
23
+ allowFullScreen = true,
24
+ path,
25
+ entityId,
26
+ formProps,
27
+ } = props;
28
+
22
29
  const {
23
30
  blocked,
24
31
  setBlocked,
25
32
  setBlockedNavigationMessage,
26
- close
33
+ close,
27
34
  } = useSideDialogContext();
28
35
 
29
36
  const navigate = useNavigate();
@@ -64,22 +71,10 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
64
71
  }
65
72
 
66
73
  const parentCollectionIds = useMemo(() => {
67
- return navigationController.getParentCollectionIds(props.path);
68
- }, [navigationController, props.path]);
69
-
70
- const collection = useMemo(() => {
71
- if (props.collection) {
72
- return props.collection;
73
- }
74
+ return navigationController.getParentCollectionIds(path);
75
+ }, [navigationController, path]);
74
76
 
75
- const registryCollection = navigationController.getCollection(props.path);
76
- if (registryCollection) {
77
- return registryCollection;
78
- }
79
-
80
- console.error("ERROR: No collection found in path `", props.path, "`. Entity id: ", props.entityId);
81
- throw Error("ERROR: No collection found in path `" + props.path + "`. Make sure you have defined a collection for this path in the root navigation.");
82
- }, [navigationController, props.collection]);
77
+ const collection = props.collection ?? navigationController.getCollection(path);
83
78
 
84
79
  useEffect(() => {
85
80
  function beforeunload(e: any) {
@@ -126,16 +121,16 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
126
121
  onClick={onClose}>
127
122
  <CloseIcon size={"small"}/>
128
123
  </IconButton>
129
- <IconButton
124
+ {allowFullScreen && <IconButton
130
125
  className="self-center"
131
126
  onClick={() => {
132
- if (props.entityId)
127
+ if (entityId)
133
128
  navigate(location.pathname);
134
129
  else
135
130
  navigate(location.pathname + "#new");
136
131
  }}>
137
132
  <OpenInFullIcon size={"small"}/>
138
- </IconButton>
133
+ </IconButton>}
139
134
  </>}
140
135
  onTabChange={({
141
136
  path,
@@ -151,7 +146,7 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
151
146
  collection,
152
147
  });
153
148
  }}
154
- formProps={props.formProps}
149
+ formProps={formProps}
155
150
  />
156
151
  </ErrorBoundary>
157
152
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useMemo } from "react";
4
4
  import { CenteredView, Typography } from "@firecms/ui";
5
- import { CustomizationController, EntityCollection, FireCMSContext, FireCMSPlugin, FireCMSProps, User } from "../types";
5
+ import { CustomizationController, FireCMSContext, FireCMSPlugin, FireCMSProps, User } from "../types";
6
6
  import { AuthControllerContext } from "../contexts";
7
7
  import { useBuildSideEntityController } from "../internal/useBuildSideEntityController";
8
8
  import { useCustomizationController, useFireCMSContext } from "../hooks";
@@ -33,7 +33,7 @@ import { BreadcrumbsProvider } from "../contexts/BreacrumbsContext";
33
33
 
34
34
  * @group Core
35
35
  */
36
- export function FireCMS<USER extends User, EC extends EntityCollection>(props: FireCMSProps<USER, EC>) {
36
+ export function FireCMS<USER extends User>(props: FireCMSProps<USER>) {
37
37
 
38
38
  const {
39
39
  children,
@@ -44,7 +44,7 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
44
44
  authController,
45
45
  storageSource,
46
46
  dataSourceDelegate,
47
- plugins,
47
+ plugins: pluginsProp,
48
48
  onAnalyticsEvent,
49
49
  propertyConfigs,
50
50
  entityViews,
@@ -53,17 +53,14 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
53
53
  apiKey
54
54
  } = props;
55
55
 
56
- /**
57
- * Controller in charge of fetching and persisting data
58
- */
59
- const dataSource = useBuildDataSource({
60
- delegate: dataSourceDelegate,
61
- propertyConfigs,
62
- navigationController
63
- });
56
+ if (pluginsProp) {
57
+ console.warn("The `plugins` prop is deprecated in the FireCMS component. You should pass your plugins to `useBuildNavigationController` instead.");
58
+ }
59
+
60
+ const plugins = navigationController.plugins ?? pluginsProp;
64
61
 
65
62
  const sideDialogsController = useBuildSideDialogsController();
66
- const sideEntityController = useBuildSideEntityController(navigationController, sideDialogsController);
63
+ const sideEntityController = useBuildSideEntityController(navigationController, sideDialogsController, authController);
67
64
 
68
65
  const pluginsLoading = plugins?.some(p => p.loading) ?? false;
69
66
 
@@ -90,6 +87,16 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
90
87
  plugins
91
88
  });
92
89
 
90
+ /**
91
+ * Controller in charge of fetching and persisting data
92
+ */
93
+ const dataSource = useBuildDataSource({
94
+ delegate: dataSourceDelegate,
95
+ propertyConfigs,
96
+ navigationController,
97
+ authController
98
+ });
99
+
93
100
  if (accessResponse?.message) {
94
101
  console.warn(accessResponse.message);
95
102
  }