@firecms/core 3.0.0-canary.216 → 3.0.0-canary.218
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/core/EntityEditView.d.ts +2 -1
- package/dist/index.es.js +141 -132
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +141 -132
- package/dist/index.umd.js.map +1 -1
- package/dist/types/fields.d.ts +3 -1
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/objects.d.ts +1 -1
- package/dist/util/resolutions.d.ts +4 -4
- package/package.json +5 -5
- package/src/components/EntityJsonPreview.tsx +3 -3
- package/src/core/EntityEditView.tsx +106 -60
- package/src/form/EntityForm.tsx +5 -3
- package/src/hooks/useBuildNavigationController.tsx +1 -1
- package/src/routes/FireCMSRoute.tsx +1 -1
- package/src/types/fields.tsx +3 -1
- package/src/util/createFormexStub.tsx +62 -0
- package/src/util/join_collections.ts +3 -1
- package/src/util/objects.ts +5 -1
- package/src/util/resolutions.ts +4 -2
package/dist/types/fields.d.ts
CHANGED
|
@@ -138,7 +138,9 @@ export interface FormContext<M extends Record<string, any> = any> {
|
|
|
138
138
|
savingError?: Error;
|
|
139
139
|
openEntityMode: "side_panel" | "full_screen";
|
|
140
140
|
/**
|
|
141
|
-
* This is the underlying formex controller that powers the form
|
|
141
|
+
* This is the underlying formex controller that powers the form.
|
|
142
|
+
* If you are in a red only mode, the formex controller is there, but you can't
|
|
143
|
+
* operate with it
|
|
142
144
|
*/
|
|
143
145
|
formex: FormexController<M>;
|
|
144
146
|
}
|
package/dist/util/objects.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const pick: <T>(obj: T, ...args: any[]) => T;
|
|
2
2
|
export declare function isObject(item: any): any;
|
|
3
|
-
export declare function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U): T & U;
|
|
3
|
+
export declare function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U, ignoreUndefined?: boolean): T & U;
|
|
4
4
|
export declare function getValueInPath(o: object | undefined, path: string): any;
|
|
5
5
|
export declare function removeInPath(o: object, path: string): object | undefined;
|
|
6
6
|
export declare function removeFunctions(o: object | undefined): any;
|
|
@@ -81,8 +81,8 @@ export declare function resolveProperties<M extends Record<string, any>>({ prope
|
|
|
81
81
|
export declare function resolvePropertyEnum(property: StringProperty | NumberProperty, fromBuilder?: boolean): ResolvedStringProperty | ResolvedNumberProperty;
|
|
82
82
|
export declare function resolveEnumValues(input: EnumValues): EnumValueConfig[] | undefined;
|
|
83
83
|
export declare function resolveEntityView(entityView: string | EntityCustomView<any>, contextEntityViews?: EntityCustomView<any>[]): EntityCustomView<any> | undefined;
|
|
84
|
-
export declare function resolvedSelectedEntityView<M extends Record<string, any>>(customViews: (string | EntityCustomView<M>)[] | undefined, customizationController: CustomizationController, selectedTab?: string): {
|
|
85
|
-
resolvedEntityViews: EntityCustomView[];
|
|
86
|
-
selectedEntityView: EntityCustomView | undefined;
|
|
87
|
-
selectedSecondaryForm: EntityCustomView | undefined;
|
|
84
|
+
export declare function resolvedSelectedEntityView<M extends Record<string, any>>(customViews: (string | EntityCustomView<M>)[] | undefined, customizationController: CustomizationController, selectedTab?: string, canEdit?: boolean): {
|
|
85
|
+
resolvedEntityViews: EntityCustomView<M>[];
|
|
86
|
+
selectedEntityView: EntityCustomView<M> | undefined;
|
|
87
|
+
selectedSecondaryForm: EntityCustomView<M> | undefined;
|
|
88
88
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.0-canary.
|
|
4
|
+
"version": "3.0.0-canary.218",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"./package.json": "./package.json"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@firecms/editor": "^3.0.0-canary.
|
|
54
|
-
"@firecms/formex": "^3.0.0-canary.
|
|
55
|
-
"@firecms/ui": "^3.0.0-canary.
|
|
53
|
+
"@firecms/editor": "^3.0.0-canary.218",
|
|
54
|
+
"@firecms/formex": "^3.0.0-canary.218",
|
|
55
|
+
"@firecms/ui": "^3.0.0-canary.218",
|
|
56
56
|
"@hello-pangea/dnd": "^17.0.0",
|
|
57
57
|
"@radix-ui/react-portal": "^1.1.3",
|
|
58
58
|
"clsx": "^2.1.1",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"dist",
|
|
106
106
|
"src"
|
|
107
107
|
],
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "c0efa30360fec2f9be27b9d47bc6a800b17d34f2",
|
|
109
109
|
"publishConfig": {
|
|
110
110
|
"access": "public"
|
|
111
111
|
},
|
|
@@ -38,7 +38,7 @@ export function EntityJsonPreview({ values }: { values: object }) {
|
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<Highlight
|
|
41
|
-
theme={mode === "dark" ? themes.vsDark : themes.
|
|
41
|
+
theme={mode === "dark" ? themes.vsDark : themes.github}
|
|
42
42
|
code={code}
|
|
43
43
|
language="json"
|
|
44
44
|
>
|
|
@@ -48,9 +48,9 @@ export function EntityJsonPreview({ values }: { values: object }) {
|
|
|
48
48
|
ref={preRef}
|
|
49
49
|
style={{
|
|
50
50
|
...style,
|
|
51
|
-
|
|
51
|
+
backgroundColor: "inherit"
|
|
52
52
|
}}
|
|
53
|
-
className="
|
|
53
|
+
className="max-w-6xl mx-auto p-8 rounded text-sm"
|
|
54
54
|
>
|
|
55
55
|
{tokens.map((line, i) => (
|
|
56
56
|
<div key={i} {...getLineProps({ line })} className="text-wrap">
|
|
@@ -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";
|
|
@@ -21,6 +22,7 @@ import { getEntityFromCache } from "../util/entity_cache";
|
|
|
21
22
|
import { EntityForm, EntityFormProps } from "../form";
|
|
22
23
|
import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
|
|
23
24
|
import { EntityJsonPreview } from "../components/EntityJsonPreview";
|
|
25
|
+
import { createFormexStub } from "../util/createFormexStub";
|
|
24
26
|
|
|
25
27
|
export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
|
|
26
28
|
export const JSON_TAB_VALUE = "__json";
|
|
@@ -54,7 +56,7 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
|
54
56
|
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
55
57
|
layout?: "side_panel" | "full_screen";
|
|
56
58
|
barActions?: React.ReactNode;
|
|
57
|
-
formProps?: Partial<EntityFormProps<M
|
|
59
|
+
formProps?: Partial<EntityFormProps<M>>,
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
|
@@ -104,19 +106,6 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
|
|
|
104
106
|
console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
if (!canEdit) {
|
|
108
|
-
return <div className={"flex flex-col"}>
|
|
109
|
-
<Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
|
|
110
|
-
{props.collection.singularName ?? props.collection.name}
|
|
111
|
-
</Typography>
|
|
112
|
-
<EntityView
|
|
113
|
-
className={"px-8"}
|
|
114
|
-
entity={entity as Entity<M>}
|
|
115
|
-
path={props.path}
|
|
116
|
-
collection={props.collection}/>
|
|
117
|
-
</div>
|
|
118
|
-
}
|
|
119
|
-
|
|
120
109
|
return <EntityEditViewInner<M> {...props}
|
|
121
110
|
entityId={entityId}
|
|
122
111
|
entity={entity}
|
|
@@ -124,9 +113,11 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
|
|
|
124
113
|
dataLoading={dataLoading}
|
|
125
114
|
status={status}
|
|
126
115
|
setStatus={setStatus}
|
|
116
|
+
canEdit={canEdit}
|
|
127
117
|
/>;
|
|
128
118
|
}
|
|
129
119
|
|
|
120
|
+
|
|
130
121
|
export function EntityEditViewInner<M extends Record<string, any>>({
|
|
131
122
|
path,
|
|
132
123
|
entityId,
|
|
@@ -143,13 +134,15 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
143
134
|
barActions,
|
|
144
135
|
status,
|
|
145
136
|
setStatus,
|
|
146
|
-
formProps
|
|
137
|
+
formProps,
|
|
138
|
+
canEdit
|
|
147
139
|
}: EntityEditViewProps<M> & {
|
|
148
140
|
entity?: Entity<M>,
|
|
149
141
|
cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
|
|
150
142
|
dataLoading: boolean,
|
|
151
143
|
status: EntityStatus,
|
|
152
144
|
setStatus: (status: EntityStatus) => void,
|
|
145
|
+
canEdit?: boolean,
|
|
153
146
|
}) {
|
|
154
147
|
|
|
155
148
|
const context = useFireCMSContext();
|
|
@@ -177,10 +170,9 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
177
170
|
), []);
|
|
178
171
|
|
|
179
172
|
const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
180
|
-
|
|
181
173
|
useEffect(() => {
|
|
182
|
-
if ((selectedTabProp ?? MAIN_TAB_VALUE) !== selectedTab) {
|
|
183
|
-
setSelectedTab(selectedTabProp ?? MAIN_TAB_VALUE);
|
|
174
|
+
if ((selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE) !== selectedTab) {
|
|
175
|
+
setSelectedTab(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
184
176
|
}
|
|
185
177
|
}, [selectedTabProp]);
|
|
186
178
|
|
|
@@ -195,23 +187,58 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
195
187
|
resolvedEntityViews,
|
|
196
188
|
selectedEntityView,
|
|
197
189
|
selectedSecondaryForm
|
|
198
|
-
} = resolvedSelectedEntityView(customViews, customizationController, selectedTab);
|
|
190
|
+
} = resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit);
|
|
199
191
|
|
|
200
192
|
const actionsAtTheBottom = !largeLayout || layout === "side_panel" || selectedEntityView?.includeActions === "bottom";
|
|
201
193
|
|
|
202
194
|
const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
|
|
203
195
|
|
|
196
|
+
const authController = useAuthController();
|
|
197
|
+
|
|
204
198
|
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
205
199
|
.filter(e => !e.includeActions)
|
|
206
200
|
.map((customView) => {
|
|
201
|
+
|
|
207
202
|
if (!customView)
|
|
208
203
|
return null;
|
|
209
204
|
const Builder = customView.Builder;
|
|
210
205
|
if (!Builder) {
|
|
211
|
-
console.error("customView.Builder is not defined");
|
|
206
|
+
console.error("INTERNAL: customView.Builder is not defined");
|
|
212
207
|
return null;
|
|
213
208
|
}
|
|
214
209
|
|
|
210
|
+
if (!entityId) {
|
|
211
|
+
console.error("INTERNAL: entityId is not defined");
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
|
|
216
|
+
const usedFormContext: FormContext = formContext ?? {
|
|
217
|
+
entityId,
|
|
218
|
+
openEntityMode: layout,
|
|
219
|
+
status: status,
|
|
220
|
+
values: usedEntity?.values ?? {},
|
|
221
|
+
setFieldValue: (key: string, value: any) => {
|
|
222
|
+
throw new Error("You can't update values in read only mode");
|
|
223
|
+
},
|
|
224
|
+
save: () => {
|
|
225
|
+
throw new Error("You can't save in read only mode");
|
|
226
|
+
},
|
|
227
|
+
collection: resolveCollection<M>({
|
|
228
|
+
collection,
|
|
229
|
+
path,
|
|
230
|
+
entityId,
|
|
231
|
+
values: usedEntity?.values ?? {},
|
|
232
|
+
previousValues: usedEntity?.values ?? {},
|
|
233
|
+
propertyConfigs: customizationController.propertyConfigs,
|
|
234
|
+
authController
|
|
235
|
+
}),
|
|
236
|
+
path,
|
|
237
|
+
entity: usedEntity,
|
|
238
|
+
savingError: undefined,
|
|
239
|
+
formex: formexStub
|
|
240
|
+
};
|
|
241
|
+
|
|
215
242
|
return <div
|
|
216
243
|
className={cls(defaultBorderMixin,
|
|
217
244
|
"relative flex-1 w-full h-full overflow-auto",
|
|
@@ -220,11 +247,11 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
220
247
|
key={`custom_view_${customView.key}`}
|
|
221
248
|
role="tabpanel">
|
|
222
249
|
<ErrorBoundary>
|
|
223
|
-
{
|
|
250
|
+
{usedFormContext && <Builder
|
|
224
251
|
collection={collection}
|
|
225
252
|
entity={usedEntity}
|
|
226
|
-
modifiedValues={
|
|
227
|
-
formContext={
|
|
253
|
+
modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
|
|
254
|
+
formContext={usedFormContext}
|
|
228
255
|
/>}
|
|
229
256
|
</ErrorBoundary>
|
|
230
257
|
</div>;
|
|
@@ -239,7 +266,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
239
266
|
role="tabpanel">
|
|
240
267
|
<ErrorBoundary>
|
|
241
268
|
<EntityJsonPreview
|
|
242
|
-
values={formContext?.values ?? {}}/>
|
|
269
|
+
values={formContext?.values ?? entity?.values ?? {}}/>
|
|
243
270
|
</ErrorBoundary>
|
|
244
271
|
</div>;
|
|
245
272
|
|
|
@@ -288,42 +315,61 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
288
315
|
}
|
|
289
316
|
};
|
|
290
317
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
318
|
+
const entityView = !canEdit ?
|
|
319
|
+
(
|
|
320
|
+
<div
|
|
321
|
+
className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", !mainViewVisible ? "hidden" : "")}>
|
|
322
|
+
<div
|
|
323
|
+
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")}>
|
|
324
|
+
<Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
|
|
325
|
+
{collection.singularName ?? collection.name}
|
|
326
|
+
</Typography>
|
|
327
|
+
<EntityView
|
|
328
|
+
className={"px-8 h-full overflow-auto"}
|
|
329
|
+
entity={entity as Entity<M>}
|
|
330
|
+
path={path}
|
|
331
|
+
collection={collection}/>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
)
|
|
335
|
+
:
|
|
336
|
+
(
|
|
337
|
+
<EntityForm<M>
|
|
338
|
+
collection={collection}
|
|
339
|
+
path={path}
|
|
340
|
+
entityId={entityId ?? usedEntity?.id}
|
|
341
|
+
onValuesModified={onValuesModified}
|
|
342
|
+
entity={entity}
|
|
343
|
+
initialDirtyValues={cachedDirtyValues}
|
|
344
|
+
openEntityMode={layout}
|
|
345
|
+
forceActionsAtTheBottom={actionsAtTheBottom}
|
|
346
|
+
initialStatus={status}
|
|
347
|
+
className={cls(!mainViewVisible ? "hidden" : "", formProps?.className)}
|
|
348
|
+
EntityFormActionsComponent={EntityEditViewFormActions}
|
|
349
|
+
{...formProps}
|
|
350
|
+
onEntityChange={(entity) => {
|
|
351
|
+
setUsedEntity(entity);
|
|
352
|
+
formProps?.onEntityChange?.(entity);
|
|
353
|
+
}}
|
|
354
|
+
onStatusChange={(status) => {
|
|
355
|
+
setStatus(status);
|
|
356
|
+
formProps?.onStatusChange?.(status);
|
|
357
|
+
}}
|
|
358
|
+
onFormContextReady={(formContext) => {
|
|
359
|
+
setFormContext(formContext);
|
|
360
|
+
formProps?.onFormContextReady?.(formContext);
|
|
361
|
+
}}
|
|
362
|
+
onSaved={(params) => {
|
|
363
|
+
const res = {
|
|
364
|
+
...params,
|
|
365
|
+
selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab
|
|
366
|
+
};
|
|
367
|
+
onSaved?.(res);
|
|
368
|
+
formProps?.onSaved?.(res);
|
|
369
|
+
}}
|
|
370
|
+
Builder={selectedSecondaryForm?.Builder}
|
|
371
|
+
/>
|
|
372
|
+
);
|
|
327
373
|
|
|
328
374
|
const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
|
|
329
375
|
<Tab
|
package/src/form/EntityForm.tsx
CHANGED
|
@@ -90,6 +90,7 @@ export type EntityFormProps<M extends Record<string, any>> = {
|
|
|
90
90
|
children?: React.ReactNode;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
|
|
93
94
|
export function EntityForm<M extends Record<string, any>>({
|
|
94
95
|
path,
|
|
95
96
|
entityId: entityIdProp,
|
|
@@ -678,9 +679,9 @@ export function EntityForm<M extends Record<string, any>>({
|
|
|
678
679
|
/>;
|
|
679
680
|
|
|
680
681
|
return (
|
|
681
|
-
<Formex value={
|
|
682
|
+
<Formex value={formex}>
|
|
682
683
|
<form
|
|
683
|
-
onSubmit={
|
|
684
|
+
onSubmit={formex.handleSubmit}
|
|
684
685
|
onReset={() => formex.resetForm({
|
|
685
686
|
values: getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs) as M
|
|
686
687
|
})}
|
|
@@ -692,7 +693,7 @@ export function EntityForm<M extends Record<string, any>>({
|
|
|
692
693
|
|
|
693
694
|
<div className={cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10")}>
|
|
694
695
|
|
|
695
|
-
{
|
|
696
|
+
{formex.dirty
|
|
696
697
|
? <Tooltip title={"Local unsaved changes"}
|
|
697
698
|
className={"self-end sticky top-4 z-10"}>
|
|
698
699
|
<Chip size={"small"} colorScheme={"orangeDarker"}>
|
|
@@ -777,3 +778,4 @@ function useOnAutoSave(autoSave: undefined | boolean, formex: FormexController<a
|
|
|
777
778
|
}
|
|
778
779
|
}, [formex.values]);
|
|
779
780
|
}
|
|
781
|
+
|
|
@@ -528,7 +528,7 @@ function useCustomBlocker(): NavigationBlocker {
|
|
|
528
528
|
return shouldBlock;
|
|
529
529
|
});
|
|
530
530
|
} catch (e) {
|
|
531
|
-
console.warn("Blocker not available, navigation will not be blocked");
|
|
531
|
+
// console.warn("Blocker not available, navigation will not be blocked");
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
const updateBlockListener = (path: string, block: boolean, basePath?: string) => {
|
|
@@ -175,7 +175,7 @@ function EntityFullScreenRoute({
|
|
|
175
175
|
return blocked.current;
|
|
176
176
|
});
|
|
177
177
|
} catch (e) {
|
|
178
|
-
console.warn("Blocker not available, navigation will not be blocked");
|
|
178
|
+
// console.warn("Blocker not available, navigation will not be blocked");
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
|
package/src/types/fields.tsx
CHANGED
|
@@ -171,7 +171,9 @@ export interface FormContext<M extends Record<string, any> = any> {
|
|
|
171
171
|
openEntityMode: "side_panel" | "full_screen";
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
|
-
* This is the underlying formex controller that powers the form
|
|
174
|
+
* This is the underlying formex controller that powers the form.
|
|
175
|
+
* If you are in a red only mode, the formex controller is there, but you can't
|
|
176
|
+
* operate with it
|
|
175
177
|
*/
|
|
176
178
|
formex: FormexController<M>;
|
|
177
179
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { FormexController } from "@firecms/formex";
|
|
2
|
+
|
|
3
|
+
export function createFormexStub<T extends object>(values: T): FormexController<T> {
|
|
4
|
+
const errorMessage = "You are in a read-only context. You cannot modify the formex controller.";
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
values,
|
|
8
|
+
initialValues: values,
|
|
9
|
+
touched: {} as Record<string, boolean>,
|
|
10
|
+
dirty: false,
|
|
11
|
+
errors: {} as Record<string, string>,
|
|
12
|
+
submitCount: 0,
|
|
13
|
+
isSubmitting: false,
|
|
14
|
+
isValidating: false,
|
|
15
|
+
version: 0,
|
|
16
|
+
canUndo: false,
|
|
17
|
+
canRedo: false,
|
|
18
|
+
|
|
19
|
+
setValues: () => {
|
|
20
|
+
throw new Error(errorMessage);
|
|
21
|
+
},
|
|
22
|
+
setFieldValue: () => {
|
|
23
|
+
throw new Error(errorMessage);
|
|
24
|
+
},
|
|
25
|
+
setFieldTouched: () => {
|
|
26
|
+
throw new Error(errorMessage);
|
|
27
|
+
},
|
|
28
|
+
setDirty: () => {
|
|
29
|
+
throw new Error(errorMessage);
|
|
30
|
+
},
|
|
31
|
+
setSubmitCount: () => {
|
|
32
|
+
throw new Error(errorMessage);
|
|
33
|
+
},
|
|
34
|
+
setFieldError: () => {
|
|
35
|
+
throw new Error(errorMessage);
|
|
36
|
+
},
|
|
37
|
+
handleChange: () => {
|
|
38
|
+
throw new Error(errorMessage);
|
|
39
|
+
},
|
|
40
|
+
handleBlur: () => {
|
|
41
|
+
throw new Error(errorMessage);
|
|
42
|
+
},
|
|
43
|
+
handleSubmit: () => {
|
|
44
|
+
throw new Error(errorMessage);
|
|
45
|
+
},
|
|
46
|
+
validate: () => {
|
|
47
|
+
throw new Error(errorMessage);
|
|
48
|
+
},
|
|
49
|
+
resetForm: () => {
|
|
50
|
+
throw new Error(errorMessage);
|
|
51
|
+
},
|
|
52
|
+
setSubmitting: () => {
|
|
53
|
+
throw new Error(errorMessage);
|
|
54
|
+
},
|
|
55
|
+
undo: () => {
|
|
56
|
+
throw new Error(errorMessage);
|
|
57
|
+
},
|
|
58
|
+
redo: () => {
|
|
59
|
+
throw new Error(errorMessage);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -78,6 +78,7 @@ export function mergeCollection(target: EntityCollection,
|
|
|
78
78
|
modifyCollection?: (props: ModifyCollectionProps) => EntityCollection | void
|
|
79
79
|
): EntityCollection {
|
|
80
80
|
|
|
81
|
+
|
|
81
82
|
const subcollectionsMerged = joinCollectionLists(
|
|
82
83
|
target?.subcollections ?? [],
|
|
83
84
|
source?.subcollections ?? [],
|
|
@@ -94,7 +95,7 @@ export function mergeCollection(target: EntityCollection,
|
|
|
94
95
|
propertiesMerged[key] = source.properties[key] as PropertyOrBuilder;
|
|
95
96
|
});
|
|
96
97
|
|
|
97
|
-
const mergedCollection = mergeDeep(target, source);
|
|
98
|
+
const mergedCollection = mergeDeep(target, source, true);
|
|
98
99
|
const targetPropertiesOrder = getCollectionKeys(target);
|
|
99
100
|
const sourcePropertiesOrder = getCollectionKeys(source);
|
|
100
101
|
const mergedPropertiesOrder = [...new Set([...sourcePropertiesOrder, ...targetPropertiesOrder])];
|
|
@@ -118,6 +119,7 @@ export function mergeCollection(target: EntityCollection,
|
|
|
118
119
|
|
|
119
120
|
// @ts-ignore
|
|
120
121
|
resultCollection["merged"] = true;
|
|
122
|
+
|
|
121
123
|
return resultCollection
|
|
122
124
|
}
|
|
123
125
|
|
package/src/util/objects.ts
CHANGED
|
@@ -12,12 +12,16 @@ export function isObject(item: any) {
|
|
|
12
12
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U): T & U {
|
|
15
|
+
export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U, ignoreUndefined: boolean = false): T & U {
|
|
16
16
|
const targetIsObject = isObject(target);
|
|
17
17
|
const output = targetIsObject ? { ...target } : target;
|
|
18
18
|
if (targetIsObject && isObject(source)) {
|
|
19
19
|
Object.keys(source).forEach(key => {
|
|
20
20
|
const sourceElement = source[key];
|
|
21
|
+
// Skip undefined values when ignoreUndefined is true
|
|
22
|
+
if (ignoreUndefined && sourceElement === undefined) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
21
25
|
if (sourceElement instanceof Date) {
|
|
22
26
|
// Assign a new Date instance with the same time value
|
|
23
27
|
Object.assign(output, { [key]: new Date(sourceElement.getTime()) });
|
package/src/util/resolutions.ts
CHANGED
|
@@ -441,11 +441,13 @@ export function resolveEntityView(entityView: string | EntityCustomView<any>, co
|
|
|
441
441
|
export function resolvedSelectedEntityView<M extends Record<string, any>>(
|
|
442
442
|
customViews: (string | EntityCustomView<M>)[] | undefined,
|
|
443
443
|
customizationController: CustomizationController,
|
|
444
|
-
selectedTab?: string
|
|
444
|
+
selectedTab?: string,
|
|
445
|
+
canEdit?: boolean,
|
|
445
446
|
) {
|
|
446
447
|
const resolvedEntityViews = customViews ? customViews
|
|
447
448
|
.map(e => resolveEntityView(e, customizationController.entityViews))
|
|
448
|
-
.filter(
|
|
449
|
+
.filter((e): e is EntityCustomView<M> => Boolean(e))
|
|
450
|
+
.filter((e) => canEdit || !e.includeActions)
|
|
449
451
|
: [];
|
|
450
452
|
|
|
451
453
|
const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);
|