@firecms/core 3.0.0-canary.168 → 3.0.0-canary.169
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/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/index.d.ts +1 -1
- package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
- package/dist/components/common/useDebouncedCallback.d.ts +1 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/contexts/BreacrumbsContext.d.ts +8 -0
- package/dist/core/EntityEditView.d.ts +25 -7
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/index.es.js +2344 -1305
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2344 -1306
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
- package/dist/routes/CustomCMSRoute.d.ts +4 -0
- package/dist/routes/FireCMSRoute.d.ts +1 -0
- package/dist/routes/HomePageRoute.d.ts +3 -0
- package/dist/types/collections.d.ts +18 -6
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +3 -7
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +1 -6
- package/dist/types/navigation.d.ts +28 -12
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/side_entity_controller.d.ts +1 -1
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/navigation_from_path.d.ts +5 -1
- package/dist/util/navigation_utils.d.ts +12 -1
- package/package.json +5 -5
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +1 -1
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +79 -38
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -5
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -2
- package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
- package/src/components/common/default_entity_actions.tsx +24 -10
- package/src/components/common/index.ts +1 -1
- package/src/components/common/{useDataSourceEntityCollectionTableController.tsx → useDataSourceTableController.tsx} +122 -13
- package/src/components/common/useDebouncedCallback.tsx +20 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +2 -3
- package/src/core/DefaultAppBar.tsx +28 -4
- package/src/core/EntityEditView.tsx +457 -296
- package/src/core/EntitySidePanel.tsx +72 -6
- package/src/core/FireCMS.tsx +7 -4
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +15 -28
- package/src/core/SideDialogs.tsx +10 -5
- package/src/core/index.tsx +4 -2
- package/src/hooks/data/save.ts +1 -1
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +5 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBuildNavigationController.tsx +59 -21
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildSideDialogsController.tsx +4 -2
- package/src/internal/useBuildSideEntityController.tsx +157 -77
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +230 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +19 -6
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +3 -7
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +1 -7
- package/src/types/navigation.ts +35 -16
- package/src/types/plugins.tsx +1 -0
- package/src/types/side_entity_controller.tsx +1 -1
- package/src/util/entity_cache.ts +196 -0
- package/src/util/navigation_from_path.ts +10 -2
- package/src/util/navigation_utils.ts +53 -1
- package/src/util/parent_references_from_path.ts +3 -3
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import React, { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
CMSAnalyticsEvent,
|
|
4
4
|
Entity,
|
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
EntityCustomView,
|
|
8
8
|
EntityStatus,
|
|
9
9
|
EntityValues,
|
|
10
|
+
FireCMSContext,
|
|
10
11
|
FireCMSPlugin,
|
|
11
12
|
FormContext,
|
|
12
13
|
PluginFormActionProps,
|
|
13
14
|
PropertyFieldBindingProps,
|
|
14
15
|
ResolvedEntityCollection,
|
|
16
|
+
SideEntityController,
|
|
15
17
|
User
|
|
16
18
|
} from "../types";
|
|
17
19
|
import equal from "react-fast-compare"
|
|
@@ -23,7 +25,7 @@ import {
|
|
|
23
25
|
EntityCollectionView,
|
|
24
26
|
EntityView,
|
|
25
27
|
ErrorBoundary,
|
|
26
|
-
getFormFieldKeys
|
|
28
|
+
getFormFieldKeys
|
|
27
29
|
} from "../components";
|
|
28
30
|
import {
|
|
29
31
|
canCreateEntity,
|
|
@@ -49,25 +51,29 @@ import {
|
|
|
49
51
|
useDataSource,
|
|
50
52
|
useEntityFetch,
|
|
51
53
|
useFireCMSContext,
|
|
54
|
+
useLargeLayout,
|
|
52
55
|
useSideEntityController,
|
|
53
56
|
useSnackbarController
|
|
54
57
|
} from "../hooks";
|
|
55
58
|
import {
|
|
56
59
|
Alert,
|
|
57
60
|
Button,
|
|
61
|
+
CheckIcon,
|
|
62
|
+
Chip,
|
|
58
63
|
CircularProgress,
|
|
59
|
-
CloseIcon,
|
|
60
64
|
cls,
|
|
61
65
|
defaultBorderMixin,
|
|
62
66
|
DialogActions,
|
|
67
|
+
EditIcon,
|
|
63
68
|
IconButton,
|
|
69
|
+
LoadingButton,
|
|
64
70
|
NotesIcon,
|
|
65
71
|
paperMixin,
|
|
66
72
|
Tab,
|
|
67
73
|
Tabs,
|
|
74
|
+
Tooltip,
|
|
68
75
|
Typography
|
|
69
76
|
} from "@firecms/ui";
|
|
70
|
-
import { useSideDialogContext } from "./index";
|
|
71
77
|
import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
|
|
72
78
|
import { useAnalyticsController } from "../hooks/useAnalyticsController";
|
|
73
79
|
import { CustomIdField } from "../form/components/CustomIdField";
|
|
@@ -75,19 +81,40 @@ import { CustomFieldValidator, getYupEntitySchema } from "../form/validation";
|
|
|
75
81
|
import { ErrorFocus } from "../form/components/ErrorFocus";
|
|
76
82
|
import { LabelWithIconAndTooltip, PropertyFieldBinding } from "../form";
|
|
77
83
|
import { ValidationError } from "yup";
|
|
84
|
+
import { getEntityFromCache, removeEntityFromCache, saveEntityToCache } from "../util/entity_cache";
|
|
78
85
|
|
|
79
86
|
const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
|
|
80
87
|
|
|
88
|
+
export type OnUpdateParams = {
|
|
89
|
+
entity: Entity<any>,
|
|
90
|
+
status: EntityStatus,
|
|
91
|
+
path: string
|
|
92
|
+
entityId?: string;
|
|
93
|
+
selectedTab?: string;
|
|
94
|
+
collection: EntityCollection<any>
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type OnTabChangeParams<M extends Record<string, any>> = {
|
|
98
|
+
path: string;
|
|
99
|
+
entityId?: string;
|
|
100
|
+
selectedTab?: string;
|
|
101
|
+
collection: EntityCollection<M>;
|
|
102
|
+
};
|
|
103
|
+
|
|
81
104
|
export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
82
105
|
path: string;
|
|
83
106
|
collection: EntityCollection<M>;
|
|
84
107
|
entityId?: string;
|
|
108
|
+
databaseId?: string;
|
|
85
109
|
copy?: boolean;
|
|
86
|
-
|
|
110
|
+
selectedTab?: string;
|
|
87
111
|
parentCollectionIds: string[];
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
onValuesModified?: (modified: boolean) => void;
|
|
113
|
+
onSaved?: (params: OnUpdateParams) => void;
|
|
90
114
|
onClose?: () => void;
|
|
115
|
+
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
116
|
+
layout?: "side_panel" | "full_screen";
|
|
117
|
+
barActions?: React.ReactNode;
|
|
91
118
|
}
|
|
92
119
|
|
|
93
120
|
/**
|
|
@@ -109,44 +136,57 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
|
|
|
109
136
|
path: props.path,
|
|
110
137
|
entityId: entityId,
|
|
111
138
|
collection: props.collection,
|
|
139
|
+
databaseId: props.databaseId,
|
|
112
140
|
useCache: false
|
|
113
141
|
});
|
|
114
142
|
|
|
115
|
-
|
|
143
|
+
const cachedValues = entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new");
|
|
144
|
+
|
|
145
|
+
if (dataLoading && !cachedValues) {
|
|
116
146
|
return <CircularProgressCenter/>
|
|
117
147
|
}
|
|
118
148
|
|
|
119
|
-
if (entityId && !entity) {
|
|
120
|
-
console.error(`Entity with id ${entityId} not found in collection ${props.
|
|
149
|
+
if (entityId && !entity && !cachedValues) {
|
|
150
|
+
console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
|
|
121
151
|
}
|
|
122
152
|
|
|
123
153
|
return <EntityEditViewInner<M> {...props}
|
|
124
154
|
entityId={entityId}
|
|
125
155
|
entity={entity}
|
|
126
|
-
|
|
156
|
+
cachedDirtyValues={cachedValues as Partial<M>}
|
|
157
|
+
dataLoading={dataLoading}
|
|
158
|
+
/>;
|
|
127
159
|
}
|
|
128
160
|
|
|
129
161
|
export function EntityEditViewInner<M extends Record<string, any>>({
|
|
130
162
|
path,
|
|
131
163
|
entityId: entityIdProp,
|
|
132
|
-
|
|
164
|
+
selectedTab: selectedTabProp,
|
|
133
165
|
copy,
|
|
134
166
|
collection,
|
|
135
167
|
parentCollectionIds,
|
|
136
|
-
|
|
137
|
-
|
|
168
|
+
onValuesModified,
|
|
169
|
+
onSaved,
|
|
138
170
|
onClose,
|
|
171
|
+
onTabChange,
|
|
139
172
|
entity,
|
|
140
|
-
|
|
173
|
+
cachedDirtyValues,
|
|
174
|
+
dataLoading,
|
|
175
|
+
layout = "side_panel",
|
|
176
|
+
barActions
|
|
141
177
|
}: EntityEditViewProps<M> & {
|
|
142
178
|
entity?: Entity<M>,
|
|
143
|
-
|
|
179
|
+
cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
|
|
180
|
+
dataLoading: boolean,
|
|
144
181
|
}) {
|
|
145
182
|
|
|
183
|
+
const largeLayout = useLargeLayout();
|
|
146
184
|
if (collection.customId && collection.formAutoSave) {
|
|
147
185
|
console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
|
|
148
186
|
}
|
|
149
187
|
|
|
188
|
+
const actionsAtTheBottom = !largeLayout || layout === "side_panel";
|
|
189
|
+
|
|
150
190
|
const [saving, setSaving] = useState(false);
|
|
151
191
|
/**
|
|
152
192
|
* These are the values that are being saved. They are debounced.
|
|
@@ -164,15 +204,10 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
164
204
|
});
|
|
165
205
|
}, false, 2000);
|
|
166
206
|
|
|
167
|
-
// const largeLayout = useLargeLayout();
|
|
168
|
-
// const largeLayoutTabSelected = useRef(!largeLayout);
|
|
169
|
-
// const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
|
|
170
|
-
|
|
171
207
|
const inputCollection = collection;
|
|
172
208
|
|
|
173
209
|
const authController = useAuthController();
|
|
174
210
|
const dataSource = useDataSource(collection);
|
|
175
|
-
const sideDialogContext = useSideDialogContext();
|
|
176
211
|
const sideEntityController = useSideEntityController();
|
|
177
212
|
const snackbarController = useSnackbarController();
|
|
178
213
|
const customizationController = useCustomizationController();
|
|
@@ -209,30 +244,26 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
209
244
|
|
|
210
245
|
const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
|
|
211
246
|
|
|
212
|
-
// const doOnValuesChanges = (values?: EntityValues<M>) => {
|
|
213
|
-
// const initialValues = formex.initialValues;
|
|
214
|
-
// setInternalValues(values);
|
|
215
|
-
// if (onValuesChanged)
|
|
216
|
-
// onValuesChanged(values);
|
|
217
|
-
// if (autoSave && values && !equal(values, initialValues)) {
|
|
218
|
-
// save(values);
|
|
219
|
-
// }
|
|
220
|
-
// };
|
|
221
|
-
|
|
222
247
|
const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
|
|
223
248
|
const [savingError, setSavingError] = React.useState<Error | undefined>();
|
|
224
249
|
|
|
225
250
|
const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
|
|
226
251
|
|
|
227
|
-
const defaultSelectedView =
|
|
252
|
+
const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
|
|
228
253
|
collection ? collection.defaultSelectedView : undefined,
|
|
229
254
|
{
|
|
230
255
|
status,
|
|
231
256
|
entityId
|
|
232
257
|
}
|
|
233
|
-
);
|
|
258
|
+
), []);
|
|
259
|
+
|
|
260
|
+
const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
234
261
|
|
|
235
|
-
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if ((selectedTabProp ?? MAIN_TAB_VALUE) !== selectedTab) {
|
|
264
|
+
setSelectedTab(selectedTabProp ?? MAIN_TAB_VALUE);
|
|
265
|
+
}
|
|
266
|
+
}, [selectedTabProp]);
|
|
236
267
|
|
|
237
268
|
const mainViewVisible = selectedTab === MAIN_TAB_VALUE;
|
|
238
269
|
|
|
@@ -247,7 +278,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
247
278
|
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
248
279
|
const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
|
|
249
280
|
|
|
250
|
-
const baseDataSourceValuesRef = useRef<Partial<EntityValues<M
|
|
281
|
+
const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>> | null>(cachedDirtyValues ?? getDataSourceEntityValues(initialResolvedCollection, status, usedEntity));
|
|
251
282
|
|
|
252
283
|
useEffect(() => {
|
|
253
284
|
if (entity)
|
|
@@ -282,8 +313,20 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
282
313
|
console.error(e);
|
|
283
314
|
}, [snackbarController]);
|
|
284
315
|
|
|
316
|
+
function clearDirtyCache() {
|
|
317
|
+
if (status === "new" || status === "copy") {
|
|
318
|
+
removeEntityFromCache(path + "#new");
|
|
319
|
+
} else {
|
|
320
|
+
removeEntityFromCache(path + "/" + entityId);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
285
324
|
const onSaveSuccess = (updatedEntity: Entity<M>, closeAfterSave: boolean) => {
|
|
286
325
|
|
|
326
|
+
clearDirtyCache();
|
|
327
|
+
|
|
328
|
+
onValuesModified?.(false);
|
|
329
|
+
|
|
287
330
|
setSaving(false);
|
|
288
331
|
if (!autoSave)
|
|
289
332
|
snackbarController.open({
|
|
@@ -293,26 +336,23 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
293
336
|
|
|
294
337
|
setUsedEntity(updatedEntity);
|
|
295
338
|
setStatus("existing");
|
|
339
|
+
setEntityId(updatedEntity.id);
|
|
296
340
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (closeAfterSave) {
|
|
303
|
-
sideDialogContext.setBlocked(false);
|
|
304
|
-
sideDialogContext.close(true);
|
|
305
|
-
onClose?.();
|
|
306
|
-
} else if (status !== "existing") {
|
|
307
|
-
sideEntityController.replace({
|
|
341
|
+
if (onSaved) {
|
|
342
|
+
onSaved({
|
|
343
|
+
entity: updatedEntity,
|
|
344
|
+
status,
|
|
308
345
|
path,
|
|
309
346
|
entityId: updatedEntity.id,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
collection,
|
|
347
|
+
selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
|
|
348
|
+
collection
|
|
313
349
|
});
|
|
314
350
|
}
|
|
315
351
|
|
|
352
|
+
if (closeAfterSave) {
|
|
353
|
+
onClose?.();
|
|
354
|
+
}
|
|
355
|
+
|
|
316
356
|
};
|
|
317
357
|
|
|
318
358
|
const onSaveFailure = useCallback((e: Error) => {
|
|
@@ -425,6 +465,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
425
465
|
|
|
426
466
|
const formex: FormexController<M> = useCreateFormex<M>({
|
|
427
467
|
initialValues: baseDataSourceValuesRef.current as M,
|
|
468
|
+
initialDirty: Boolean(cachedDirtyValues),
|
|
428
469
|
onSubmit,
|
|
429
470
|
validation: (values) => {
|
|
430
471
|
return validationSchema?.validate(values, { abortEarly: false })
|
|
@@ -499,8 +540,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
499
540
|
(customView, colIndex) => {
|
|
500
541
|
if (!customView)
|
|
501
542
|
return null;
|
|
502
|
-
if (selectedTab !== customView.key)
|
|
503
|
-
return null;
|
|
504
543
|
const Builder = customView.Builder;
|
|
505
544
|
if (!Builder) {
|
|
506
545
|
console.error("customView.Builder is not defined");
|
|
@@ -508,7 +547,10 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
508
547
|
}
|
|
509
548
|
return <div
|
|
510
549
|
className={cls(defaultBorderMixin,
|
|
511
|
-
"relative flex-grow w-full h-full overflow-auto
|
|
550
|
+
"relative flex-grow w-full h-full overflow-auto",
|
|
551
|
+
{
|
|
552
|
+
"hidden": selectedTab !== customView.key
|
|
553
|
+
})}
|
|
512
554
|
key={`custom_view_${customView.key}`}
|
|
513
555
|
role="tabpanel">
|
|
514
556
|
<ErrorBoundary>
|
|
@@ -526,21 +568,20 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
526
568
|
const globalLoading = (dataLoading && !usedEntity) ||
|
|
527
569
|
((!usedEntity || readOnly === undefined) && (status === "existing" || status === "copy"));
|
|
528
570
|
|
|
529
|
-
const loading = globalLoading || saving;
|
|
530
|
-
|
|
531
571
|
const subCollectionsViews = subcollections && subcollections.map(
|
|
532
572
|
(subcollection, colIndex) => {
|
|
533
573
|
const subcollectionId = subcollection.id ?? subcollection.path;
|
|
534
574
|
const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
535
|
-
if (selectedTab !== subcollectionId)
|
|
536
|
-
return null;
|
|
537
575
|
return (
|
|
538
576
|
<div
|
|
539
|
-
className={"relative flex-grow h-full overflow-auto w-full"
|
|
577
|
+
className={cls("relative flex-grow h-full overflow-auto w-full",
|
|
578
|
+
{
|
|
579
|
+
"hidden": selectedTab !== subcollectionId
|
|
580
|
+
})}
|
|
540
581
|
key={`subcol_${subcollectionId}`}
|
|
541
582
|
role="tabpanel">
|
|
542
583
|
|
|
543
|
-
{
|
|
584
|
+
{globalLoading || saving && <CircularProgressCenter/>}
|
|
544
585
|
|
|
545
586
|
{!globalLoading &&
|
|
546
587
|
(usedEntity && fullPath
|
|
@@ -548,7 +589,9 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
548
589
|
fullPath={fullPath}
|
|
549
590
|
parentCollectionIds={[...parentCollectionIds, collection.id]}
|
|
550
591
|
isSubCollection={true}
|
|
551
|
-
{
|
|
592
|
+
updateUrl={false}
|
|
593
|
+
{...subcollection}
|
|
594
|
+
openEntityMode={layout}/>
|
|
552
595
|
: <div
|
|
553
596
|
className="flex items-center justify-center w-full h-full p-3">
|
|
554
597
|
<Typography variant={"label"}>
|
|
@@ -564,18 +607,17 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
564
607
|
).filter(Boolean);
|
|
565
608
|
|
|
566
609
|
const onDiscard = useCallback(() => {
|
|
567
|
-
|
|
610
|
+
onValuesModified?.(false);
|
|
568
611
|
}, []);
|
|
569
612
|
|
|
570
613
|
const onSideTabClick = (value: string) => {
|
|
571
614
|
setSelectedTab(value);
|
|
572
615
|
if (status === "existing") {
|
|
573
|
-
|
|
616
|
+
onTabChange?.({
|
|
574
617
|
path,
|
|
575
618
|
entityId,
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
collection,
|
|
619
|
+
selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
|
|
620
|
+
collection
|
|
579
621
|
});
|
|
580
622
|
}
|
|
581
623
|
};
|
|
@@ -596,30 +638,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
596
638
|
: undefined);
|
|
597
639
|
}, []);
|
|
598
640
|
|
|
599
|
-
// useEffect(() => {
|
|
600
|
-
// baseDataSourceValuesRef.current = getDataSourceEntityValues(initialResolvedCollection, status, entity);
|
|
601
|
-
// const initialValues = formex.initialValues;
|
|
602
|
-
// if (!formex.isSubmitting && initialValues && status === "existing") {
|
|
603
|
-
// setUnderlyingChanges(
|
|
604
|
-
// Object.entries(resolvedCollection.properties)
|
|
605
|
-
// .map(([key, property]) => {
|
|
606
|
-
// if (isHidden(property)) {
|
|
607
|
-
// return {};
|
|
608
|
-
// }
|
|
609
|
-
// const initialValue = initialValues[key];
|
|
610
|
-
// const latestValue = baseDataSourceValuesRef.current[key];
|
|
611
|
-
// if (!equal(initialValue, latestValue)) {
|
|
612
|
-
// return { [key]: latestValue };
|
|
613
|
-
// }
|
|
614
|
-
// return {};
|
|
615
|
-
// })
|
|
616
|
-
// .reduce((a, b) => ({ ...a, ...b }), {}) as Partial<EntityValues<M>>
|
|
617
|
-
// );
|
|
618
|
-
// } else {
|
|
619
|
-
// setUnderlyingChanges({});
|
|
620
|
-
// }
|
|
621
|
-
// }, [entity, initialResolvedCollection, status]);
|
|
622
|
-
|
|
623
641
|
const pluginActions: React.ReactNode[] = [];
|
|
624
642
|
|
|
625
643
|
const plugins = customizationController.plugins;
|
|
@@ -632,7 +650,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
632
650
|
collection: inputCollection,
|
|
633
651
|
context,
|
|
634
652
|
currentEntityId: entityId,
|
|
635
|
-
formContext
|
|
653
|
+
formContext,
|
|
654
|
+
layout
|
|
636
655
|
};
|
|
637
656
|
pluginActions.push(...plugins.map((plugin, i) => (
|
|
638
657
|
plugin.form?.Actions
|
|
@@ -707,16 +726,23 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
707
726
|
return actions;
|
|
708
727
|
}, [authController, inputCollection, path]);
|
|
709
728
|
|
|
729
|
+
const deferredValues = useDeferredValue(formex.values);
|
|
710
730
|
const modified = formex.dirty;
|
|
731
|
+
|
|
732
|
+
useEffect(() => {
|
|
733
|
+
const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
|
|
734
|
+
if (modified) {
|
|
735
|
+
saveEntityToCache(key, deferredValues);
|
|
736
|
+
}
|
|
737
|
+
}, [deferredValues, modified]);
|
|
738
|
+
|
|
711
739
|
useEffect(() => {
|
|
712
740
|
if (!autoSave) {
|
|
713
|
-
|
|
714
|
-
} else {
|
|
715
|
-
if (formex.values && !equal(formex.values, lastSavedValues.current)) {
|
|
716
|
-
save(formex.values);
|
|
717
|
-
}
|
|
741
|
+
onValuesModified?.(modified);
|
|
718
742
|
}
|
|
719
|
-
}, [modified
|
|
743
|
+
}, [modified]);
|
|
744
|
+
|
|
745
|
+
useOnAutoSave(autoSave, formex, lastSavedValues, save);
|
|
720
746
|
|
|
721
747
|
useEffect(() => {
|
|
722
748
|
if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
|
|
@@ -820,148 +846,97 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
820
846
|
});
|
|
821
847
|
const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
|
|
822
848
|
|
|
823
|
-
const dialogActions =
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
{formex.isSubmitting && <CircularProgress size={"small"}/>}
|
|
855
|
-
|
|
856
|
-
<Button
|
|
857
|
-
variant="text"
|
|
858
|
-
disabled={disabled || formex.isSubmitting}
|
|
859
|
-
type="reset">
|
|
860
|
-
{status === "existing" ? "Discard" : "Clear"}
|
|
861
|
-
</Button>
|
|
862
|
-
|
|
863
|
-
<Button
|
|
864
|
-
variant="text"
|
|
865
|
-
color="primary"
|
|
866
|
-
type="submit"
|
|
867
|
-
disabled={disabled || formex.isSubmitting}
|
|
868
|
-
onClick={() => {
|
|
869
|
-
closeAfterSaveRef.current = false;
|
|
870
|
-
}}>
|
|
871
|
-
{status === "existing" && "Save"}
|
|
872
|
-
{status === "copy" && "Create copy"}
|
|
873
|
-
{status === "new" && "Create"}
|
|
874
|
-
</Button>
|
|
875
|
-
|
|
876
|
-
<Button
|
|
877
|
-
variant="filled"
|
|
878
|
-
color="primary"
|
|
879
|
-
type="submit"
|
|
880
|
-
disabled={disabled || formex.isSubmitting}
|
|
881
|
-
onClick={() => {
|
|
882
|
-
closeAfterSaveRef.current = true;
|
|
883
|
-
}}>
|
|
884
|
-
{status === "existing" && "Save and close"}
|
|
885
|
-
{status === "copy" && "Create copy and close"}
|
|
886
|
-
{status === "new" && "Create and close"}
|
|
887
|
-
</Button>
|
|
888
|
-
|
|
889
|
-
</DialogActions>;
|
|
890
|
-
|
|
891
|
-
function buildForm() {
|
|
892
|
-
|
|
893
|
-
let form = <>
|
|
894
|
-
|
|
895
|
-
{pluginActions.length > 0 && <div
|
|
896
|
-
className={cls("w-full flex justify-end items-center sticky top-0 right-0 left-0 z-10 bg-opacity-60 bg-surface-accent-200 dark:bg-opacity-60 dark:bg-surface-accent-800 backdrop-blur-md")}>
|
|
897
|
-
{pluginActions}
|
|
898
|
-
</div>}
|
|
899
|
-
|
|
900
|
-
<div className="pt-12 pb-16 pl-4 sm:px-8 md:px-10">
|
|
901
|
-
<div
|
|
902
|
-
className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
|
|
903
|
-
|
|
904
|
-
<Typography
|
|
905
|
-
className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
|
|
906
|
-
variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
|
|
907
|
-
</Typography>
|
|
908
|
-
<Alert color={"base"} className={"w-full"} size={"small"}>
|
|
909
|
-
<code className={"text-xs select-all"}>{path}/{entityId}</code>
|
|
910
|
-
</Alert>
|
|
911
|
-
</div>
|
|
849
|
+
const dialogActions = actionsAtTheBottom
|
|
850
|
+
? buildBottomActions({
|
|
851
|
+
savingError: savingError,
|
|
852
|
+
entity: entity,
|
|
853
|
+
formActions: formActions,
|
|
854
|
+
resolvedCollection: resolvedCollection,
|
|
855
|
+
context: context,
|
|
856
|
+
sideEntityController: sideEntityController,
|
|
857
|
+
isSubmitting: formex.isSubmitting,
|
|
858
|
+
disabled: disabled,
|
|
859
|
+
status: status,
|
|
860
|
+
setPendingClose: (value: boolean) => {
|
|
861
|
+
closeAfterSaveRef.current = value;
|
|
862
|
+
},
|
|
863
|
+
pluginActions,
|
|
864
|
+
layout
|
|
865
|
+
})
|
|
866
|
+
: buildSideActions({
|
|
867
|
+
savingError: savingError,
|
|
868
|
+
entity: entity,
|
|
869
|
+
formActions: formActions,
|
|
870
|
+
resolvedCollection: resolvedCollection,
|
|
871
|
+
context: context,
|
|
872
|
+
sideEntityController: sideEntityController,
|
|
873
|
+
isSubmitting: formex.isSubmitting,
|
|
874
|
+
disabled: disabled,
|
|
875
|
+
status: status,
|
|
876
|
+
pluginActions,
|
|
877
|
+
layout
|
|
878
|
+
});
|
|
912
879
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
880
|
+
const entityView = (readOnly === undefined)
|
|
881
|
+
? <></>
|
|
882
|
+
: (!readOnly
|
|
883
|
+
? (
|
|
884
|
+
<ErrorBoundary>
|
|
885
|
+
<div className="flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
|
|
886
|
+
|
|
887
|
+
{formex.dirty
|
|
888
|
+
? <Tooltip title={"Unsaved changes"}
|
|
889
|
+
className={"self-end sticky top-4 z-10"}>
|
|
890
|
+
<Chip size={"small"} colorScheme={"orangeDarker"}>
|
|
891
|
+
<EditIcon size={"smallest"}/>
|
|
892
|
+
</Chip>
|
|
893
|
+
</Tooltip>
|
|
894
|
+
: <Tooltip title={"In sync with the database"}
|
|
895
|
+
className={"self-end sticky top-4 z-10"}>
|
|
896
|
+
<Chip size={"small"}>
|
|
897
|
+
<CheckIcon size={"smallest"}/>
|
|
898
|
+
</Chip>
|
|
899
|
+
</Tooltip>}
|
|
900
|
+
|
|
901
|
+
<div
|
|
902
|
+
className={"w-full py-2 flex flex-col items-start mt-4 lg:mt-8 mb-8"}>
|
|
903
|
+
|
|
904
|
+
<Typography
|
|
905
|
+
className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
|
|
906
|
+
variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
|
|
907
|
+
</Typography>
|
|
908
|
+
<Alert color={"base"} className={"w-full"} size={"small"}>
|
|
909
|
+
<code
|
|
910
|
+
className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>{path}/{entityId}</code>
|
|
911
|
+
</Alert>
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
{!collection.hideIdFromForm &&
|
|
915
|
+
<CustomIdField customId={inputCollection.customId}
|
|
916
|
+
entityId={entityId}
|
|
917
|
+
status={status}
|
|
918
|
+
onChange={setEntityId}
|
|
919
|
+
error={entityIdError}
|
|
920
|
+
loading={customIdLoading}
|
|
921
|
+
entity={entity}/>}
|
|
922
|
+
|
|
923
|
+
{entityId && formContext && <>
|
|
924
|
+
<div className="mt-12 flex flex-col gap-8"
|
|
925
|
+
ref={formRef}>
|
|
926
|
+
|
|
927
|
+
{formFields}
|
|
928
|
+
|
|
929
|
+
<ErrorFocus containerRef={formRef}/>
|
|
921
930
|
|
|
922
|
-
|
|
923
|
-
<div className="mt-12 flex flex-col gap-8"
|
|
924
|
-
ref={formRef}>
|
|
931
|
+
</div>
|
|
925
932
|
|
|
926
|
-
|
|
933
|
+
{actionsAtTheBottom && <div className="h-16"/>}
|
|
927
934
|
|
|
928
|
-
|
|
935
|
+
</>}
|
|
929
936
|
|
|
930
937
|
</div>
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
</>}
|
|
935
|
-
|
|
936
|
-
</div>
|
|
937
|
-
</>;
|
|
938
|
-
|
|
939
|
-
if (plugins) {
|
|
940
|
-
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
941
|
-
if (plugin.form?.provider) {
|
|
942
|
-
form = (
|
|
943
|
-
<plugin.form.provider.Component
|
|
944
|
-
status={status}
|
|
945
|
-
path={path}
|
|
946
|
-
collection={collection}
|
|
947
|
-
onDiscard={onDiscard}
|
|
948
|
-
entity={usedEntity}
|
|
949
|
-
context={context}
|
|
950
|
-
formContext={formContext}
|
|
951
|
-
{...plugin.form.provider.props}>
|
|
952
|
-
{form}
|
|
953
|
-
</plugin.form.provider.Component>
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
return <ErrorBoundary>{form}</ErrorBoundary>;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
const entityView = (readOnly === undefined)
|
|
962
|
-
? <></>
|
|
963
|
-
: (!readOnly
|
|
964
|
-
? buildForm()
|
|
938
|
+
</ErrorBoundary>
|
|
939
|
+
)
|
|
965
940
|
: (
|
|
966
941
|
<>
|
|
967
942
|
<Typography
|
|
@@ -980,7 +955,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
980
955
|
const subcollectionTabs = subcollections && subcollections.map(
|
|
981
956
|
(subcollection) =>
|
|
982
957
|
<Tab
|
|
983
|
-
className="text-sm min-w-[
|
|
958
|
+
className="text-sm min-w-[120px]"
|
|
984
959
|
value={subcollection.id}
|
|
985
960
|
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
986
961
|
{subcollection.name}
|
|
@@ -991,7 +966,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
991
966
|
(view) =>
|
|
992
967
|
|
|
993
968
|
<Tab
|
|
994
|
-
className="text-sm min-w-[
|
|
969
|
+
className="text-sm min-w-[120px]"
|
|
995
970
|
value={view.key}
|
|
996
971
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
997
972
|
{view.name}
|
|
@@ -1003,93 +978,115 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
1003
978
|
onIdChange(entityId);
|
|
1004
979
|
}, [entityId, onIdChange]);
|
|
1005
980
|
|
|
1006
|
-
|
|
1007
|
-
<Formex value={formex}>
|
|
981
|
+
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
1008
982
|
|
|
1009
|
-
|
|
983
|
+
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
|
|
1010
984
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
className="pb-1 self-center">
|
|
1016
|
-
<IconButton
|
|
1017
|
-
onClick={() => {
|
|
1018
|
-
onClose?.();
|
|
1019
|
-
return sideDialogContext.close(false);
|
|
1020
|
-
}}>
|
|
1021
|
-
<CloseIcon size={"small"}/>
|
|
1022
|
-
</IconButton>
|
|
1023
|
-
</div>
|
|
985
|
+
{shouldShowTopBar && <div
|
|
986
|
+
className={cls("h-14 flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex items-end bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
|
|
987
|
+
|
|
988
|
+
{barActions}
|
|
1024
989
|
|
|
1025
|
-
|
|
990
|
+
<div className={"flex-grow"}/>
|
|
1026
991
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
992
|
+
{globalLoading && <div
|
|
993
|
+
className="self-center">
|
|
994
|
+
<CircularProgress size={"small"}/>
|
|
995
|
+
</div>}
|
|
1031
996
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
innerClassName="pl-4 pr-4 pt-0">
|
|
997
|
+
{hasAdditionalViews && <Tabs
|
|
998
|
+
value={selectedTab}
|
|
999
|
+
onValueChange={(value) => {
|
|
1000
|
+
onSideTabClick(value);
|
|
1001
|
+
}}>
|
|
1038
1002
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
} text-sm min-w-[140px]`}
|
|
1045
|
-
>{collection.singularName ?? collection.name}</Tab>
|
|
1003
|
+
<Tab
|
|
1004
|
+
disabled={!hasAdditionalViews}
|
|
1005
|
+
value={MAIN_TAB_VALUE}
|
|
1006
|
+
className={"text-sm min-w-[120px]"}
|
|
1007
|
+
>{collection.singularName ?? collection.name}</Tab>
|
|
1046
1008
|
|
|
1047
|
-
|
|
1009
|
+
{customViewTabs}
|
|
1048
1010
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1011
|
+
{subcollectionTabs}
|
|
1012
|
+
</Tabs>}
|
|
1051
1013
|
|
|
1052
|
-
|
|
1014
|
+
</div>}
|
|
1053
1015
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
formex.resetForm();
|
|
1058
|
-
return onDiscard && onDiscard();
|
|
1059
|
-
}}
|
|
1060
|
-
noValidate
|
|
1061
|
-
className={"flex-grow h-full flex overflow-auto flex-col w-full"}>
|
|
1062
|
-
|
|
1063
|
-
<div
|
|
1064
|
-
role="tabpanel"
|
|
1065
|
-
hidden={!mainViewVisible}
|
|
1066
|
-
id={`form_${path}`}
|
|
1067
|
-
className={"relative w-full"}>
|
|
1068
|
-
|
|
1069
|
-
{globalLoading
|
|
1070
|
-
? <CircularProgressCenter/>
|
|
1071
|
-
: entityView}
|
|
1016
|
+
<form
|
|
1017
|
+
onSubmit={formex.handleSubmit}
|
|
1018
|
+
onReset={() => {
|
|
1072
1019
|
|
|
1073
|
-
|
|
1020
|
+
clearDirtyCache();
|
|
1074
1021
|
|
|
1075
|
-
|
|
1022
|
+
formex.resetForm({
|
|
1023
|
+
values: getDataSourceEntityValues(initialResolvedCollection, status, entity) as M,
|
|
1024
|
+
});
|
|
1076
1025
|
|
|
1077
|
-
|
|
1026
|
+
return onDiscard();
|
|
1027
|
+
}}
|
|
1028
|
+
noValidate
|
|
1029
|
+
className={"flex-1 flex flex-row w-full overflow-y-auto justify-center"}>
|
|
1078
1030
|
|
|
1079
|
-
|
|
1031
|
+
<div
|
|
1032
|
+
role="tabpanel"
|
|
1033
|
+
hidden={!mainViewVisible}
|
|
1034
|
+
id={`form_${path}`}
|
|
1035
|
+
className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-5xl w-full h-fit", {
|
|
1036
|
+
"hidden": !mainViewVisible
|
|
1037
|
+
})}>
|
|
1080
1038
|
|
|
1081
|
-
|
|
1039
|
+
{globalLoading
|
|
1040
|
+
? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10"><CircularProgressCenter/></div>
|
|
1041
|
+
: entityView}
|
|
1082
1042
|
|
|
1083
1043
|
</div>
|
|
1044
|
+
|
|
1045
|
+
{mainViewVisible && shouldShowEntityActions && !actionsAtTheBottom && dialogActions}
|
|
1046
|
+
|
|
1047
|
+
{customViewsView}
|
|
1048
|
+
|
|
1049
|
+
{subCollectionsViews}
|
|
1050
|
+
|
|
1051
|
+
{shouldShowEntityActions && actionsAtTheBottom && dialogActions}
|
|
1052
|
+
|
|
1053
|
+
</form>
|
|
1054
|
+
|
|
1055
|
+
</div>;
|
|
1056
|
+
|
|
1057
|
+
if (plugins) {
|
|
1058
|
+
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
1059
|
+
if (plugin.form?.provider) {
|
|
1060
|
+
result = (
|
|
1061
|
+
<plugin.form.provider.Component
|
|
1062
|
+
status={status}
|
|
1063
|
+
path={path}
|
|
1064
|
+
collection={collection}
|
|
1065
|
+
onDiscard={onDiscard}
|
|
1066
|
+
entity={usedEntity}
|
|
1067
|
+
context={context}
|
|
1068
|
+
formContext={formContext}
|
|
1069
|
+
{...plugin.form.provider.props}>
|
|
1070
|
+
{result}
|
|
1071
|
+
</plugin.form.provider.Component>
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return (
|
|
1078
|
+
<Formex value={formex}>
|
|
1079
|
+
|
|
1080
|
+
{result}
|
|
1084
1081
|
</Formex>
|
|
1085
1082
|
);
|
|
1086
1083
|
}
|
|
1087
1084
|
|
|
1088
|
-
function getDataSourceEntityValues<M extends object>(
|
|
1085
|
+
function getDataSourceEntityValues<M extends object>(collection: ResolvedEntityCollection,
|
|
1089
1086
|
status: "new" | "existing" | "copy",
|
|
1090
1087
|
entity: Entity<M> | undefined): Partial<EntityValues<M>> {
|
|
1091
1088
|
|
|
1092
|
-
const properties =
|
|
1089
|
+
const properties = collection.properties;
|
|
1093
1090
|
if ((status === "existing" || status === "copy") && entity) {
|
|
1094
1091
|
return entity.values ?? getDefaultValuesFor(properties);
|
|
1095
1092
|
} else if (status === "new") {
|
|
@@ -1127,3 +1124,167 @@ export function yupToFormErrors(yupError: ValidationError): Record<string, any>
|
|
|
1127
1124
|
}
|
|
1128
1125
|
return errors;
|
|
1129
1126
|
}
|
|
1127
|
+
|
|
1128
|
+
type ActionsViewProps<M extends object> = {
|
|
1129
|
+
savingError: Error | undefined,
|
|
1130
|
+
entity: Entity<M> | undefined,
|
|
1131
|
+
formActions: EntityAction[],
|
|
1132
|
+
resolvedCollection: ResolvedEntityCollection,
|
|
1133
|
+
context: FireCMSContext,
|
|
1134
|
+
sideEntityController: SideEntityController,
|
|
1135
|
+
isSubmitting: boolean,
|
|
1136
|
+
disabled: boolean,
|
|
1137
|
+
status: "new" | "existing" | "copy",
|
|
1138
|
+
setPendingClose?: (value: boolean) => void,
|
|
1139
|
+
pluginActions?: React.ReactNode[],
|
|
1140
|
+
layout: "side_panel" | "full_screen";
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
function buildBottomActions<M extends object>({
|
|
1144
|
+
savingError,
|
|
1145
|
+
entity,
|
|
1146
|
+
formActions,
|
|
1147
|
+
resolvedCollection,
|
|
1148
|
+
context,
|
|
1149
|
+
sideEntityController,
|
|
1150
|
+
isSubmitting,
|
|
1151
|
+
disabled,
|
|
1152
|
+
status,
|
|
1153
|
+
setPendingClose,
|
|
1154
|
+
pluginActions,
|
|
1155
|
+
layout
|
|
1156
|
+
}: ActionsViewProps<M>) {
|
|
1157
|
+
|
|
1158
|
+
return <DialogActions position={"absolute"}>
|
|
1159
|
+
|
|
1160
|
+
{savingError &&
|
|
1161
|
+
<div className="text-right">
|
|
1162
|
+
<Typography color={"error"}>
|
|
1163
|
+
{savingError.message}
|
|
1164
|
+
</Typography>
|
|
1165
|
+
</div>}
|
|
1166
|
+
|
|
1167
|
+
{entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
|
|
1168
|
+
{formActions.map(action => (
|
|
1169
|
+
<IconButton
|
|
1170
|
+
key={action.name}
|
|
1171
|
+
color="primary"
|
|
1172
|
+
onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
1173
|
+
event.stopPropagation();
|
|
1174
|
+
if (entity)
|
|
1175
|
+
action.onClick({
|
|
1176
|
+
entity,
|
|
1177
|
+
fullPath: resolvedCollection.path,
|
|
1178
|
+
collection: resolvedCollection,
|
|
1179
|
+
context,
|
|
1180
|
+
sideEntityController,
|
|
1181
|
+
openEntityMode: layout
|
|
1182
|
+
});
|
|
1183
|
+
}}>
|
|
1184
|
+
{action.icon}
|
|
1185
|
+
</IconButton>
|
|
1186
|
+
))}
|
|
1187
|
+
</div>}
|
|
1188
|
+
|
|
1189
|
+
{pluginActions}
|
|
1190
|
+
|
|
1191
|
+
<Button
|
|
1192
|
+
variant="text"
|
|
1193
|
+
disabled={disabled || isSubmitting}
|
|
1194
|
+
type="reset">
|
|
1195
|
+
{status === "existing" ? "Discard" : "Clear"}
|
|
1196
|
+
</Button>
|
|
1197
|
+
|
|
1198
|
+
<Button
|
|
1199
|
+
variant="text"
|
|
1200
|
+
color="primary"
|
|
1201
|
+
type="submit"
|
|
1202
|
+
disabled={disabled || isSubmitting}
|
|
1203
|
+
onClick={() => {
|
|
1204
|
+
setPendingClose?.(false);
|
|
1205
|
+
}}>
|
|
1206
|
+
{status === "existing" && "Save"}
|
|
1207
|
+
{status === "copy" && "Create copy"}
|
|
1208
|
+
{status === "new" && "Create"}
|
|
1209
|
+
</Button>
|
|
1210
|
+
|
|
1211
|
+
<LoadingButton
|
|
1212
|
+
variant="filled"
|
|
1213
|
+
color="primary"
|
|
1214
|
+
type="submit"
|
|
1215
|
+
loading={isSubmitting}
|
|
1216
|
+
disabled={disabled}
|
|
1217
|
+
onClick={() => {
|
|
1218
|
+
setPendingClose?.(true);
|
|
1219
|
+
}}>
|
|
1220
|
+
{status === "existing" && "Save and close"}
|
|
1221
|
+
{status === "copy" && "Create copy and close"}
|
|
1222
|
+
{status === "new" && "Create and close"}
|
|
1223
|
+
</LoadingButton>
|
|
1224
|
+
|
|
1225
|
+
</DialogActions>;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function buildSideActions<M extends object>({
|
|
1229
|
+
savingError,
|
|
1230
|
+
entity,
|
|
1231
|
+
formActions,
|
|
1232
|
+
resolvedCollection,
|
|
1233
|
+
context,
|
|
1234
|
+
sideEntityController,
|
|
1235
|
+
isSubmitting,
|
|
1236
|
+
disabled,
|
|
1237
|
+
status,
|
|
1238
|
+
setPendingClose,
|
|
1239
|
+
pluginActions
|
|
1240
|
+
}: ActionsViewProps<M>) {
|
|
1241
|
+
|
|
1242
|
+
return <div
|
|
1243
|
+
className={cls("overflow-auto h-full flex flex-col gap-2 w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin)}>
|
|
1244
|
+
|
|
1245
|
+
<LoadingButton
|
|
1246
|
+
fullWidth={true}
|
|
1247
|
+
variant="filled"
|
|
1248
|
+
color="primary"
|
|
1249
|
+
type="submit"
|
|
1250
|
+
size={"large"}
|
|
1251
|
+
disabled={disabled || isSubmitting}
|
|
1252
|
+
onClick={() => {
|
|
1253
|
+
setPendingClose?.(false);
|
|
1254
|
+
}}>
|
|
1255
|
+
{status === "existing" && "Save"}
|
|
1256
|
+
{status === "copy" && "Create copy"}
|
|
1257
|
+
{status === "new" && "Create"}
|
|
1258
|
+
</LoadingButton>
|
|
1259
|
+
|
|
1260
|
+
<Button
|
|
1261
|
+
fullWidth={true}
|
|
1262
|
+
variant="text"
|
|
1263
|
+
disabled={disabled || isSubmitting}
|
|
1264
|
+
type="reset">
|
|
1265
|
+
{status === "existing" ? "Discard" : "Clear"}
|
|
1266
|
+
</Button>
|
|
1267
|
+
|
|
1268
|
+
{pluginActions}
|
|
1269
|
+
|
|
1270
|
+
{savingError &&
|
|
1271
|
+
<div className="text-right">
|
|
1272
|
+
<Typography color={"error"}>
|
|
1273
|
+
{savingError.message}
|
|
1274
|
+
</Typography>
|
|
1275
|
+
</div>}
|
|
1276
|
+
|
|
1277
|
+
</div>;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function useOnAutoSave(autoSave: undefined | boolean, formex: FormexController<any>, lastSavedValues: any, save: (values: EntityValues<any>) => Promise<void>) {
|
|
1281
|
+
if (!autoSave) return;
|
|
1282
|
+
useEffect(() => {
|
|
1283
|
+
if (autoSave) {
|
|
1284
|
+
if (formex.values && !equal(formex.values, lastSavedValues.current)) {
|
|
1285
|
+
save(formex.values);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}, [formex.values]);
|
|
1289
|
+
}
|
|
1290
|
+
|