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

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 (84) hide show
  1. package/dist/app/Scaffold.d.ts +4 -0
  2. package/dist/components/ArrayContainer.d.ts +12 -5
  3. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  4. package/dist/components/EntityJsonPreview.d.ts +3 -0
  5. package/dist/core/DefaultAppBar.d.ts +8 -2
  6. package/dist/core/EntityEditView.d.ts +8 -1
  7. package/dist/core/FireCMS.d.ts +2 -2
  8. package/dist/form/EntityForm.d.ts +5 -1
  9. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  10. package/dist/hooks/data/save.d.ts +1 -1
  11. package/dist/index.es.js +8630 -8578
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +8663 -8612
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/internal/useBuildDataSource.d.ts +3 -2
  16. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  17. package/dist/types/collections.d.ts +6 -0
  18. package/dist/types/fields.d.ts +4 -1
  19. package/dist/types/firecms.d.ts +1 -1
  20. package/dist/types/navigation.d.ts +4 -0
  21. package/dist/types/plugins.d.ts +1 -0
  22. package/dist/types/properties.d.ts +22 -1
  23. package/dist/util/createFormexStub.d.ts +2 -0
  24. package/dist/util/navigation_utils.d.ts +2 -1
  25. package/dist/util/objects.d.ts +1 -1
  26. package/dist/util/property_utils.d.ts +2 -2
  27. package/dist/util/references.d.ts +2 -2
  28. package/dist/util/resolutions.d.ts +11 -6
  29. package/package.json +6 -5
  30. package/src/app/Scaffold.tsx +13 -3
  31. package/src/components/ArrayContainer.tsx +40 -23
  32. package/src/components/DeleteEntityDialog.tsx +4 -2
  33. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
  34. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -2
  35. package/src/components/EntityCollectionView/EntityCollectionView.tsx +22 -6
  36. package/src/components/EntityJsonPreview.tsx +66 -0
  37. package/src/components/EntityPreview.tsx +10 -3
  38. package/src/components/EntityView.tsx +4 -1
  39. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
  40. package/src/components/VirtualTable/VirtualTable.tsx +29 -1
  41. package/src/components/common/default_entity_actions.tsx +15 -4
  42. package/src/core/DefaultAppBar.tsx +17 -5
  43. package/src/core/EntityEditView.tsx +111 -35
  44. package/src/core/FireCMS.tsx +13 -12
  45. package/src/form/EntityForm.tsx +31 -23
  46. package/src/form/PropertyFieldBinding.tsx +8 -5
  47. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
  48. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -0
  49. package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
  50. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  51. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
  52. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -2
  53. package/src/form/field_bindings/RepeatFieldBinding.tsx +8 -2
  54. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  55. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +5 -2
  56. package/src/hooks/data/save.ts +24 -29
  57. package/src/hooks/useBuildNavigationController.tsx +12 -1
  58. package/src/hooks/useFireCMSContext.tsx +0 -30
  59. package/src/internal/useBuildDataSource.ts +9 -5
  60. package/src/internal/useBuildSideEntityController.tsx +26 -20
  61. package/src/preview/PropertyPreview.tsx +4 -2
  62. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +4 -3
  63. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  64. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
  65. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  66. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
  67. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
  68. package/src/routes/FireCMSRoute.tsx +15 -4
  69. package/src/types/collections.ts +7 -0
  70. package/src/types/fields.tsx +5 -1
  71. package/src/types/firecms.tsx +1 -1
  72. package/src/types/navigation.ts +5 -0
  73. package/src/types/plugins.tsx +1 -0
  74. package/src/types/properties.ts +27 -1
  75. package/src/types/side_entity_controller.tsx +5 -0
  76. package/src/util/createFormexStub.tsx +62 -0
  77. package/src/util/join_collections.ts +3 -1
  78. package/src/util/navigation_from_path.ts +5 -1
  79. package/src/util/navigation_utils.ts +64 -15
  80. package/src/util/objects.ts +5 -1
  81. package/src/util/property_utils.tsx +7 -3
  82. package/src/util/references.ts +8 -6
  83. package/src/util/resolutions.ts +17 -9
  84. package/src/util/useStorageUploadController.tsx +21 -2
