@firecms/collection_editor 3.0.0-canary.15 → 3.0.0-canary.151
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/LICENSE +114 -21
- package/dist/ConfigControllerProvider.d.ts +11 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +4921 -3536
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +6846 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +14 -2
- package/dist/types/collection_inference.d.ts +1 -1
- package/dist/types/config_permissions.d.ts +2 -2
- package/dist/types/persisted_collection.d.ts +1 -1
- package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
- package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
- package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +4 -3
- package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
- package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
- package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
- package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
- package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
- package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
- package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
- package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
- package/dist/useCollectionEditorPlugin.d.ts +17 -11
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +21 -35
- package/src/ConfigControllerProvider.tsx +75 -63
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +14 -4
- package/src/types/collection_inference.ts +1 -1
- package/src/types/config_permissions.ts +1 -1
- package/src/types/persisted_collection.ts +2 -3
- package/src/ui/CollectionViewHeaderAction.tsx +10 -5
- package/src/ui/EditorCollectionAction.tsx +10 -63
- package/src/ui/EditorCollectionActionStart.tsx +88 -0
- package/src/ui/HomePageEditorCollectionAction.tsx +18 -13
- package/src/ui/NewCollectionButton.tsx +12 -10
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +11 -6
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +70 -9
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +61 -34
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +37 -34
- package/src/ui/collection_editor/EnumForm.tsx +5 -2
- package/src/ui/collection_editor/GetCodeDialog.tsx +52 -21
- package/src/ui/collection_editor/PropertyEditView.tsx +255 -80
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +4 -7
- package/src/ui/collection_editor/PropertyTree.tsx +7 -5
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +25 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +40 -9
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +50 -47
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +1 -1
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
- package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
- package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
- package/src/ui/collection_editor/properties/StoragePropertyField.tsx +32 -17
- package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
- package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
- package/src/ui/collection_editor/templates/pages_template.ts +1 -6
- package/src/useCollectionEditorPlugin.tsx +41 -31
- package/src/utils/collections.ts +30 -0
- package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
- package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
- package/src/ui/RootCollectionSuggestions.tsx +0 -63
- package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
CircularProgressCenter,
|
|
5
|
+
Entity,
|
|
5
6
|
EntityCollection,
|
|
6
7
|
ErrorView,
|
|
7
8
|
isPropertyBuilder,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
import {
|
|
26
27
|
ArrowBackIcon,
|
|
27
28
|
Button,
|
|
28
|
-
|
|
29
|
+
cls,
|
|
29
30
|
coolIconKeys,
|
|
30
31
|
defaultBorderMixin,
|
|
31
32
|
Dialog,
|
|
@@ -76,9 +77,10 @@ export interface CollectionEditorDialogProps {
|
|
|
76
77
|
icon: React.ReactNode
|
|
77
78
|
};
|
|
78
79
|
pathSuggestions?: (path?: string) => Promise<string[]>;
|
|
79
|
-
getUser
|
|
80
|
+
getUser?: (uid: string) => User | null;
|
|
80
81
|
getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
|
|
81
82
|
parentCollection?: PersistedCollection;
|
|
83
|
+
existingEntities?: Entity<any>[];
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
@@ -88,13 +90,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
88
90
|
const [formDirty, setFormDirty] = React.useState<boolean>(false);
|
|
89
91
|
const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
|
|
90
92
|
|
|
91
|
-
const handleCancel =
|
|
93
|
+
const handleCancel = () => {
|
|
92
94
|
if (!formDirty) {
|
|
93
95
|
props.handleClose(undefined);
|
|
94
96
|
} else {
|
|
95
97
|
setUnsavedChangesDialogOpen(true);
|
|
96
98
|
}
|
|
97
|
-
}
|
|
99
|
+
};
|
|
98
100
|
|
|
99
101
|
useEffect(() => {
|
|
100
102
|
if (!open) {
|
|
@@ -136,7 +138,7 @@ type EditorView = "welcome"
|
|
|
136
138
|
| "extra_view"
|
|
137
139
|
| "subcollections";
|
|
138
140
|
|
|
139
|
-
export function CollectionEditor
|
|
141
|
+
export function CollectionEditor(props: CollectionEditorDialogProps & {
|
|
140
142
|
handleCancel: () => void,
|
|
141
143
|
setFormDirty: (dirty: boolean) => void
|
|
142
144
|
}) {
|
|
@@ -154,14 +156,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
154
156
|
const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
|
|
155
157
|
const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
|
|
156
158
|
const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
|
|
157
|
-
const [collection, setCollection] = React.useState<PersistedCollection<
|
|
159
|
+
const [collection, setCollection] = React.useState<PersistedCollection<any> | undefined>();
|
|
158
160
|
const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
|
|
159
161
|
|
|
160
162
|
useEffect(() => {
|
|
161
163
|
try {
|
|
162
164
|
if (navigation.initialised) {
|
|
163
165
|
if (props.editedCollectionId) {
|
|
164
|
-
setCollection(navigation.getCollectionFromPaths
|
|
166
|
+
setCollection(navigation.getCollectionFromPaths([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
|
|
165
167
|
} else {
|
|
166
168
|
setCollection(undefined);
|
|
167
169
|
}
|
|
@@ -170,7 +172,8 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
170
172
|
} catch (e) {
|
|
171
173
|
console.error(e);
|
|
172
174
|
}
|
|
173
|
-
}, [
|
|
175
|
+
}, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
|
|
176
|
+
|
|
174
177
|
if (!topLevelNavigation) {
|
|
175
178
|
throw Error("Internal: Navigation not ready in collection editor");
|
|
176
179
|
}
|
|
@@ -186,14 +189,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
186
189
|
}
|
|
187
190
|
: undefined;
|
|
188
191
|
|
|
189
|
-
const initialValues: PersistedCollection<
|
|
192
|
+
const initialValues: PersistedCollection<any> = initialCollection
|
|
190
193
|
? applyPropertyConfigs(initialCollection, propertyConfigs)
|
|
191
194
|
: {
|
|
192
195
|
id: initialValuesProp?.path ?? randomString(16),
|
|
193
196
|
path: initialValuesProp?.path ?? "",
|
|
194
197
|
name: initialValuesProp?.name ?? "",
|
|
195
198
|
group: initialValuesProp?.group ?? "",
|
|
196
|
-
properties: {} as PropertiesOrBuilders
|
|
199
|
+
properties: {} as PropertiesOrBuilders,
|
|
197
200
|
propertiesOrder: [],
|
|
198
201
|
icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
|
|
199
202
|
ownerId: authController.user?.uid ?? ""
|
|
@@ -243,7 +246,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
243
246
|
setCollection,
|
|
244
247
|
initialValues,
|
|
245
248
|
propertyConfigs,
|
|
246
|
-
groups
|
|
249
|
+
groups,
|
|
250
|
+
existingEntities
|
|
247
251
|
}: CollectionEditorDialogProps & {
|
|
248
252
|
handleCancel: () => void,
|
|
249
253
|
setFormDirty: (dirty: boolean) => void,
|
|
@@ -272,6 +276,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
272
276
|
|
|
273
277
|
const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
|
|
274
278
|
const id = updatedCollection.id || updatedCollection.path;
|
|
279
|
+
|
|
275
280
|
return configController.saveCollection({
|
|
276
281
|
id,
|
|
277
282
|
collectionData: updatedCollection,
|
|
@@ -293,7 +298,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
293
298
|
});
|
|
294
299
|
};
|
|
295
300
|
|
|
296
|
-
const setNextMode =
|
|
301
|
+
const setNextMode = () => {
|
|
297
302
|
if (currentView === "details") {
|
|
298
303
|
if (importConfig.inUse) {
|
|
299
304
|
setCurrentView("import_data_saving");
|
|
@@ -314,14 +319,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
314
319
|
setCurrentView("details");
|
|
315
320
|
}
|
|
316
321
|
|
|
317
|
-
}
|
|
322
|
+
};
|
|
318
323
|
|
|
319
|
-
const doCollectionInference =
|
|
324
|
+
const doCollectionInference = (collection: PersistedCollection<any>) => {
|
|
320
325
|
if (!collectionInference) return undefined;
|
|
321
|
-
return collectionInference?.(collection.path, collection.collectionGroup ?? false,
|
|
322
|
-
}
|
|
326
|
+
return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
|
|
327
|
+
};
|
|
323
328
|
|
|
324
|
-
const inferCollectionFromData =
|
|
329
|
+
const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
|
|
325
330
|
|
|
326
331
|
try {
|
|
327
332
|
if (!doCollectionInference) {
|
|
@@ -365,15 +370,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
365
370
|
});
|
|
366
371
|
return newCollection;
|
|
367
372
|
}
|
|
368
|
-
}
|
|
373
|
+
};
|
|
369
374
|
|
|
370
375
|
const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
|
|
371
|
-
console.
|
|
376
|
+
console.debug("Submitting collection", newCollectionState);
|
|
372
377
|
try {
|
|
373
378
|
|
|
374
379
|
if (!isNewCollection) {
|
|
375
380
|
saveCollection(newCollectionState).then(() => {
|
|
376
|
-
formexController.resetForm(
|
|
381
|
+
formexController.resetForm();
|
|
377
382
|
handleClose(newCollectionState);
|
|
378
383
|
});
|
|
379
384
|
return;
|
|
@@ -462,7 +467,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
462
467
|
const formController = useCreateFormex<PersistedCollection<M>>({
|
|
463
468
|
initialValues,
|
|
464
469
|
onSubmit,
|
|
465
|
-
validation
|
|
470
|
+
validation,
|
|
471
|
+
debugId: "COLLECTION_EDITOR"
|
|
466
472
|
});
|
|
467
473
|
|
|
468
474
|
const {
|
|
@@ -480,25 +486,36 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
480
486
|
|
|
481
487
|
const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
|
|
482
488
|
const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
|
|
483
|
-
const getDataWithPath = resolvedPath && getData ? () =>
|
|
489
|
+
const getDataWithPath = resolvedPath && getData ? async () => {
|
|
490
|
+
const data = await getData(resolvedPath, parentPaths ?? []);
|
|
491
|
+
if (existingEntities) {
|
|
492
|
+
const existingData = existingEntities.map(e => e.values);
|
|
493
|
+
data.push(...existingData);
|
|
494
|
+
}
|
|
495
|
+
return data;
|
|
496
|
+
} : undefined;
|
|
484
497
|
|
|
485
498
|
useEffect(() => {
|
|
486
499
|
setFormDirty(dirty);
|
|
487
500
|
}, [dirty]);
|
|
488
501
|
|
|
489
|
-
function onImportDataSet(data: object[]) {
|
|
502
|
+
function onImportDataSet(data: object[], propertiesOrder?: string[]) {
|
|
490
503
|
importConfig.setInUse(true);
|
|
491
504
|
buildEntityPropertiesFromData(data, getInferenceType)
|
|
492
505
|
.then((properties) => {
|
|
493
506
|
const res = cleanPropertiesFromImport(properties);
|
|
494
507
|
|
|
495
|
-
setFieldValue("properties", res.properties);
|
|
496
|
-
setFieldValue("propertiesOrder", Object.keys(res.properties));
|
|
497
|
-
|
|
498
508
|
importConfig.setIdColumn(res.idColumn);
|
|
499
509
|
importConfig.setImportData(data);
|
|
500
510
|
importConfig.setHeadersMapping(res.headersMapping);
|
|
511
|
+
const filteredHeadingsOrder = ((propertiesOrder ?? [])
|
|
512
|
+
.filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
|
|
513
|
+
importConfig.setHeadingsOrder(filteredHeadingsOrder);
|
|
501
514
|
importConfig.setOriginProperties(res.properties);
|
|
515
|
+
|
|
516
|
+
const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
|
|
517
|
+
setFieldValue("properties", res.properties);
|
|
518
|
+
setFieldValue("propertiesOrder", mappedHeadings);
|
|
502
519
|
});
|
|
503
520
|
}
|
|
504
521
|
|
|
@@ -521,7 +538,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
521
538
|
|
|
522
539
|
<>
|
|
523
540
|
{!isNewCollection && <Tabs value={currentView}
|
|
524
|
-
|
|
541
|
+
innerClassName={cls(defaultBorderMixin, "justify-end bg-surface-50 dark:bg-surface-950 border-b")}
|
|
525
542
|
onValueChange={(v) => setCurrentView(v as EditorView)}>
|
|
526
543
|
<Tab value={"details"}>
|
|
527
544
|
Details
|
|
@@ -536,7 +553,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
536
553
|
|
|
537
554
|
<form noValidate
|
|
538
555
|
onSubmit={formController.handleSubmit}
|
|
539
|
-
className={
|
|
556
|
+
className={cls(
|
|
540
557
|
isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
|
|
541
558
|
"flex-grow flex flex-col relative")}>
|
|
542
559
|
|
|
@@ -551,9 +568,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
551
568
|
{currentView === "welcome" &&
|
|
552
569
|
<CollectionEditorWelcomeView
|
|
553
570
|
path={path}
|
|
554
|
-
onContinue={(importData) => {
|
|
571
|
+
onContinue={(importData, propertiesOrder) => {
|
|
572
|
+
// console.log("Import data", importData, propertiesOrder)
|
|
555
573
|
if (importData) {
|
|
556
|
-
onImportDataSet(importData);
|
|
574
|
+
onImportDataSet(importData, propertiesOrder);
|
|
557
575
|
setCurrentView("import_data_mapping");
|
|
558
576
|
} else {
|
|
559
577
|
setCurrentView("details");
|
|
@@ -733,7 +751,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
733
751
|
}
|
|
734
752
|
|
|
735
753
|
function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
|
|
736
|
-
const {
|
|
754
|
+
const {
|
|
755
|
+
properties,
|
|
756
|
+
...rest
|
|
757
|
+
} = collection;
|
|
737
758
|
const propertiesResult: PropertiesOrBuilders<any> = {};
|
|
738
759
|
if (properties) {
|
|
739
760
|
Object.keys(properties).forEach((key) => {
|
|
@@ -741,7 +762,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
|
|
|
741
762
|
});
|
|
742
763
|
}
|
|
743
764
|
|
|
744
|
-
return {
|
|
765
|
+
return {
|
|
766
|
+
...rest,
|
|
767
|
+
properties: propertiesResult
|
|
768
|
+
};
|
|
745
769
|
}
|
|
746
770
|
|
|
747
771
|
function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
|
|
@@ -761,7 +785,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
|
|
|
761
785
|
Object.keys(internalProperty.properties).forEach((key) => {
|
|
762
786
|
properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
|
|
763
787
|
});
|
|
764
|
-
internalProperty = {
|
|
788
|
+
internalProperty = {
|
|
789
|
+
...internalProperty,
|
|
790
|
+
properties
|
|
791
|
+
};
|
|
765
792
|
}
|
|
766
793
|
|
|
767
794
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { EntityCollection, unslugify, } from "@firecms/core";
|
|
3
|
-
import { Button, Card, Chip, CircularProgress,
|
|
3
|
+
import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
|
|
4
4
|
|
|
5
5
|
import { productsCollectionTemplate } from "./templates/products_template";
|
|
6
6
|
import { blogCollectionTemplate } from "./templates/blog_template";
|
|
@@ -19,7 +19,7 @@ export function CollectionEditorWelcomeView({
|
|
|
19
19
|
path: string;
|
|
20
20
|
pathSuggestions?: (path: string) => Promise<string[]>;
|
|
21
21
|
parentCollection?: EntityCollection;
|
|
22
|
-
onContinue: (importData?: object[]) => void;
|
|
22
|
+
onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
|
|
23
23
|
existingCollectionPaths?: string[];
|
|
24
24
|
}) {
|
|
25
25
|
|
|
@@ -154,7 +154,7 @@ export function CollectionEditorWelcomeView({
|
|
|
154
154
|
● Create a collection from a file (csv, json, xls, xslx...)
|
|
155
155
|
</Typography>
|
|
156
156
|
|
|
157
|
-
<ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
|
|
157
|
+
<ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
|
|
158
158
|
|
|
159
159
|
</div>}
|
|
160
160
|
|
|
@@ -185,14 +185,15 @@ export function TemplateButton({
|
|
|
185
185
|
}) {
|
|
186
186
|
|
|
187
187
|
return (
|
|
188
|
-
<Tooltip title={subtitle}
|
|
188
|
+
<Tooltip title={subtitle}
|
|
189
|
+
asChild={true}>
|
|
189
190
|
<Card
|
|
190
191
|
onClick={onClick}
|
|
191
|
-
className={
|
|
192
|
+
className={cls(
|
|
192
193
|
"my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
|
|
193
|
-
"text-
|
|
194
|
+
"text-surface-700 dark:text-surface-accent-300",
|
|
194
195
|
"hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
195
|
-
"border-
|
|
196
|
+
"border-surface-400 dark:border-surface-600 "
|
|
196
197
|
)}
|
|
197
198
|
>
|
|
198
199
|
{icon}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { Field, getIn, useFormex } from "@firecms/formex";
|
|
4
4
|
import {
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
AutoAwesomeIcon,
|
|
20
20
|
Button,
|
|
21
21
|
CircularProgress,
|
|
22
|
-
|
|
22
|
+
cls,
|
|
23
23
|
CodeIcon,
|
|
24
24
|
DebouncedTextField,
|
|
25
25
|
defaultBorderMixin,
|
|
@@ -43,7 +43,7 @@ type CollectionEditorFormProps = {
|
|
|
43
43
|
setDirty?: (dirty: boolean) => void;
|
|
44
44
|
reservedGroups?: string[];
|
|
45
45
|
extraIcon: React.ReactNode;
|
|
46
|
-
getUser
|
|
46
|
+
getUser?: (uid: string) => User | null;
|
|
47
47
|
getData?: () => Promise<object[]>;
|
|
48
48
|
doCollectionInference: (collection: PersistedCollection) => Promise<Partial<EntityCollection> | null> | undefined;
|
|
49
49
|
propertyConfigs: Record<string, PropertyConfig>;
|
|
@@ -158,20 +158,20 @@ export function CollectionPropertiesEditorForm({
|
|
|
158
158
|
}
|
|
159
159
|
: undefined;
|
|
160
160
|
|
|
161
|
-
const getCurrentPropertiesOrder =
|
|
162
|
-
if (!namespace) return currentPropertiesOrderRef.current[""];
|
|
161
|
+
const getCurrentPropertiesOrder = (namespace?: string) => {
|
|
162
|
+
if (!namespace) return currentPropertiesOrderRef.current[""] ?? getIn(values, namespaceToPropertiesOrderPath());
|
|
163
163
|
return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
|
|
164
|
-
}
|
|
164
|
+
};
|
|
165
165
|
|
|
166
|
-
const updatePropertiesOrder =
|
|
166
|
+
const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
|
|
167
167
|
const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
|
|
168
168
|
|
|
169
169
|
setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
|
|
170
170
|
currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
|
|
171
171
|
|
|
172
|
-
}
|
|
172
|
+
};
|
|
173
173
|
|
|
174
|
-
const deleteProperty =
|
|
174
|
+
const deleteProperty = (propertyKey?: string, namespace?: string) => {
|
|
175
175
|
const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
|
|
176
176
|
if (!fullId)
|
|
177
177
|
throw Error("collection editor miss config");
|
|
@@ -179,15 +179,17 @@ export function CollectionPropertiesEditorForm({
|
|
|
179
179
|
setFieldValue(idToPropertiesPath(fullId), undefined, false);
|
|
180
180
|
|
|
181
181
|
const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
if (currentPropertiesOrder) {
|
|
183
|
+
const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
|
|
184
|
+
updatePropertiesOrder(newPropertiesOrder, namespace);
|
|
185
|
+
}
|
|
184
186
|
|
|
185
187
|
setNewPropertyDialogOpen(false);
|
|
186
188
|
|
|
187
189
|
setSelectedPropertyIndex(undefined);
|
|
188
190
|
setSelectedPropertyKey(undefined);
|
|
189
191
|
setSelectedPropertyNamespace(undefined);
|
|
190
|
-
}
|
|
192
|
+
};
|
|
191
193
|
|
|
192
194
|
const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
|
|
193
195
|
setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
|
|
@@ -207,8 +209,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
207
209
|
...(values.properties ?? {}),
|
|
208
210
|
[id]: property
|
|
209
211
|
}, false);
|
|
210
|
-
const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
|
|
211
212
|
|
|
213
|
+
const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
|
|
212
214
|
updatePropertiesOrder(newPropertiesOrder);
|
|
213
215
|
|
|
214
216
|
setNewPropertyDialogOpen(false);
|
|
@@ -226,17 +228,13 @@ export function CollectionPropertiesEditorForm({
|
|
|
226
228
|
namespace
|
|
227
229
|
}: OnPropertyChangedParams) => {
|
|
228
230
|
|
|
231
|
+
console.log("!!!!!! onPropertyChanged", property)
|
|
232
|
+
|
|
229
233
|
const fullId = id ? getFullId(id, namespace) : undefined;
|
|
230
234
|
const propertyPath = fullId ? idToPropertiesPath(fullId) : undefined;
|
|
231
235
|
|
|
232
236
|
// If the id has changed we need to a little cleanup
|
|
233
237
|
if (previousId && previousId !== id) {
|
|
234
|
-
console.debug("onPropertyChanged, id change", {
|
|
235
|
-
id,
|
|
236
|
-
property,
|
|
237
|
-
previousId,
|
|
238
|
-
namespace
|
|
239
|
-
})
|
|
240
238
|
|
|
241
239
|
const previousFullId = getFullId(previousId, namespace);
|
|
242
240
|
const previousPropertyPath = idToPropertiesPath(previousFullId);
|
|
@@ -273,7 +271,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
273
271
|
|
|
274
272
|
};
|
|
275
273
|
|
|
276
|
-
const onPropertyErrorInternal =
|
|
274
|
+
const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
|
|
277
275
|
const propertyPath = id ? getFullId(id, namespace) : undefined;
|
|
278
276
|
console.debug("onPropertyErrorInternal", {
|
|
279
277
|
id,
|
|
@@ -286,7 +284,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
286
284
|
onPropertyError(id, namespace, hasError ? error : undefined);
|
|
287
285
|
setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
|
|
288
286
|
}
|
|
289
|
-
}
|
|
287
|
+
}
|
|
290
288
|
|
|
291
289
|
const closePropertyDialog = () => {
|
|
292
290
|
setSelectedPropertyIndex(undefined);
|
|
@@ -301,9 +299,9 @@ export function CollectionPropertiesEditorForm({
|
|
|
301
299
|
? values.propertiesOrder
|
|
302
300
|
: Object.keys(values.properties)) as string[];
|
|
303
301
|
|
|
304
|
-
const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
|
|
302
|
+
const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
|
|
305
303
|
|
|
306
|
-
const onPropertyClick =
|
|
304
|
+
const onPropertyClick = (propertyKey: string, namespace?: string) => {
|
|
307
305
|
console.debug("CollectionEditor: onPropertyClick", {
|
|
308
306
|
propertyKey,
|
|
309
307
|
namespace
|
|
@@ -311,11 +309,11 @@ export function CollectionPropertiesEditorForm({
|
|
|
311
309
|
setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
|
|
312
310
|
setSelectedPropertyKey(propertyKey);
|
|
313
311
|
setSelectedPropertyNamespace(namespace);
|
|
314
|
-
}
|
|
312
|
+
};
|
|
315
313
|
|
|
316
314
|
const body = (
|
|
317
|
-
<div className={"grid grid-cols-12 gap-2 h-full bg-
|
|
318
|
-
<div className={
|
|
315
|
+
<div className={"grid grid-cols-12 gap-2 h-full bg-surface-50 dark:bg-surface-900"}>
|
|
316
|
+
<div className={cls(
|
|
319
317
|
"p-4 md:p-8 pb-20 md:pb-20",
|
|
320
318
|
"col-span-12 lg:col-span-5 h-full overflow-auto",
|
|
321
319
|
!asDialog && "border-r " + defaultBorderMixin
|
|
@@ -349,7 +347,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
349
347
|
</div>}
|
|
350
348
|
|
|
351
349
|
<div className="ml-1 mt-2 flex flex-row gap-2">
|
|
352
|
-
<Tooltip title={"Get the code for this collection"}
|
|
350
|
+
<Tooltip title={"Get the code for this collection"}
|
|
351
|
+
asChild={true}>
|
|
353
352
|
<IconButton
|
|
354
353
|
variant={"filled"}
|
|
355
354
|
disabled={inferringProperties}
|
|
@@ -357,7 +356,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
357
356
|
<CodeIcon/>
|
|
358
357
|
</IconButton>
|
|
359
358
|
</Tooltip>
|
|
360
|
-
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
359
|
+
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
360
|
+
asChild={true}>
|
|
361
361
|
<IconButton
|
|
362
362
|
variant={"filled"}
|
|
363
363
|
disabled={inferringProperties}
|
|
@@ -365,7 +365,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
365
365
|
{inferringProperties ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
|
|
366
366
|
</IconButton>
|
|
367
367
|
</Tooltip>}
|
|
368
|
-
<Tooltip title={"Add new property"}
|
|
368
|
+
<Tooltip title={"Add new property"}
|
|
369
|
+
asChild={true}>
|
|
369
370
|
<Button
|
|
370
371
|
variant={"outlined"}
|
|
371
372
|
onClick={() => setNewPropertyDialogOpen(true)}>
|
|
@@ -469,6 +470,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
469
470
|
getData={getData}
|
|
470
471
|
propertyConfigs={propertyConfigs}
|
|
471
472
|
collectionEditable={collectionEditable}
|
|
473
|
+
onCancel={closePropertyDialog}
|
|
472
474
|
onOkClicked={asDialog
|
|
473
475
|
? closePropertyDialog
|
|
474
476
|
: undefined
|
|
@@ -496,11 +498,12 @@ export function CollectionPropertiesEditorForm({
|
|
|
496
498
|
collectionEditable={collectionEditable}
|
|
497
499
|
existingPropertyKeys={values.propertiesOrder as string[]}/>
|
|
498
500
|
|
|
499
|
-
<
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
501
|
+
<ErrorBoundary>
|
|
502
|
+
<GetCodeDialog
|
|
503
|
+
collection={values}
|
|
504
|
+
open={codeDialogOpen}
|
|
505
|
+
onOpenChange={setCodeDialogOpen}/>
|
|
506
|
+
</ErrorBoundary>
|
|
504
507
|
</>
|
|
505
508
|
);
|
|
506
509
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
2
|
import equal from "react-fast-compare"
|
|
3
3
|
|
|
4
|
-
import { ArrayContainer, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
|
|
4
|
+
import { ArrayContainer, ArrayEntryParams, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
|
|
5
5
|
import {
|
|
6
6
|
AutoAwesomeIcon,
|
|
7
7
|
Badge,
|
|
@@ -121,7 +121,10 @@ function EnumFormFields({
|
|
|
121
121
|
const inferredValuesRef = React.useRef(new Set());
|
|
122
122
|
const inferredValues = inferredValuesRef.current;
|
|
123
123
|
|
|
124
|
-
const buildEntry = (
|
|
124
|
+
const buildEntry = ({
|
|
125
|
+
index,
|
|
126
|
+
internalId
|
|
127
|
+
}:ArrayEntryParams) => {
|
|
125
128
|
const justAdded = lastInternalIdAdded === internalId;
|
|
126
129
|
const entryError = errors?.enumValues && errors?.enumValues[index];
|
|
127
130
|
return <EnumEntry index={index}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import { EntityCollection, useSnackbarController } from "@firecms/core";
|
|
1
|
+
import { EntityCollection, isEmptyObject, useSnackbarController } from "@firecms/core";
|
|
2
2
|
import { Button, ContentCopyIcon, Dialog, DialogActions, DialogContent, Typography, } from "@firecms/ui";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import JSON5 from "json5";
|
|
5
5
|
import { Highlight, themes } from "prism-react-renderer"
|
|
6
6
|
import { camelCase } from "./utils/strings";
|
|
7
|
+
import { clone } from "@firecms/formex";
|
|
7
8
|
|
|
8
|
-
export function GetCodeDialog({
|
|
9
|
+
export function GetCodeDialog({
|
|
10
|
+
collection,
|
|
11
|
+
onOpenChange,
|
|
12
|
+
open
|
|
13
|
+
}: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
|
|
9
14
|
|
|
10
15
|
const snackbarController = useSnackbarController();
|
|
11
16
|
|
|
12
|
-
const code =
|
|
17
|
+
const code = collection
|
|
18
|
+
? "import { EntityCollection } from \"@firecms/core\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode({ ...collection }), null, "\t")
|
|
19
|
+
: "No collection selected";
|
|
13
20
|
return <Dialog open={open}
|
|
14
21
|
onOpenChange={onOpenChange}
|
|
15
22
|
maxWidth={"4xl"}>
|
|
@@ -29,7 +36,13 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
|
|
|
29
36
|
code={code}
|
|
30
37
|
language="typescript"
|
|
31
38
|
>
|
|
32
|
-
{({
|
|
39
|
+
{({
|
|
40
|
+
className,
|
|
41
|
+
style,
|
|
42
|
+
tokens,
|
|
43
|
+
getLineProps,
|
|
44
|
+
getTokenProps
|
|
45
|
+
}) => (
|
|
33
46
|
<pre style={style} className={"p-4 rounded text-sm"}>
|
|
34
47
|
{tokens.map((line, i) => (
|
|
35
48
|
<div key={i} {...getLineProps({ line })}>
|
|
@@ -66,24 +79,40 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
|
|
|
66
79
|
|
|
67
80
|
function collectionToCode(collection: EntityCollection): object {
|
|
68
81
|
|
|
69
|
-
const propertyCleanup = (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
const propertyCleanup = (value: any): any => {
|
|
83
|
+
if (value === undefined || value === null) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
const valueCopy = clone(value);
|
|
87
|
+
if (typeof valueCopy === "function") {
|
|
88
|
+
return valueCopy;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(valueCopy)) {
|
|
91
|
+
return valueCopy.map((v: any) => propertyCleanup(v));
|
|
92
|
+
}
|
|
93
|
+
if (typeof valueCopy === "object") {
|
|
94
|
+
if (valueCopy === null)
|
|
95
|
+
return valueCopy;
|
|
96
|
+
Object.keys(valueCopy).forEach((key) => {
|
|
97
|
+
if (!isEmptyObject(valueCopy)) {
|
|
98
|
+
const childRes = propertyCleanup(valueCopy[key]);
|
|
99
|
+
if (childRes !== null && childRes !== undefined && childRes !== false && !isEmptyObject(childRes)) {
|
|
100
|
+
valueCopy[key] = childRes;
|
|
101
|
+
} else {
|
|
102
|
+
delete valueCopy[key];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
delete valueCopy.fromBuilder;
|
|
107
|
+
delete valueCopy.resolved;
|
|
108
|
+
delete valueCopy.propertiesOrder;
|
|
109
|
+
delete valueCopy.propertyConfig;
|
|
110
|
+
delete valueCopy.resolvedProperties;
|
|
111
|
+
delete valueCopy.editable;
|
|
79
112
|
|
|
80
|
-
if (updatedProperty.type === "map") {
|
|
81
|
-
return {
|
|
82
|
-
...updatedProperty,
|
|
83
|
-
properties: updatedProperty.properties.map(propertyCleanup)
|
|
84
|
-
}
|
|
85
113
|
}
|
|
86
|
-
|
|
114
|
+
|
|
115
|
+
return valueCopy;
|
|
87
116
|
}
|
|
88
117
|
|
|
89
118
|
return {
|
|
@@ -99,7 +128,9 @@ function collectionToCode(collection: EntityCollection): object {
|
|
|
99
128
|
customId: collection.customId,
|
|
100
129
|
initialFilter: collection.initialFilter,
|
|
101
130
|
initialSort: collection.initialSort,
|
|
102
|
-
properties: Object.entries(
|
|
131
|
+
properties: Object.entries({
|
|
132
|
+
...(collection.properties ?? {})
|
|
133
|
+
})
|
|
103
134
|
.map(([key, value]) => ({
|
|
104
135
|
[key]: propertyCleanup(value)
|
|
105
136
|
}))
|