@firecms/core 3.0.0-canary.66 → 3.0.0-canary.67

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 (40) hide show
  1. package/dist/core/EntityEditView.d.ts +17 -3
  2. package/dist/form/PropertiesForm.d.ts +8 -0
  3. package/dist/form/components/FieldHelperText.d.ts +3 -3
  4. package/dist/form/components/StorageItemPreview.d.ts +2 -4
  5. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  6. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +2 -4
  7. package/dist/form/index.d.ts +0 -2
  8. package/dist/index.es.js +4269 -4322
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/index.umd.js +5 -5
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/types/collections.d.ts +14 -0
  13. package/dist/types/fields.d.ts +31 -30
  14. package/dist/types/plugins.d.ts +2 -2
  15. package/dist/types/properties.d.ts +1 -1
  16. package/dist/util/storage.d.ts +23 -2
  17. package/dist/util/useStorageUploadController.d.ts +1 -1
  18. package/package.json +4 -4
  19. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +2 -1
  20. package/src/core/EntityEditView.tsx +662 -120
  21. package/src/core/EntitySidePanel.tsx +0 -1
  22. package/src/form/PropertiesForm.tsx +81 -0
  23. package/src/form/PropertyFieldBinding.tsx +28 -5
  24. package/src/form/components/FieldHelperText.tsx +3 -3
  25. package/src/form/components/StorageItemPreview.tsx +0 -4
  26. package/src/form/field_bindings/MapFieldBinding.tsx +10 -3
  27. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +0 -7
  28. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +3 -26
  29. package/src/form/index.tsx +4 -4
  30. package/src/form/validation.ts +1 -17
  31. package/src/types/collections.ts +14 -0
  32. package/src/types/customization_controller.tsx +0 -1
  33. package/src/types/fields.tsx +33 -33
  34. package/src/types/plugins.tsx +2 -2
  35. package/src/types/properties.ts +1 -1
  36. package/src/util/permissions.ts +1 -0
  37. package/src/util/storage.ts +75 -21
  38. package/src/util/useStorageUploadController.tsx +21 -3
  39. package/dist/form/EntityForm.d.ts +0 -77
  40. package/src/form/EntityForm.tsx +0 -735