@@ -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,10 +174,9 @@ 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
 
@@ -186,29 +184,66 @@ export function EntityEditViewInner<M extends Record<string, any>>({
186
184
  const subcollectionsCount = subcollections?.length ?? 0;
187
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;
190
189
 
191
190
  const {
192
191
  resolvedEntityViews,
193
192
  selectedEntityView,
194
193
  selectedSecondaryForm
195
- } = resolvedSelectedEntityView(customViews, customizationController, selectedTab);
194
+ } = resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit);
196
195
 
197
196
  const actionsAtTheBottom = !largeLayout || layout === "side_panel" || selectedEntityView?.includeActions === "bottom";
198
197
 
199
198
  const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
200
199
 
200
+ const authController = useAuthController();
201
+
201
202
  const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
202
203
  .filter(e => !e.includeActions)
203
204
  .map((customView) => {
205
+
204
206
  if (!customView)
205
207
  return null;
206
208
  const Builder = customView.Builder;
207
209
  if (!Builder) {
208
- console.error("customView.Builder is not defined");
210
+ console.error("INTERNAL: customView.Builder is not defined");
209
211
  return null;
210
212
  }
211
213
 
214
+ if (!entityId) {
215
+ console.error("INTERNAL: entityId is not defined");
216
+ return null;
217
+ }
218
+
219
+ const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
220
+ const usedFormContext: FormContext = formContext ?? {
221
+ entityId,
222
+ disabled: false,
223
+ openEntityMode: layout,
224
+ status: status,
225
+ values: usedEntity?.values ?? {},
226
+ setFieldValue: (key: string, value: any) => {
227
+ throw new Error("You can't update values in read only mode");
228
+ },
229
+ save: () => {
230
+ throw new Error("You can't save in read only mode");
231
+ },
232
+ collection: resolveCollection<M>({
233
+ collection,
234
+ path,
235
+ entityId,
236
+ values: usedEntity?.values ?? {},
237
+ previousValues: usedEntity?.values ?? {},
238
+ propertyConfigs: customizationController.propertyConfigs,
239
+ authController
240
+ }),
241
+ path,
242
+ entity: usedEntity,
243
+ savingError: undefined,
244
+ formex: formexStub
245
+ };
246
+
212
247
  return <div
213
248
  className={cls(defaultBorderMixin,
214
249
  "relative flex-1 w-full h-full overflow-auto",
@@ -217,11 +252,11 @@ export function EntityEditViewInner<M extends Record<string, any>>({
217
252
  key={`custom_view_${customView.key}`}
218
253
  role="tabpanel">
219
254
  <ErrorBoundary>
220
- {formContext && <Builder
255
+ {usedFormContext && <Builder
221
256
  collection={collection}
222
257
  entity={usedEntity}
223
- modifiedValues={formContext.formex.values ?? usedEntity?.values}
224
- formContext={formContext}
258
+ modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
259
+ formContext={usedFormContext}
225
260
  />}
226
261
  </ErrorBoundary>
227
262
  </div>;
@@ -229,9 +264,22 @@ export function EntityEditViewInner<M extends Record<string, any>>({
229
264
 
230
265
  const globalLoading = dataLoading && !usedEntity;
231
266
 
267
+ const jsonView = <div
268
+ className={cls("relative flex-1 h-full overflow-auto w-full",
269
+ { "hidden": selectedTab !== JSON_TAB_VALUE })}
270
+ key={"json_view"}
271
+ role="tabpanel">
272
+ <ErrorBoundary>
273
+ <EntityJsonPreview
274
+ values={formContext?.values ?? entity?.values ?? {}}/>
275
+ </ErrorBoundary>
276
+ </div>;
277
+
232
278
  const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
233
279
  const subcollectionId = subcollection.id ?? subcollection.path;
234
- const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
280
+ const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
281
+ const newFullIdPath = fullIdPath ? `${fullIdPath}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
282
+
235
283
  if (selectedTab !== subcollectionId) return null;
236
284
  return (
237
285
  <div
@@ -242,9 +290,10 @@ export function EntityEditViewInner<M extends Record<string, any>>({
242
290
  {globalLoading && <CircularProgressCenter/>}
243
291
 
244
292
  {!globalLoading &&
245
- (usedEntity && fullPath
293
+ (usedEntity && newFullPath
246
294
  ? <EntityCollectionView
247
- fullPath={fullPath}
295
+ fullPath={newFullPath}
296
+ fullIdPath={newFullIdPath}
248
297
  parentCollectionIds={[...parentCollectionIds, collection.id]}
249
298
  isSubCollection={true}
250
299
  updateUrl={false}
@@ -266,15 +315,29 @@ export function EntityEditViewInner<M extends Record<string, any>>({
266
315
  setSelectedTab(value);
267
316
  if (status === "existing") {
268
317
  onTabChange?.({
269
- path,
318
+ path: fullIdPath ?? path,
270
319
  entityId,
271
320
  selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
272
- collection,
321
+ collection
273
322
  });
274
323
  }
275
324
  };
276
325
 
277
- // Render the main entity form view (or a read-only view if the user cannot edit)
326
+ const entityReadOnlyView = !canEdit && entity ? <div
327
+ className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", (canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : "")}>
328
+ <div
329
+ 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")}>
330
+ <Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
331
+ {collection.singularName ?? collection.name}
332
+ </Typography>
333
+ <EntityView
334
+ className={"px-8 h-full overflow-auto"}
335
+ entity={entity}
336
+ path={path}
337
+ collection={collection}/>
338
+ </div>
339
+ </div> : null;
340
+
278
341
  const entityView = <EntityForm<M>
279
342
  collection={collection}
280
343
  path={path}
@@ -285,8 +348,9 @@ export function EntityEditViewInner<M extends Record<string, any>>({
285
348
  openEntityMode={layout}
286
349
  forceActionsAtTheBottom={actionsAtTheBottom}
287
350
  initialStatus={status}
288
- className={cls(!mainViewVisible ? "hidden" : "", formProps?.className)}
351
+ className={cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className)}
289
352
  EntityFormActionsComponent={EntityEditViewFormActions}
353
+ disabled={!canEdit}
290
354
  {...formProps}
291
355
  onEntityChange={(entity) => {
292
356
  setUsedEntity(entity);
@@ -303,7 +367,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
303
367
  onSaved={(params) => {
304
368
  const res = {
305
369
  ...params,
306
- selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
370
+ selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab
307
371
  };
308
372
  onSaved?.(res);
309
373
  formProps?.onSaved?.(res);
@@ -350,6 +414,14 @@ export function EntityEditViewInner<M extends Record<string, any>>({
350
414
  onSideTabClick(value);
351
415
  }}>
352
416
 
417
+ {includeJsonView && <Tab
418
+ disabled={!hasAdditionalViews}
419
+ value={JSON_TAB_VALUE}
420
+ innerClassName={"block"}
421
+ className={"text-sm"}>
422
+ <CodeIcon size={"small"}/>
423
+ </Tab>}
424
+
353
425
  <Tab
354
426
  disabled={!hasAdditionalViews}
355
427
  value={MAIN_TAB_VALUE}
@@ -357,6 +429,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
357
429
  {collection.singularName ?? collection.name}
358
430
  </Tab>
359
431
 
432
+
360
433
  {customViewTabs}
361
434
 
362
435
  {subcollectionTabs}
@@ -367,9 +440,12 @@ export function EntityEditViewInner<M extends Record<string, any>>({
367
440
  ? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
368
441
  <CircularProgressCenter/>
369
442
  </div>
370
- : entityView}
443
+ : <>
444
+ {entityReadOnlyView}
445
+ {entityView}
446
+ </>}
371
447
 
372
- {/*{secondaryForms}*/}
448
+ {jsonView}
373
449
 
374
450
  {customViewsView}
375
451
 
@@ -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,
@@ -53,17 +53,8 @@ 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
- });
64
-
65
56
  const sideDialogsController = useBuildSideDialogsController();
66
- const sideEntityController = useBuildSideEntityController(navigationController, sideDialogsController);
57
+ const sideEntityController = useBuildSideEntityController(navigationController, sideDialogsController, authController);
67
58
 
68
59
  const pluginsLoading = plugins?.some(p => p.loading) ?? false;
69
60
 
@@ -90,6 +81,16 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
90
81
  plugins
91
82
  });
92
83
 
84
+ /**
85
+ * Controller in charge of fetching and persisting data
86
+ */
87
+ const dataSource = useBuildDataSource({
88
+ delegate: dataSourceDelegate,
89
+ propertyConfigs,
90
+ navigationController,
91
+ authController
92
+ });
93
+
93
94
  if (accessResponse?.message) {
94
95
  console.warn(accessResponse.message);
95
96
  }
@@ -1,5 +1,6 @@
1
1
  import React, { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
2
2
  import {
3
+ AuthController,
3
4
  CMSAnalyticsEvent,
4
5
  Entity,
5
6
  EntityCollection,
@@ -28,6 +29,7 @@ import {
28
29
 
29
30
  import {
30
31
  saveEntityWithCallbacks,
32
+ useAuthController,
31
33
  useCustomizationController,
32
34
  useDataSource,
33
35
  useFireCMSContext,
@@ -71,6 +73,10 @@ export type EntityFormProps<M extends Record<string, any>> = {
71
73
  onEntityChange?: (entity: Entity<M>) => void;
72
74
  formex?: FormexController<M>;
73
75
  openEntityMode?: "side_panel" | "full_screen";
76
+ /**
77
+ * If true, the form will be disabled and no actions will be available
78
+ */
79
+ disabled?: boolean;
74
80
  /**
75
81
  * Include the copy and delete actions in the form
76
82
  */
@@ -105,6 +111,7 @@ export function EntityForm<M extends Record<string, any>>({
105
111
  onEntityChange,
106
112
  openEntityMode = "full_screen",
107
113
  formex: formexProp,
114
+ disabled: disabledProp,
108
115
  Builder,
109
116
  EntityFormActionsComponent = EntityFormActions,
110
117
  showDefaultActions = true,
@@ -116,8 +123,8 @@ export function EntityForm<M extends Record<string, any>>({
116
123
  console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
117
124
  }
118
125
 
126
+ const authController = useAuthController();
119
127
  const [status, setStatus] = useState<EntityStatus>(initialStatus);
120
- const [saving, setSaving] = useState(false);
121
128
 
122
129
  const updateStatus = (status: EntityStatus) => {
123
130
  setStatus(status);
@@ -204,7 +211,7 @@ export function EntityForm<M extends Record<string, any>>({
204
211
  };
205
212
 
206
213
  const formex: FormexController<M> = formexProp ?? useCreateFormex<M>({
207
- initialValues: (initialDirtyValues ?? getInitialEntityValues(collection, path, status, entity, customizationController.propertyConfigs)) as M,
214
+ initialValues: (initialDirtyValues ?? getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs)) as M,
208
215
  initialDirty: Boolean(initialDirtyValues),
209
216
  onSubmit,
210
217
  onReset: () => {
@@ -222,7 +229,6 @@ export function EntityForm<M extends Record<string, any>>({
222
229
  }
223
230
  });
224
231
 
225
-
226
232
  useEffect(() => {
227
233
 
228
234
  const handleKeyDown = (e: KeyboardEvent) => {
@@ -251,11 +257,11 @@ export function EntityForm<M extends Record<string, any>>({
251
257
  entityId,
252
258
  values: formex.values,
253
259
  previousValues: formex.initialValues,
254
- propertyConfigs: customizationController.propertyConfigs
260
+ propertyConfigs: customizationController.propertyConfigs,
261
+ authController
255
262
  }), [collection, path, entityId, formex.values, formex.initialValues, customizationController.propertyConfigs]);
256
263
 
257
264
  const onPreSaveHookError = useCallback((e: Error) => {
258
- setSaving(false);
259
265
  snackbarController.open({
260
266
  type: "error",
261
267
  message: "Error before saving: " + e?.message
@@ -264,7 +270,6 @@ export function EntityForm<M extends Record<string, any>>({
264
270
  }, [snackbarController]);
265
271
 
266
272
  const onSaveSuccessHookError = useCallback((e: Error) => {
267
- setSaving(false);
268
273
  snackbarController.open({
269
274
  type: "error",
270
275
  message: "Error after saving (entity is saved): " + e?.message
@@ -284,7 +289,6 @@ export function EntityForm<M extends Record<string, any>>({
284
289
 
285
290
  clearDirtyCache();
286
291
  onValuesModified?.(false);
287
- setSaving(false);
288
292
  if (!autoSave)
289
293
  snackbarController.open({
290
294
  type: "success",
@@ -306,7 +310,6 @@ export function EntityForm<M extends Record<string, any>>({
306
310
  };
307
311
 
308
312
  const onSaveFailure = useCallback((e: Error) => {
309
- setSaving(false);
310
313
  snackbarController.open({
311
314
  type: "error",
312
315
  message: "Error saving: " + e?.message
@@ -328,7 +331,6 @@ export function EntityForm<M extends Record<string, any>>({
328
331
  values: M,
329
332
  previousValues?: M,
330
333
  }) => {
331
- setSaving(true);
332
334
  return saveEntityWithCallbacks({
333
335
  path,
334
336
  entityId,
@@ -338,7 +340,7 @@ export function EntityForm<M extends Record<string, any>>({
338
340
  status,
339
341
  dataSource,
340
342
  context,
341
- onSaveSuccess: (updatedEntity: Entity<M>) => onSaveSuccess(updatedEntity),
343
+ onSaveSuccess,
342
344
  onSaveFailure,
343
345
  onPreSaveHookError,
344
346
  onSaveSuccessHookError
@@ -398,6 +400,8 @@ export function EntityForm<M extends Record<string, any>>({
398
400
  });
399
401
  };
400
402
 
403
+ const disabled = formex.isSubmitting || Boolean(disabledProp);
404
+
401
405
  const formContext: FormContext<M> = {
402
406
  // @ts-ignore
403
407
  setFieldValue: useCallback(formex.setFieldValue, []),
@@ -410,7 +414,8 @@ export function EntityForm<M extends Record<string, any>>({
410
414
  entity,
411
415
  savingError,
412
416
  status,
413
- openEntityMode
417
+ openEntityMode,
418
+ disabled
414
419
  };
415
420
 
416
421
  useEffect(() => {
@@ -427,6 +432,7 @@ export function EntityForm<M extends Record<string, any>>({
427
432
  const pluginActions: React.ReactNode[] = [];
428
433
  const plugins = customizationController.plugins;
429
434
 
435
+ const actionsDisabled = disabled || formex.isSubmitting || (status === "existing" && !formex.dirty) || Boolean(disabledProp);
430
436
  if (plugins && collection) {
431
437
  const actionProps: PluginFormActionProps = {
432
438
  entityId,
@@ -436,7 +442,8 @@ export function EntityForm<M extends Record<string, any>>({
436
442
  context,
437
443
  currentEntityId: entityId,
438
444
  formContext,
439
- openEntityMode
445
+ openEntityMode,
446
+ disabled: actionsDisabled,
440
447
  };
441
448
  pluginActions.push(...plugins.map((plugin) => (
442
449
  plugin.form?.Actions
@@ -544,7 +551,7 @@ export function EntityForm<M extends Record<string, any>>({
544
551
  !!underlyingChanges &&
545
552
  Object.keys(underlyingChanges).includes(key) &&
546
553
  formex.touched[key];
547
- const disabled = (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
554
+ const disabled = disabledProp || (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
548
555
  const hidden = isHidden(property);
549
556
  if (hidden) return null;
550
557
  const widthPercentage = property.widthPercentage ?? 100;
@@ -654,8 +661,6 @@ export function EntityForm<M extends Record<string, any>>({
654
661
  onIdChange(entityId);
655
662
  }, [entityId, onIdChange]);
656
663
 
657
- const disabled = formex.isSubmitting || (!formex.dirty && status === "existing");
658
-
659
664
  if (!resolvedCollection || !path) {
660
665
  throw Error("INTERNAL: Collection and path must be defined in form context");
661
666
  }
@@ -667,7 +672,7 @@ export function EntityForm<M extends Record<string, any>>({
667
672
  layout={forceActionsAtTheBottom ? "bottom" : "side"}
668
673
  savingError={savingError}
669
674
  formex={formex}
670
- disabled={disabled}
675
+ disabled={actionsDisabled}
671
676
  status={status}
672
677
  pluginActions={pluginActions ?? []}
673
678
  openEntityMode={openEntityMode}
@@ -675,11 +680,11 @@ export function EntityForm<M extends Record<string, any>>({
675
680
  />;
676
681
 
677
682
  return (
678
- <Formex value={formContext.formex}>
683
+ <Formex value={formex}>
679
684
  <form
680
- onSubmit={formContext.formex.handleSubmit}
685
+ onSubmit={formex.handleSubmit}
681
686
  onReset={() => formex.resetForm({
682
- values: getInitialEntityValues(collection, path, status, entity, customizationController.propertyConfigs) as M
687
+ values: getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs) as M
683
688
  })}
684
689
  noValidate
685
690
  className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className)}>
@@ -689,8 +694,8 @@ export function EntityForm<M extends Record<string, any>>({
689
694
 
690
695
  <div className={cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10")}>
691
696
 
692
- {formContext.formex.dirty
693
- ? <Tooltip title={"Unsaved changes"}
697
+ {formex.dirty
698
+ ? <Tooltip title={"Local unsaved changes"}
694
699
  className={"self-end sticky top-4 z-10"}>
695
700
  <Chip size={"small"} colorScheme={"orangeDarker"}>
696
701
  <EditIcon size={"smallest"}/>
@@ -716,17 +721,19 @@ export function EntityForm<M extends Record<string, any>>({
716
721
  }
717
722
 
718
723
  function getInitialEntityValues<M extends object>(
724
+ authController: AuthController,
719
725
  collection: EntityCollection,
720
726
  path: string,
721
727
  status: "new" | "existing" | "copy",
722
728
  entity: Entity<M> | undefined,
723
- propertyConfigs?: Record<string, PropertyConfig>
729
+ propertyConfigs?: Record<string, PropertyConfig>,
724
730
  ): Partial<EntityValues<M>> {
725
731
  const resolvedCollection = resolveCollection({
726
732
  collection,
727
733
  path,
728
734
  values: entity?.values,
729
- propertyConfigs
735
+ propertyConfigs,
736
+ authController
730
737
  });
731
738
  const properties = resolvedCollection.properties;
732
739
  if ((status === "existing" || status === "copy") && entity) {
@@ -772,3 +779,4 @@ function useOnAutoSave(autoSave: undefined | boolean, formex: FormexController<a
772
779
  }
773
780
  }, [formex.values]);
774
781
  }
782
+
@@ -17,7 +17,7 @@ import {
17
17
  import { ReadOnlyFieldBinding } from "./field_bindings/ReadOnlyFieldBinding";
18
18
 
19
19
  import { isHidden, isPropertyBuilder, isReadOnly, resolveProperty } from "../util";
20
- import { useCustomizationController } from "../hooks";
20
+ import { useAuthController, useCustomizationController } from "../hooks";
21
21
  import { Typography } from "@firecms/ui";
22
22
  import { getFieldConfig, getFieldId } from "../core";
23
23
  import { ErrorBoundary } from "../components";
@@ -87,9 +87,10 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
87
87
  autoFocus,
88
88
  index,
89
89
  size,
90
- onPropertyChange
90
+ onPropertyChange,
91
91
  }: PropertyFieldBindingProps<T, M>): ReactElement<PropertyFieldBindingProps<T, M>> {
92
92
 
93
+ const authController = useAuthController();
93
94
  const customizationController = useCustomizationController();
94
95
 
95
96
  return (
@@ -107,10 +108,11 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
107
108
  path: context.path,
108
109
  entityId: context.entityId,
109
110
  propertyConfigs: customizationController.propertyConfigs,
110
- index
111
+ index,
112
+ authController
111
113
  });
112
114
 
113
- const disabled = disabledProp || isReadOnly(resolvedProperty) || Boolean(resolvedProperty?.disabled);
115
+ const disabled = disabledProp || isReadOnly(resolvedProperty) || Boolean(resolvedProperty?.disabled) || context.disabled;
114
116
 
115
117
  if (resolvedProperty === null || isHidden(resolvedProperty)) {
116
118
  return <></>;
@@ -139,7 +141,8 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
139
141
  path: context.path,
140
142
  entityId: context.entityId,
141
143
  propertyConfigs: customizationController.propertyConfigs,
142
- index
144
+ index,
145
+ authController
143
146
  });
144
147
  Component = configProperty.Field as ComponentType<FieldProps<T>>;
145
148
  }
@@ -5,6 +5,7 @@ import { PropertyFieldBinding } from "../PropertyFieldBinding";
5
5
  import { ExpandablePanel, Typography } from "@firecms/ui";
6
6
  import { getArrayResolvedProperties, getIconForProperty, isReadOnly } from "../../util";
7
7
  import { useClearRestoreValue } from "../useClearRestoreValue";
8
+ import { useAuthController } from "../../hooks";
8
9
 
9
10
  /**
10
11
  * Array field used for custom
@@ -27,6 +28,7 @@ export function ArrayCustomShapedFieldBinding<T extends Array<any>>({
27
28
  disabled
28
29
  }: FieldProps<T, any, any>) {
29
30
 
31
+ const authController = useAuthController();
30
32
  const minimalistView = minimalistViewProp || property.minimalistView;
31
33
 
32
34
  let resolvedProperties = "resolvedProperties" in property ? property.resolvedProperties : undefined;
@@ -35,7 +37,8 @@ export function ArrayCustomShapedFieldBinding<T extends Array<any>>({
35
37
  propertyValue: value,
36
38
  propertyKey,
37
39
  property,
38
- ignoreMissingFields: false
40
+ ignoreMissingFields: false,
41
+ authController
39
42
  })
40
43
  }
41
44
 
@@ -120,6 +120,7 @@ export function ArrayOfReferencesFieldBinding({
120
120
  value={value}
121
121
  disabled={isSubmitting}
122
122
  buildEntry={buildEntry}
123
+ canAddElements={false}
123
124
  addLabel={property.name ? "Add reference to " + property.name : "Add reference"}
124
125
  newDefaultEntry={property.of.defaultValue}
125
126
  onValueChange={(value) => setFieldValue(propertyKey, value)}