@firecms/collection_editor 3.0.0-canary.1 → 3.0.0-canary.100
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 +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +4412 -3413
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +6467 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +4 -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/PropertyTree.d.ts +9 -9
- package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
- package/dist/ui/collection_editor/utils/supported_fields.d.ts +2 -2
- package/dist/useCollectionEditorPlugin.d.ts +6 -9
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +21 -35
- package/src/ConfigControllerProvider.tsx +66 -63
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +6 -4
- package/src/types/persisted_collection.ts +1 -1
- package/src/ui/CollectionViewHeaderAction.tsx +6 -3
- package/src/ui/EditorCollectionAction.tsx +9 -63
- package/src/ui/EditorCollectionActionStart.tsx +87 -0
- package/src/ui/HomePageEditorCollectionAction.tsx +16 -11
- package/src/ui/MissingReferenceWidget.tsx +2 -1
- package/src/ui/NewCollectionButton.tsx +12 -10
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +6 -3
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +51 -22
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +64 -35
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +5 -5
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +27 -24
- package/src/ui/collection_editor/EnumForm.tsx +3 -4
- package/src/ui/collection_editor/GetCodeDialog.tsx +15 -3
- package/src/ui/collection_editor/PropertyEditView.tsx +9 -8
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +3 -6
- package/src/ui/collection_editor/PropertySelectItem.tsx +2 -2
- package/src/ui/collection_editor/PropertyTree.tsx +3 -3
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +2 -2
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +25 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +9 -7
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +14 -8
- package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +7 -8
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +7 -8
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +1 -1
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +8 -9
- package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +3 -4
- package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
- package/src/ui/collection_editor/properties/StoragePropertyField.tsx +21 -1
- package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +3 -4
- package/src/ui/collection_editor/utils/supported_fields.tsx +3 -3
- package/src/useCollectionEditorPlugin.tsx +29 -30
- package/src/utils/collections.ts +30 -0
- package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
- package/dist/ui/collection_editor/properties/FieldHelperView.d.ts +0 -4
- package/src/ui/RootCollectionSuggestions.tsx +0 -63
- package/src/ui/collection_editor/properties/FieldHelperView.tsx +0 -13
|
@@ -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,
|
|
@@ -293,7 +297,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
293
297
|
});
|
|
294
298
|
};
|
|
295
299
|
|
|
296
|
-
const setNextMode =
|
|
300
|
+
const setNextMode = () => {
|
|
297
301
|
if (currentView === "details") {
|
|
298
302
|
if (importConfig.inUse) {
|
|
299
303
|
setCurrentView("import_data_saving");
|
|
@@ -314,14 +318,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
314
318
|
setCurrentView("details");
|
|
315
319
|
}
|
|
316
320
|
|
|
317
|
-
}
|
|
321
|
+
};
|
|
318
322
|
|
|
319
|
-
const doCollectionInference =
|
|
323
|
+
const doCollectionInference = (collection: PersistedCollection<any>) => {
|
|
320
324
|
if (!collectionInference) return undefined;
|
|
321
325
|
return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentCollectionIds ?? []);
|
|
322
|
-
}
|
|
326
|
+
};
|
|
323
327
|
|
|
324
|
-
const inferCollectionFromData =
|
|
328
|
+
const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
|
|
325
329
|
|
|
326
330
|
try {
|
|
327
331
|
if (!doCollectionInference) {
|
|
@@ -365,10 +369,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
365
369
|
});
|
|
366
370
|
return newCollection;
|
|
367
371
|
}
|
|
368
|
-
}
|
|
372
|
+
};
|
|
369
373
|
|
|
370
374
|
const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
|
|
371
|
-
console.
|
|
375
|
+
console.debug("Submitting collection", newCollectionState);
|
|
372
376
|
try {
|
|
373
377
|
|
|
374
378
|
if (!isNewCollection) {
|
|
@@ -473,31 +477,43 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
473
477
|
submitCount
|
|
474
478
|
} = formController;
|
|
475
479
|
|
|
476
|
-
|
|
480
|
+
// TODO: getting data is only working in root collections with this code
|
|
481
|
+
const path = values.path;
|
|
477
482
|
const updatedFullPath = fullPath?.includes("/") ? fullPath?.split("/").slice(0, -1).join("/") + "/" + path : path; // TODO: this path is wrong
|
|
478
483
|
const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
|
|
479
484
|
|
|
480
485
|
const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
|
|
481
486
|
const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
|
|
482
|
-
const getDataWithPath = resolvedPath && getData ? () =>
|
|
487
|
+
const getDataWithPath = resolvedPath && getData ? async () => {
|
|
488
|
+
const data = await getData(resolvedPath, parentPaths ?? []);
|
|
489
|
+
if (existingEntities) {
|
|
490
|
+
const existingData = existingEntities.map(e => e.values);
|
|
491
|
+
data.push(...existingData);
|
|
492
|
+
}
|
|
493
|
+
return data;
|
|
494
|
+
} : undefined;
|
|
483
495
|
|
|
484
496
|
useEffect(() => {
|
|
485
497
|
setFormDirty(dirty);
|
|
486
498
|
}, [dirty]);
|
|
487
499
|
|
|
488
|
-
function onImportDataSet(data: object[]) {
|
|
500
|
+
function onImportDataSet(data: object[], propertiesOrder?: string[]) {
|
|
489
501
|
importConfig.setInUse(true);
|
|
490
502
|
buildEntityPropertiesFromData(data, getInferenceType)
|
|
491
503
|
.then((properties) => {
|
|
492
504
|
const res = cleanPropertiesFromImport(properties);
|
|
493
505
|
|
|
494
|
-
setFieldValue("properties", res.properties);
|
|
495
|
-
setFieldValue("propertiesOrder", Object.keys(res.properties));
|
|
496
|
-
|
|
497
506
|
importConfig.setIdColumn(res.idColumn);
|
|
498
507
|
importConfig.setImportData(data);
|
|
499
508
|
importConfig.setHeadersMapping(res.headersMapping);
|
|
509
|
+
const filteredHeadingsOrder = ((propertiesOrder ?? [])
|
|
510
|
+
.filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
|
|
511
|
+
importConfig.setHeadingsOrder(filteredHeadingsOrder);
|
|
500
512
|
importConfig.setOriginProperties(res.properties);
|
|
513
|
+
|
|
514
|
+
const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
|
|
515
|
+
setFieldValue("properties", res.properties);
|
|
516
|
+
setFieldValue("propertiesOrder", mappedHeadings);
|
|
501
517
|
});
|
|
502
518
|
}
|
|
503
519
|
|
|
@@ -520,7 +536,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
520
536
|
|
|
521
537
|
<>
|
|
522
538
|
{!isNewCollection && <Tabs value={currentView}
|
|
523
|
-
className={
|
|
539
|
+
className={cls(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
|
|
524
540
|
onValueChange={(v) => setCurrentView(v as EditorView)}>
|
|
525
541
|
<Tab value={"details"}>
|
|
526
542
|
Details
|
|
@@ -535,7 +551,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
535
551
|
|
|
536
552
|
<form noValidate
|
|
537
553
|
onSubmit={formController.handleSubmit}
|
|
538
|
-
className={
|
|
554
|
+
className={cls(
|
|
539
555
|
isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
|
|
540
556
|
"flex-grow flex flex-col relative")}>
|
|
541
557
|
|
|
@@ -550,9 +566,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
550
566
|
{currentView === "welcome" &&
|
|
551
567
|
<CollectionEditorWelcomeView
|
|
552
568
|
path={path}
|
|
553
|
-
onContinue={(importData) => {
|
|
569
|
+
onContinue={(importData, propertiesOrder) => {
|
|
570
|
+
// console.log("Import data", importData, propertiesOrder)
|
|
554
571
|
if (importData) {
|
|
555
|
-
onImportDataSet(importData);
|
|
572
|
+
onImportDataSet(importData, propertiesOrder);
|
|
556
573
|
setCurrentView("import_data_mapping");
|
|
557
574
|
} else {
|
|
558
575
|
setCurrentView("details");
|
|
@@ -575,6 +592,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
575
592
|
{currentView === "import_data_saving" && importConfig &&
|
|
576
593
|
<ImportSaveInProgress importConfig={importConfig}
|
|
577
594
|
collection={values}
|
|
595
|
+
path={path}
|
|
578
596
|
onImportSuccess={async (importedCollection) => {
|
|
579
597
|
snackbarController.open({
|
|
580
598
|
type: "info",
|
|
@@ -731,13 +749,21 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
731
749
|
}
|
|
732
750
|
|
|
733
751
|
function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
|
|
734
|
-
const {
|
|
752
|
+
const {
|
|
753
|
+
properties,
|
|
754
|
+
...rest
|
|
755
|
+
} = collection;
|
|
735
756
|
const propertiesResult: PropertiesOrBuilders<any> = {};
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
757
|
+
if (properties) {
|
|
758
|
+
Object.keys(properties).forEach((key) => {
|
|
759
|
+
propertiesResult[key] = applyPropertiesConfig(properties[key] as PropertyOrBuilder, propertyConfigs);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
739
762
|
|
|
740
|
-
return {
|
|
763
|
+
return {
|
|
764
|
+
...rest,
|
|
765
|
+
properties: propertiesResult
|
|
766
|
+
};
|
|
741
767
|
}
|
|
742
768
|
|
|
743
769
|
function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
|
|
@@ -757,7 +783,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
|
|
|
757
783
|
Object.keys(internalProperty.properties).forEach((key) => {
|
|
758
784
|
properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
|
|
759
785
|
});
|
|
760
|
-
internalProperty = {
|
|
786
|
+
internalProperty = {
|
|
787
|
+
...internalProperty,
|
|
788
|
+
properties
|
|
789
|
+
};
|
|
761
790
|
}
|
|
762
791
|
|
|
763
792
|
}
|
|
@@ -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
|
|
|
@@ -188,9 +188,9 @@ export function TemplateButton({
|
|
|
188
188
|
<Tooltip title={subtitle}>
|
|
189
189
|
<Card
|
|
190
190
|
onClick={onClick}
|
|
191
|
-
className={
|
|
191
|
+
className={cls(
|
|
192
192
|
"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-gray-700 dark:text-
|
|
193
|
+
"text-gray-700 dark:text-slate-300",
|
|
194
194
|
"hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
195
195
|
"border-gray-400 dark:border-gray-600 "
|
|
196
196
|
)}
|
|
@@ -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);
|
|
@@ -273,7 +275,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
273
275
|
|
|
274
276
|
};
|
|
275
277
|
|
|
276
|
-
const onPropertyErrorInternal =
|
|
278
|
+
const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
|
|
277
279
|
const propertyPath = id ? getFullId(id, namespace) : undefined;
|
|
278
280
|
console.debug("onPropertyErrorInternal", {
|
|
279
281
|
id,
|
|
@@ -286,7 +288,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
286
288
|
onPropertyError(id, namespace, hasError ? error : undefined);
|
|
287
289
|
setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
|
|
288
290
|
}
|
|
289
|
-
}
|
|
291
|
+
}
|
|
290
292
|
|
|
291
293
|
const closePropertyDialog = () => {
|
|
292
294
|
setSelectedPropertyIndex(undefined);
|
|
@@ -301,9 +303,9 @@ export function CollectionPropertiesEditorForm({
|
|
|
301
303
|
? values.propertiesOrder
|
|
302
304
|
: Object.keys(values.properties)) as string[];
|
|
303
305
|
|
|
304
|
-
const owner = useMemo(() => getUser(values.ownerId), [getUser, values.ownerId]);
|
|
306
|
+
const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
|
|
305
307
|
|
|
306
|
-
const onPropertyClick =
|
|
308
|
+
const onPropertyClick = (propertyKey: string, namespace?: string) => {
|
|
307
309
|
console.debug("CollectionEditor: onPropertyClick", {
|
|
308
310
|
propertyKey,
|
|
309
311
|
namespace
|
|
@@ -311,11 +313,11 @@ export function CollectionPropertiesEditorForm({
|
|
|
311
313
|
setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
|
|
312
314
|
setSelectedPropertyKey(propertyKey);
|
|
313
315
|
setSelectedPropertyNamespace(namespace);
|
|
314
|
-
}
|
|
316
|
+
};
|
|
315
317
|
|
|
316
318
|
const body = (
|
|
317
319
|
<div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
|
|
318
|
-
<div className={
|
|
320
|
+
<div className={cls(
|
|
319
321
|
"p-4 md:p-8 pb-20 md:pb-20",
|
|
320
322
|
"col-span-12 lg:col-span-5 h-full overflow-auto",
|
|
321
323
|
!asDialog && "border-r " + defaultBorderMixin
|
|
@@ -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, } from "@firecms/core";
|
|
4
|
+
import { ArrayContainer, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
|
|
5
5
|
import {
|
|
6
6
|
AutoAwesomeIcon,
|
|
7
7
|
Badge,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
SettingsIcon,
|
|
18
18
|
Typography
|
|
19
19
|
} from "@firecms/ui";
|
|
20
|
-
import { FieldHelperView } from "./properties/FieldHelperView";
|
|
21
20
|
import { extractEnumFromValues } from "@firecms/schema_inference";
|
|
22
21
|
import { Field, Formex, getIn, useCreateFormex, useFormex } from "@firecms/formex";
|
|
23
22
|
|
|
@@ -337,9 +336,9 @@ function EnumEntryDialog({
|
|
|
337
336
|
autoComplete="off"
|
|
338
337
|
error={Boolean(idError)}/>
|
|
339
338
|
|
|
340
|
-
<
|
|
339
|
+
<FieldCaption error={Boolean(idError)}>
|
|
341
340
|
{idError ?? "Value saved in the data source"}
|
|
342
|
-
</
|
|
341
|
+
</FieldCaption>
|
|
343
342
|
</div>}
|
|
344
343
|
</DialogContent>
|
|
345
344
|
|
|
@@ -5,11 +5,17 @@ import JSON5 from "json5";
|
|
|
5
5
|
import { Highlight, themes } from "prism-react-renderer"
|
|
6
6
|
import { camelCase } from "./utils/strings";
|
|
7
7
|
|
|
8
|
-
export function GetCodeDialog({
|
|
8
|
+
export function GetCodeDialog({
|
|
9
|
+
collection,
|
|
10
|
+
onOpenChange,
|
|
11
|
+
open
|
|
12
|
+
}: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
|
|
9
13
|
|
|
10
14
|
const snackbarController = useSnackbarController();
|
|
11
15
|
|
|
12
|
-
const code =
|
|
16
|
+
const code = collection
|
|
17
|
+
? "import { EntityCollection } from \"firecms\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode(collection), null, "\t")
|
|
18
|
+
: "No collection selected";
|
|
13
19
|
return <Dialog open={open}
|
|
14
20
|
onOpenChange={onOpenChange}
|
|
15
21
|
maxWidth={"4xl"}>
|
|
@@ -29,7 +35,13 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
|
|
|
29
35
|
code={code}
|
|
30
36
|
language="typescript"
|
|
31
37
|
>
|
|
32
|
-
{({
|
|
38
|
+
{({
|
|
39
|
+
className,
|
|
40
|
+
style,
|
|
41
|
+
tokens,
|
|
42
|
+
getLineProps,
|
|
43
|
+
getTokenProps
|
|
44
|
+
}) => (
|
|
33
45
|
<pre style={style} className={"p-4 rounded text-sm"}>
|
|
34
46
|
{tokens.map((line, i) => (
|
|
35
47
|
<div key={i} {...getLineProps({ line })}>
|
|
@@ -5,7 +5,7 @@ import { Formex, FormexController, getIn, useCreateFormex } from "@firecms/forme
|
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_FIELD_CONFIGS,
|
|
7
7
|
DeleteConfirmationDialog,
|
|
8
|
-
|
|
8
|
+
PropertyConfigId,
|
|
9
9
|
getFieldConfig,
|
|
10
10
|
getFieldId,
|
|
11
11
|
isPropertyBuilder,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "@firecms/core";
|
|
18
18
|
import {
|
|
19
19
|
Button,
|
|
20
|
-
|
|
20
|
+
cls,
|
|
21
21
|
DeleteIcon,
|
|
22
22
|
Dialog,
|
|
23
23
|
DialogActions,
|
|
@@ -228,6 +228,7 @@ export const PropertyForm = React.memo(
|
|
|
228
228
|
a.includeIdAndName === b.includeIdAndName &&
|
|
229
229
|
a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
|
|
230
230
|
a.autoUpdateId === b.autoUpdateId &&
|
|
231
|
+
a.existingPropertyKeys === b.existingPropertyKeys &&
|
|
231
232
|
a.existingProperty === b.existingProperty
|
|
232
233
|
);
|
|
233
234
|
|
|
@@ -367,15 +368,15 @@ function PropertyEditFormFields({
|
|
|
367
368
|
}
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
|
-
}, [deferredValues, includeIdAndTitle,
|
|
371
|
+
}, [deferredValues, includeIdAndTitle, propertyNamespace]);
|
|
371
372
|
|
|
372
373
|
useEffect(() => {
|
|
373
374
|
if (values?.id && onError) {
|
|
374
375
|
onError(values?.id, propertyNamespace, errors);
|
|
375
376
|
}
|
|
376
|
-
}, [errors,
|
|
377
|
+
}, [errors, propertyNamespace, values?.id]);
|
|
377
378
|
|
|
378
|
-
const onWidgetSelectChanged = (newSelectedWidgetId:
|
|
379
|
+
const onWidgetSelectChanged = (newSelectedWidgetId: PropertyConfigId) => {
|
|
379
380
|
setSelectedFieldConfigId(newSelectedWidgetId);
|
|
380
381
|
setValues(updatePropertyFromWidget(values, newSelectedWidgetId, propertyConfigs));
|
|
381
382
|
// Ugly hack to autofocus the name field
|
|
@@ -495,7 +496,7 @@ function PropertyEditFormFields({
|
|
|
495
496
|
return <em>Select a property
|
|
496
497
|
widget</em>;
|
|
497
498
|
}
|
|
498
|
-
const key = value as
|
|
499
|
+
const key = value as PropertyConfigId;
|
|
499
500
|
const propertyConfig = DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key];
|
|
500
501
|
const baseProperty = propertyConfig.property;
|
|
501
502
|
const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, propertyConfigs) : undefined;
|
|
@@ -508,7 +509,7 @@ function PropertyEditFormFields({
|
|
|
508
509
|
e.preventDefault();
|
|
509
510
|
}
|
|
510
511
|
}}
|
|
511
|
-
className={
|
|
512
|
+
className={cls(
|
|
512
513
|
"flex items-center",
|
|
513
514
|
optionDisabled ? "w-full pointer-events-none opacity-50" : "")}>
|
|
514
515
|
<div className={"mr-8"}>
|
|
@@ -524,7 +525,7 @@ function PropertyEditFormFields({
|
|
|
524
525
|
</div>
|
|
525
526
|
}}
|
|
526
527
|
onValueChange={(value) => {
|
|
527
|
-
onWidgetSelectChanged(value as
|
|
528
|
+
onWidgetSelectChanged(value as PropertyConfigId);
|
|
528
529
|
}}>
|
|
529
530
|
{displayedWidgets.map(([key, propertyConfig]) => {
|
|
530
531
|
const baseProperty = propertyConfig.property;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
cardClickableMixin,
|
|
12
12
|
cardMixin,
|
|
13
13
|
cardSelectedMixin,
|
|
14
|
-
|
|
14
|
+
cls,
|
|
15
15
|
FunctionsIcon,
|
|
16
16
|
Paper,
|
|
17
17
|
RemoveCircleIcon,
|
|
@@ -45,9 +45,6 @@ export function PropertyFieldPreview({
|
|
|
45
45
|
? "border-red-500 dark:border-red-500 border-opacity-100 dark:border-opacity-100 ring-0 dark:ring-0"
|
|
46
46
|
: (selected ? "border-primary" : "border-transparent");
|
|
47
47
|
|
|
48
|
-
if(hasError)
|
|
49
|
-
console.log("PropertyFieldPreview", property)
|
|
50
|
-
|
|
51
48
|
return <ErrorBoundary>
|
|
52
49
|
<div
|
|
53
50
|
onClick={onClick}
|
|
@@ -56,7 +53,7 @@ export function PropertyFieldPreview({
|
|
|
56
53
|
<PropertyConfigBadge propertyConfig={propertyConfig}/>
|
|
57
54
|
</div>
|
|
58
55
|
<Paper
|
|
59
|
-
className={
|
|
56
|
+
className={cls(
|
|
60
57
|
"border",
|
|
61
58
|
"pl-2 w-full flex flex-row gap-4 items-center",
|
|
62
59
|
cardMixin,
|
|
@@ -139,7 +136,7 @@ export function NonEditablePropertyPreview({
|
|
|
139
136
|
<RemoveCircleIcon color={"disabled"} size={"small"} className={"absolute -right-2 -top-2"}/>
|
|
140
137
|
</div>
|
|
141
138
|
<Paper
|
|
142
|
-
className={
|
|
139
|
+
className={cls(
|
|
143
140
|
"pl-2 w-full flex flex-row gap-4 items-center",
|
|
144
141
|
cardMixin,
|
|
145
142
|
onClick ? cardClickableMixin : "",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PropertyConfigBadge, PropertyConfig } from "@firecms/core";
|
|
2
|
-
import {
|
|
2
|
+
import { cls, SelectItem, Typography } from "@firecms/ui";
|
|
3
3
|
|
|
4
4
|
export interface PropertySelectItemProps {
|
|
5
5
|
value: string;
|
|
@@ -13,7 +13,7 @@ export function PropertySelectItem({ value, optionDisabled, propertyConfig, exis
|
|
|
13
13
|
disabled={optionDisabled}
|
|
14
14
|
className={"flex flex-row items-center"}>
|
|
15
15
|
<div
|
|
16
|
-
className={
|
|
16
|
+
className={cls(
|
|
17
17
|
"flex flex-row items-center text-base min-h-[52px]",
|
|
18
18
|
optionDisabled ? "w-full" : "")}>
|
|
19
19
|
<div className={"mr-8"}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import equal from "react-fast-compare"
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -48,7 +48,7 @@ export const PropertyTree = React.memo(
|
|
|
48
48
|
|
|
49
49
|
const propertiesOrder = propertiesOrderProp ?? Object.keys(properties);
|
|
50
50
|
|
|
51
|
-
const onDragEnd =
|
|
51
|
+
const onDragEnd = (result: any) => {
|
|
52
52
|
// dropped outside the list
|
|
53
53
|
if (!result.destination) {
|
|
54
54
|
return;
|
|
@@ -61,7 +61,7 @@ export const PropertyTree = React.memo(
|
|
|
61
61
|
newPropertiesOrder.splice(endIndex, 0, removed);
|
|
62
62
|
if (onPropertyMove)
|
|
63
63
|
onPropertyMove(newPropertiesOrder, namespace);
|
|
64
|
-
}
|
|
64
|
+
}
|
|
65
65
|
|
|
66
66
|
return (
|
|
67
67
|
<>
|