@@ -1,735 +0,0 @@
1
- import React, { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
-
3
- import {
4
- CMSAnalyticsEvent,
5
- Entity,
6
- EntityAction,
7
- EntityCollection,
8
- EntityStatus,
9
- EntityValues,
10
- FormContext,
11
- PluginFormActionProps,
12
- PropertyFieldBindingProps,
13
- ResolvedEntityCollection
14
- } from "../types";
15
- import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
16
- import { PropertyFieldBinding } from "./PropertyFieldBinding";
17
- import { CustomFieldValidator, getYupEntitySchema } from "./validation";
18
- import equal from "react-fast-compare"
19
- import {
20
- canCreateEntity,
21
- canDeleteEntity,
22
- getDefaultValuesFor,
23
- getEntityTitlePropertyKey,
24
- getValueInPath,
25
- isHidden,
26
- isReadOnly,
27
- resolveCollection
28
- } from "../util";
29
- import {
30
- useAuthController,
31
- useCustomizationController,
32
- useDataSource,
33
- useFireCMSContext,
34
- useSideEntityController
35
- } from "../hooks";
36
- import { ErrorFocus } from "./components/ErrorFocus";
37
- import { CustomIdField } from "./components/CustomIdField";
38
- import { Alert, Button, CircularProgress, cls, DialogActions, IconButton, Tooltip, Typography } from "@firecms/ui";
39
- import { CircularProgressCenter, ErrorBoundary } from "../components";
40
- import { copyEntityAction, deleteEntityAction } from "../components/common/default_entity_actions";
41
- import { useAnalyticsController } from "../hooks/useAnalyticsController";
42
- import { ValidationError } from "yup";
43
- import { PropertyIdCopyTooltipContent } from "../components/PropertyIdCopyTooltipContent";
44
-
45
- /**
46
- * @group Components
47
- */
48
- export interface EntityFormProps<M extends Record<string, any>> {
49
-
50
- /**
51
- * New or existing status
52
- */
53
- status: EntityStatus;
54
-
55
- /**
56
- * Path of the collection this entity is located
57
- */
58
- path: string;
59
-
60
- /**
61
- * The collection is used to build the fields of the form
62
- */
63
- collection: EntityCollection<M>
64
-
65
- /**
66
- * The updated entity is passed from the parent component when the underlying data
67
- * has changed in the datasource
68
- */
69
- entity?: Entity<M>;
70
-
71
- /**
72
- * The callback function called when Save is clicked and validation is correct
73
- */
74
- onEntitySaveRequested: (
75
- props: EntityFormSaveParams<M>
76
- ) => Promise<void>;
77
-
78
- /**
79
- * The callback function called when discard is clicked
80
- */
81
- onDiscard?: () => void;
82
-
83
- /**
84
- * The callback function when the form is dirty, so the values are different
85
- * from the original ones
86
- */
87
- onModified?: (dirty: boolean) => void;
88
-
89
- /**
90
- * The callback function when the form original values have been modified
91
- */
92
- onValuesChanged?: (values?: EntityValues<M>) => void;
93
-
94
- /**
95
- *
96
- * @param id
97
- */
98
- onIdChange?: (id: string) => void;
99
-
100
- currentEntityId?: string;
101
-
102
- onFormContextChange?: (formContext: FormContext<M>) => void;
103
-
104
- hideId?: boolean;
105
-
106
- autoSave?: boolean;
107
-
108
- onIdUpdateError?: (error: any) => void;
109
-
110
- }
111
-
112
- export type EntityFormSaveParams<M extends Record<string, any>> = {
113
- collection: ResolvedEntityCollection<M>,
114
- path: string,
115
- entityId: string | undefined,
116
- values: EntityValues<M>,
117
- previousValues?: EntityValues<M>,
118
- closeAfterSave: boolean,
119
- autoSave: boolean
120
- };
121
-
122
- /**
123
- * This is the form used internally by the CMS
124
- * @param status
125
- * @param path
126
- * @param collection
127
- * @param entity
128
- * @param onEntitySave
129
- * @param onDiscard
130
- * @param onModified
131
- * @param onValuesChanged
132
- * @constructor
133
- * @group Components
134
- */
135
- export const EntityForm = React.memo<EntityFormProps<any>>(EntityFormInternal,
136
- (a: EntityFormProps<any>, b: EntityFormProps<any>) => {
137
- return a.status === b.status &&
138
- a.path === b.path &&
139
- equal(a.entity?.values, b.entity?.values);
140
- }) as typeof EntityFormInternal;
141
-
142
- function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
143
- status: "new" | "existing" | "copy",
144
- entity: Entity<M> | undefined): Partial<EntityValues<M>> {
145
- const properties = initialResolvedCollection.properties;
146
- if ((status === "existing" || status === "copy") && entity) {
147
- return entity.values ?? getDefaultValuesFor(properties);
148
- } else if (status === "new") {
149
- return getDefaultValuesFor(properties);
150
- } else {
151
- console.error({
152
- status,
153
- entity
154
- });
155
- throw new Error("Form has not been initialised with the correct parameters");
156
- }
157
- }
158
-
159
- function EntityFormInternal<M extends Record<string, any>>({
160
- status,
161
- path,
162
- collection: inputCollection,
163
- entity,
164
- onEntitySaveRequested,
165
- onDiscard,
166
- onModified,
167
- onValuesChanged,
168
- onIdChange,
169
- onFormContextChange,
170
- hideId,
171
- autoSave,
172
- onIdUpdateError,
173
- }: EntityFormProps<M>) {
174
-
175
- const analyticsController = useAnalyticsController();
176
-
177
- const customizationController = useCustomizationController();
178
-
179
- const context = useFireCMSContext();
180
- const dataSource = useDataSource(inputCollection);
181
- const plugins = customizationController.plugins;
182
-
183
- const initialResolvedCollection = useMemo(() => resolveCollection({
184
- collection: inputCollection,
185
- path,
186
- values: entity?.values,
187
- fields: customizationController.propertyConfigs
188
- }), [entity?.values, path, customizationController.propertyConfigs]);
189
-
190
- const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
191
- (Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
192
-
193
- const initialEntityId = useMemo(() => {
194
- if (status === "new" || status === "copy") {
195
- if (mustSetCustomId) {
196
- return undefined;
197
- } else {
198
- return dataSource.generateEntityId(path);
199
- }
200
- } else {
201
- return entity?.id;
202
- }
203
- }, []);
204
-
205
- const closeAfterSaveRef = useRef(false);
206
-
207
- const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, entity));
208
-
209
- const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
210
- const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
211
- const [savingError, setSavingError] = React.useState<Error | undefined>();
212
-
213
- const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
214
-
215
- // const initialValuesRef = useRef<EntityValues<M>>(entity?.values ?? baseDataSourceValues as EntityValues<M>);
216
- const [internalValues, setInternalValues] = useState<EntityValues<M> | undefined>(entity?.values ?? baseDataSourceValuesRef.current as EntityValues<M>);
217
-
218
- const save = (values: EntityValues<M>): Promise<void> => {
219
- return onEntitySaveRequested({
220
- collection: resolvedCollection,
221
- path,
222
- entityId,
223
- values,
224
- previousValues: entity?.values,
225
- closeAfterSave: closeAfterSaveRef.current,
226
- autoSave: autoSave ?? false
227
- }).then(_ => {
228
- const eventName: CMSAnalyticsEvent = status === "new"
229
- ? "new_entity_saved"
230
- : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
231
- analyticsController.onAnalyticsEvent?.(eventName, { path });
232
- }).catch(e => {
233
- console.error(e);
234
- setSavingError(e);
235
- }).finally(() => {
236
- closeAfterSaveRef.current = false;
237
- });
238
- };
239
-
240
- const onSubmit = (values: EntityValues<M>, formexController: FormexController<EntityValues<M>>) => {
241
-
242
- if (mustSetCustomId && !entityId) {
243
- console.error("Missing custom Id");
244
- setEntityIdError(true);
245
- formexController.setSubmitting(false);
246
- return;
247
- }
248
-
249
- setSavingError(undefined);
250
- setEntityIdError(false);
251
-
252
- if (status === "existing") {
253
- if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
254
- } else if (status === "new" || status === "copy") {
255
- if (inputCollection.customId) {
256
- if (inputCollection.customId !== "optional" && !entityId) {
257
- throw Error("Form misconfiguration when saving, entityId should be set");
258
- }
259
- }
260
- } else {
261
- throw Error("New FormType added, check EntityForm");
262
- }
263
-
264
- return save(values)
265
- ?.then(_ => {
266
- formexController.resetForm({
267
- values,
268
- submitCount: 0,
269
- touched: {}
270
- });
271
- })
272
- .finally(() => {
273
- formexController.setSubmitting(false);
274
- });
275
-
276
- };
277
-
278
- const formex: FormexController<M> = useCreateFormex<M>({
279
- initialValues: baseDataSourceValuesRef.current as M,
280
- onSubmit,
281
- validation: (values) => {
282
- return validationSchema?.validate(values, { abortEarly: false })
283
- .then(() => {
284
- return {};
285
- })
286
- .catch((e) => {
287
-
288
- const errors: Record<string, string> = {};
289
- e.inner.forEach((error: any) => {
290
- errors[error.path] = error.message;
291
- });
292
- return yupToFormErrors(e);
293
- });
294
- }
295
- });
296
-
297
- useEffect(() => {
298
- baseDataSourceValuesRef.current = getDataSourceEntityValues(initialResolvedCollection, status, entity);
299
- const initialValues = formex.initialValues;
300
- if (!formex.isSubmitting && initialValues && status === "existing") {
301
- setUnderlyingChanges(
302
- Object.entries(resolvedCollection.properties)
303
- .map(([key, property]) => {
304
- if (isHidden(property)) {
305
- return {};
306
- }
307
- const initialValue = initialValues[key];
308
- const latestValue = baseDataSourceValuesRef.current[key];
309
- if (!equal(initialValue, latestValue)) {
310
- return { [key]: latestValue };
311
- }
312
- return {};
313
- })
314
- .reduce((a, b) => ({ ...a, ...b }), {}) as Partial<EntityValues<M>>
315
- );
316
- } else {
317
- setUnderlyingChanges({});
318
- }
319
- }, [entity, initialResolvedCollection, status]);
320
-
321
- const doOnValuesChanges = (values?: EntityValues<M>) => {
322
- const initialValues = formex.initialValues;
323
- setInternalValues(values);
324
- if (onValuesChanged)
325
- onValuesChanged(values);
326
- if (autoSave && values && !equal(values, initialValues)) {
327
- save(values);
328
- }
329
- };
330
-
331
- useEffect(() => {
332
- if (entityId && onIdChange)
333
- onIdChange(entityId);
334
- }, [entityId, onIdChange]);
335
-
336
- const resolvedCollection = resolveCollection<M>({
337
- collection: inputCollection,
338
- path,
339
- entityId,
340
- values: internalValues,
341
- previousValues: formex.initialValues,
342
- fields: customizationController.propertyConfigs
343
- });
344
-
345
- const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
346
- const title = internalValues && titlePropertyKey ? getValueInPath(internalValues, titlePropertyKey) : undefined;
347
-
348
- const onIdUpdate = inputCollection.callbacks?.onIdUpdate;
349
-
350
- const doOnIdUpdate = useCallback(async () => {
351
- if (onIdUpdate && internalValues && (status === "new" || status === "copy")) {
352
- setCustomIdLoading(true);
353
- try {
354
- const updatedId = await onIdUpdate({
355
- collection: resolvedCollection,
356
- path,
357
- entityId,
358
- values: internalValues,
359
- context
360
- });
361
- setEntityId(updatedId);
362
- } catch (e) {
363
- onIdUpdateError && onIdUpdateError(e);
364
- console.error(e);
365
- }
366
- setCustomIdLoading(false);
367
- }
368
- }, [entityId, internalValues, status]);
369
-
370
- useEffect(() => {
371
- doOnIdUpdate();
372
- }, [doOnIdUpdate]);
373
-
374
- const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
375
-
376
- const uniqueFieldValidator: CustomFieldValidator = useCallback(({
377
- name,
378
- value,
379
- property
380
- }) => dataSource.checkUniqueField(path, name, value, entityId),
381
- [dataSource, path, entityId]);
382
-
383
- const validationSchema = useMemo(() => entityId
384
- ? getYupEntitySchema(
385
- entityId,
386
- resolvedCollection.properties,
387
- uniqueFieldValidator)
388
- : undefined,
389
- [entityId, resolvedCollection.properties, uniqueFieldValidator]);
390
-
391
- const authController = useAuthController();
392
-
393
- const getActionsForEntity = useCallback(({
394
- entity,
395
- customEntityActions
396
- }: {
397
- entity?: Entity<M>,
398
- customEntityActions?: EntityAction[]
399
- }): EntityAction[] => {
400
- const createEnabled = canCreateEntity(inputCollection, authController, path, null);
401
- const deleteEnabled = entity ? canDeleteEntity(inputCollection, authController, path, entity) : true;
402
- const actions: EntityAction[] = [];
403
- if (createEnabled)
404
- actions.push(copyEntityAction);
405
- if (deleteEnabled)
406
- actions.push(deleteEntityAction);
407
- if (customEntityActions)
408
- actions.push(...customEntityActions);
409
- return actions;
410
- }, [authController, inputCollection, path]);
411
-
412
- const pluginActions: React.ReactNode[] = [];
413
-
414
- const formContext: FormContext<M> = {
415
- // @ts-ignore
416
- setFieldValue: useCallback(formex.setFieldValue, []),
417
- values: formex.values,
418
- collection: resolvedCollection,
419
- entityId,
420
- path,
421
- save
422
- };
423
-
424
- const submittedFormContext = useRef<FormContext<M> | null>(null);
425
- // eslint-disable-next-line react-hooks/rules-of-hooks
426
- useEffect(() => {
427
- if (onFormContextChange && !formContextsEqual(submittedFormContext.current ?? undefined, formContext)) {
428
- onFormContextChange(formContext);
429
- submittedFormContext.current = formContext;
430
- }
431
- }, [formContext, onFormContextChange]);
432
-
433
- if (plugins && inputCollection) {
434
- const actionProps: PluginFormActionProps = {
435
- entityId,
436
- path,
437
- status,
438
- collection: inputCollection,
439
- context,
440
- currentEntityId: entityId,
441
- formContext
442
- };
443
- pluginActions.push(...plugins.map((plugin, i) => (
444
- plugin.form?.Actions
445
- ? <plugin.form.Actions
446
- key={`actions_${plugin.key}`} {...actionProps}/>
447
- : null
448
- )).filter(Boolean));
449
- }
450
-
451
- return <Formex value={formex}>
452
- <div className="h-full overflow-auto">
453
-
454
- {pluginActions.length > 0 && <div
455
- className={cls("w-full flex justify-end items-center sticky top-0 right-0 left-0 z-10 bg-opacity-60 bg-slate-200 dark:bg-opacity-60 dark:bg-slate-800 backdrop-blur-md")}>
456
- {pluginActions}
457
- </div>}
458
-
459
- <div className="pt-12 pb-16 pl-8 pr-8 md:pl-10 md:pr-10">
460
- <div
461
- className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
462
-
463
- <Typography
464
- className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
465
- variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
466
- </Typography>
467
- <Alert color={"base"} className={"w-full"} size={"small"}>
468
- <code className={"text-xs select-all"}>{path}/{entityId}</code>
469
- </Alert>
470
- </div>
471
-
472
- {!hideId &&
473
- <CustomIdField customId={inputCollection.customId}
474
- entityId={entityId}
475
- status={status}
476
- onChange={setEntityId}
477
- error={entityIdError}
478
- loading={customIdLoading}
479
- entity={entity}/>}
480
-
481
- {entityId && <InnerForm
482
- {...formex}
483
- initialValues={formex.initialValues}
484
- onModified={onModified}
485
- onDiscard={onDiscard}
486
- onValuesChanged={doOnValuesChanges}
487
- underlyingChanges={underlyingChanges}
488
- entity={entity}
489
- resolvedCollection={resolvedCollection}
490
- formContext={formContext}
491
- status={status}
492
- savingError={savingError}
493
- closeAfterSaveRef={closeAfterSaveRef}
494
- autoSave={autoSave}
495
- entityActions={getActionsForEntity({
496
- entity,
497
- customEntityActions: inputCollection.entityActions
498
- })}/>}
499
-
500
- </div>
501
- </div>
502
- </Formex>
503
- }
504
-
505
- function InnerForm<M extends Record<string, any>>(props: FormexController<M> & {
506
- initialValues: EntityValues<M>,
507
- onModified: ((modified: boolean) => void) | undefined,
508
- onValuesChanged?: (changedValues?: EntityValues<M>) => void,
509
- underlyingChanges: Partial<M>,
510
- entity: Entity<M> | undefined,
511
- resolvedCollection: ResolvedEntityCollection<M>,
512
- formContext: FormContext<M>,
513
- onDiscard?: () => void,
514
- status: "new" | "existing" | "copy",
515
- savingError?: Error,
516
- closeAfterSaveRef: MutableRefObject<boolean>,
517
- autoSave?: boolean,
518
- entityActions: EntityAction[],
519
- }) {
520
-
521
- const {
522
- values,
523
- onDiscard,
524
- onModified,
525
- onValuesChanged,
526
- underlyingChanges,
527
- formContext,
528
- entity,
529
- touched,
530
- setFieldValue,
531
- resolvedCollection,
532
- isSubmitting,
533
- status,
534
- handleSubmit,
535
- resetForm,
536
- savingError,
537
- dirty,
538
- closeAfterSaveRef,
539
- autoSave,
540
- entityActions,
541
- } = props;
542
-
543
- const context = useFireCMSContext();
544
- const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
545
- const sideEntityController = useSideEntityController();
546
-
547
- const modified = dirty;
548
- useEffect(() => {
549
- if (onModified)
550
- onModified(modified);
551
- if (onValuesChanged)
552
- onValuesChanged(values);
553
- }, [modified, values]);
554
-
555
- useEffect(() => {
556
- if (!autoSave && !isSubmitting && underlyingChanges && entity) {
557
- // we update the form fields from the Firestore data
558
- // if they were not touched
559
- Object.entries(underlyingChanges).forEach(([key, value]) => {
560
- const formValue = values[key];
561
- if (!equal(value, formValue) && !touched[key]) {
562
- console.debug("Updated value from the datasource:", key, value);
563
- setFieldValue(key, value !== undefined ? value : null);
564
- }
565
- });
566
- }
567
- }, [isSubmitting, autoSave, underlyingChanges, entity, values, touched, setFieldValue]);
568
-
569
- const formFields = (
570
- <div className={"flex flex-col gap-8"}>
571
- {(resolvedCollection.propertiesOrder ?? Object.keys(resolvedCollection.properties))
572
- .map((key) => {
573
-
574
- const property = resolvedCollection.properties[key];
575
- if (!property) {
576
- console.warn(`Property ${key} not found in collection ${resolvedCollection.name}`);
577
- return null;
578
- }
579
-
580
- const underlyingValueHasChanged: boolean =
581
- !!underlyingChanges &&
582
- Object.keys(underlyingChanges).includes(key) &&
583
- !!touched[key];
584
-
585
- const disabled = (!autoSave && isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
586
- const hidden = isHidden(property);
587
- if (hidden) return null;
588
- const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
589
- propertyKey: key,
590
- disabled,
591
- property,
592
- includeDescription: property.description || property.longDescription,
593
- underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
594
- context: formContext,
595
- tableMode: false,
596
- partOfArray: false,
597
- partOfBlock: false,
598
- autoFocus: false
599
- };
600
-
601
- return (
602
- <div id={`form_field_${key}`}
603
- key={`field_${resolvedCollection.name}_${key}`}>
604
- <ErrorBoundary>
605
- <Tooltip title={<PropertyIdCopyTooltipContent propertyId={key}/>}
606
- delayDuration={800}
607
- side={"left"}
608
- align={"start"}
609
- sideOffset={16}>
610
- <PropertyFieldBinding {...cmsFormFieldProps}/>
611
- </Tooltip>
612
- </ErrorBoundary>
613
- </div>
614
- );
615
- })
616
- .filter(Boolean)}
617
-
618
- </div>
619
- );
620
-
621
- const disabled = isSubmitting || (!modified && status === "existing");
622
- const formRef = React.useRef<HTMLDivElement>(null);
623
-
624
- return (
625
-
626
- <form onSubmit={handleSubmit}
627
- onReset={() => {
628
- console.debug("Resetting form")
629
- resetForm();
630
- return onDiscard && onDiscard();
631
- }}
632
- noValidate>
633
- <div className="mt-12"
634
- ref={formRef}>
635
-
636
- {formFields}
637
-
638
- <ErrorFocus containerRef={formRef}/>
639
-
640
- </div>
641
-
642
- <div className="h-14"/>
643
-
644
- {!autoSave && <DialogActions position={"absolute"}>
645
-
646
- {savingError &&
647
- <div className="text-right">
648
- <Typography color={"error"}>
649
- {savingError.message}
650
- </Typography>
651
- </div>}
652
-
653
- {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
654
- {formActions.map(action => (
655
- <IconButton
656
- key={action.name}
657
- color="primary"
658
- onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
659
- event.stopPropagation();
660
- if (entity)
661
- action.onClick({
662
- entity,
663
- fullPath: resolvedCollection.path,
664
- collection: resolvedCollection,
665
- context,
666
- sideEntityController,
667
- });
668
- }}>
669
- {action.icon}
670
- </IconButton>
671
- ))}
672
- </div>}
673
- {isSubmitting && <CircularProgress size={"small"}/>}
674
- <Button
675
- variant="text"
676
- disabled={disabled || isSubmitting}
677
- type="reset"
678
- >
679
- {status === "existing" ? "Discard" : "Clear"}
680
- </Button>
681
-
682
- <Button
683
- variant="text"
684
- color="primary"
685
- type="submit"
686
- disabled={disabled || isSubmitting}
687
- onClick={() => {
688
- closeAfterSaveRef.current = false;
689
- }}
690
- >
691
- {status === "existing" && "Save"}
692
- {status === "copy" && "Create copy"}
693
- {status === "new" && "Create"}
694
- </Button>
695
-
696
- <Button
697
- variant="filled"
698
- color="primary"
699
- type="submit"
700
- disabled={disabled || isSubmitting}
701
- onClick={() => {
702
- closeAfterSaveRef.current = true;
703
- }}
704
- >
705
- {status === "existing" && "Save and close"}
706
- {status === "copy" && "Create copy and close"}
707
- {status === "new" && "Create and close"}
708
- </Button>
709
-
710
- </DialogActions>}
711
- </form>
712
- );
713
- }
714
-
715
- export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
716
- let errors: Record<string, any> = {};
717
- if (yupError.inner) {
718
- if (yupError.inner.length === 0) {
719
- return setIn(errors, yupError.path!, yupError.message);
720
- }
721
- for (const err of yupError.inner) {
722
- if (!getIn(errors, err.path!)) {
723
- errors = setIn(errors, err.path!, err.message);
724
- }
725
- }
726
- }
727
- return errors;
728
- }
729
-
730
- function formContextsEqual(a: FormContext<any> | undefined, b: FormContext<any> | undefined): boolean {
731
- return a?.path === b?.path &&
732
- a?.entityId === b?.entityId &&
733
- equal(a?.values, b?.values) &&
734
- equal(a?.collection, b?.collection);
735
- }