@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.
- package/dist/app/Scaffold.d.ts +4 -0
- package/dist/components/ArrayContainer.d.ts +12 -5
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
- package/dist/components/EntityJsonPreview.d.ts +3 -0
- package/dist/core/DefaultAppBar.d.ts +8 -2
- package/dist/core/EntityEditView.d.ts +8 -1
- package/dist/core/FireCMS.d.ts +2 -2
- package/dist/form/EntityForm.d.ts +5 -1
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/index.es.js +8630 -8578
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +8663 -8612
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +3 -2
- package/dist/internal/useBuildSideEntityController.d.ts +3 -3
- package/dist/types/collections.d.ts +6 -0
- package/dist/types/fields.d.ts +4 -1
- package/dist/types/firecms.d.ts +1 -1
- package/dist/types/navigation.d.ts +4 -0
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/properties.d.ts +22 -1
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/navigation_utils.d.ts +2 -1
- package/dist/util/objects.d.ts +1 -1
- package/dist/util/property_utils.d.ts +2 -2
- package/dist/util/references.d.ts +2 -2
- package/dist/util/resolutions.d.ts +11 -6
- package/package.json +6 -5
- package/src/app/Scaffold.tsx +13 -3
- package/src/components/ArrayContainer.tsx +40 -23
- package/src/components/DeleteEntityDialog.tsx +4 -2
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -2
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +22 -6
- package/src/components/EntityJsonPreview.tsx +66 -0
- package/src/components/EntityPreview.tsx +10 -3
- package/src/components/EntityView.tsx +4 -1
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
- package/src/components/VirtualTable/VirtualTable.tsx +29 -1
- package/src/components/common/default_entity_actions.tsx +15 -4
- package/src/core/DefaultAppBar.tsx +17 -5
- package/src/core/EntityEditView.tsx +111 -35
- package/src/core/FireCMS.tsx +13 -12
- package/src/form/EntityForm.tsx +31 -23
- package/src/form/PropertyFieldBinding.tsx +8 -5
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -0
- package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +8 -2
- package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +5 -2
- package/src/hooks/data/save.ts +24 -29
- package/src/hooks/useBuildNavigationController.tsx +12 -1
- package/src/hooks/useFireCMSContext.tsx +0 -30
- package/src/internal/useBuildDataSource.ts +9 -5
- package/src/internal/useBuildSideEntityController.tsx +26 -20
- package/src/preview/PropertyPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
- package/src/routes/FireCMSRoute.tsx +15 -4
- package/src/types/collections.ts +7 -0
- package/src/types/fields.tsx +5 -1
- package/src/types/firecms.tsx +1 -1
- package/src/types/navigation.ts +5 -0
- package/src/types/plugins.tsx +1 -0
- package/src/types/properties.ts +27 -1
- package/src/types/side_entity_controller.tsx +5 -0
- package/src/util/createFormexStub.tsx +62 -0
- package/src/util/join_collections.ts +3 -1
- package/src/util/navigation_from_path.ts +5 -1
- package/src/util/navigation_utils.ts +64 -15
- package/src/util/objects.ts +5 -1
- package/src/util/property_utils.tsx +7 -3
- package/src/util/references.ts +8 -6
- package/src/util/resolutions.ts +17 -9
- 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 = "
|
|
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
|
|
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
|
-
{
|
|
255
|
+
{usedFormContext && <Builder
|
|
221
256
|
collection={collection}
|
|
222
257
|
entity={usedEntity}
|
|
223
|
-
modifiedValues={
|
|
224
|
-
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
|
|
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 &&
|
|
293
|
+
(usedEntity && newFullPath
|
|
246
294
|
? <EntityCollectionView
|
|
247
|
-
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
|
-
|
|
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
|
-
:
|
|
443
|
+
: <>
|
|
444
|
+
{entityReadOnlyView}
|
|
445
|
+
{entityView}
|
|
446
|
+
</>}
|
|
371
447
|
|
|
372
|
-
{
|
|
448
|
+
{jsonView}
|
|
373
449
|
|
|
374
450
|
{customViewsView}
|
|
375
451
|
|
package/src/core/FireCMS.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useMemo } from "react";
|
|
4
4
|
import { CenteredView, Typography } from "@firecms/ui";
|
|
5
|
-
import { CustomizationController,
|
|
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
|
|
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
|
}
|
package/src/form/EntityForm.tsx
CHANGED
|
@@ -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
|
|
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={
|
|
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={
|
|
683
|
+
<Formex value={formex}>
|
|
679
684
|
<form
|
|
680
|
-
onSubmit={
|
|
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
|
-
{
|
|
693
|
-
? <Tooltip title={"
|
|
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)}
|