@firecms/collection_editor 3.0.0-canary.16 → 3.0.0-canary.160
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 +9850 -4751
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10559 -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 +23 -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 +74 -9
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +63 -34
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +37 -34
- package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
- package/src/ui/collection_editor/EnumForm.tsx +7 -3
- package/src/ui/collection_editor/GetCodeDialog.tsx +55 -25
- package/src/ui/collection_editor/PropertyEditView.tsx +257 -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/UnsavedChangesDialog.tsx +3 -5
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +25 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -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/ReferencePropertyField.tsx +2 -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/UrlPropertyField.tsx +1 -0
- 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,12 +26,13 @@ import {
|
|
|
25
26
|
import {
|
|
26
27
|
ArrowBackIcon,
|
|
27
28
|
Button,
|
|
28
|
-
|
|
29
|
+
cls,
|
|
29
30
|
coolIconKeys,
|
|
30
31
|
defaultBorderMixin,
|
|
31
32
|
Dialog,
|
|
32
33
|
DialogActions,
|
|
33
34
|
DialogContent,
|
|
35
|
+
DialogTitle,
|
|
34
36
|
DoneIcon,
|
|
35
37
|
IconButton,
|
|
36
38
|
LoadingButton,
|
|
@@ -76,9 +78,10 @@ export interface CollectionEditorDialogProps {
|
|
|
76
78
|
icon: React.ReactNode
|
|
77
79
|
};
|
|
78
80
|
pathSuggestions?: (path?: string) => Promise<string[]>;
|
|
79
|
-
getUser
|
|
81
|
+
getUser?: (uid: string) => User | null;
|
|
80
82
|
getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
|
|
81
83
|
parentCollection?: PersistedCollection;
|
|
84
|
+
existingEntities?: Entity<any>[];
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
@@ -88,13 +91,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
88
91
|
const [formDirty, setFormDirty] = React.useState<boolean>(false);
|
|
89
92
|
const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
|
|
90
93
|
|
|
91
|
-
const handleCancel =
|
|
94
|
+
const handleCancel = () => {
|
|
92
95
|
if (!formDirty) {
|
|
93
96
|
props.handleClose(undefined);
|
|
94
97
|
} else {
|
|
95
98
|
setUnsavedChangesDialogOpen(true);
|
|
96
99
|
}
|
|
97
|
-
}
|
|
100
|
+
};
|
|
98
101
|
|
|
99
102
|
useEffect(() => {
|
|
100
103
|
if (!open) {
|
|
@@ -112,6 +115,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
112
115
|
maxWidth={"7xl"}
|
|
113
116
|
onOpenChange={(open) => !open ? handleCancel() : undefined}
|
|
114
117
|
>
|
|
118
|
+
<DialogTitle hidden>Collection editor</DialogTitle>
|
|
115
119
|
{open && <CollectionEditor {...props}
|
|
116
120
|
handleCancel={handleCancel}
|
|
117
121
|
setFormDirty={setFormDirty}/>}
|
|
@@ -136,7 +140,7 @@ type EditorView = "welcome"
|
|
|
136
140
|
| "extra_view"
|
|
137
141
|
| "subcollections";
|
|
138
142
|
|
|
139
|
-
export function CollectionEditor
|
|
143
|
+
export function CollectionEditor(props: CollectionEditorDialogProps & {
|
|
140
144
|
handleCancel: () => void,
|
|
141
145
|
setFormDirty: (dirty: boolean) => void
|
|
142
146
|
}) {
|
|
@@ -154,14 +158,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
154
158
|
const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
|
|
155
159
|
const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
|
|
156
160
|
const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
|
|
157
|
-
const [collection, setCollection] = React.useState<PersistedCollection<
|
|
161
|
+
const [collection, setCollection] = React.useState<PersistedCollection<any> | undefined>();
|
|
158
162
|
const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
|
|
159
163
|
|
|
160
164
|
useEffect(() => {
|
|
161
165
|
try {
|
|
162
166
|
if (navigation.initialised) {
|
|
163
167
|
if (props.editedCollectionId) {
|
|
164
|
-
setCollection(navigation.getCollectionFromPaths
|
|
168
|
+
setCollection(navigation.getCollectionFromPaths([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
|
|
165
169
|
} else {
|
|
166
170
|
setCollection(undefined);
|
|
167
171
|
}
|
|
@@ -170,7 +174,8 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
170
174
|
} catch (e) {
|
|
171
175
|
console.error(e);
|
|
172
176
|
}
|
|
173
|
-
}, [
|
|
177
|
+
}, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
|
|
178
|
+
|
|
174
179
|
if (!topLevelNavigation) {
|
|
175
180
|
throw Error("Internal: Navigation not ready in collection editor");
|
|
176
181
|
}
|
|
@@ -186,14 +191,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
186
191
|
}
|
|
187
192
|
: undefined;
|
|
188
193
|
|
|
189
|
-
const initialValues: PersistedCollection<
|
|
194
|
+
const initialValues: PersistedCollection<any> = initialCollection
|
|
190
195
|
? applyPropertyConfigs(initialCollection, propertyConfigs)
|
|
191
196
|
: {
|
|
192
197
|
id: initialValuesProp?.path ?? randomString(16),
|
|
193
198
|
path: initialValuesProp?.path ?? "",
|
|
194
199
|
name: initialValuesProp?.name ?? "",
|
|
195
200
|
group: initialValuesProp?.group ?? "",
|
|
196
|
-
properties: {} as PropertiesOrBuilders
|
|
201
|
+
properties: {} as PropertiesOrBuilders,
|
|
197
202
|
propertiesOrder: [],
|
|
198
203
|
icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
|
|
199
204
|
ownerId: authController.user?.uid ?? ""
|
|
@@ -243,7 +248,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
243
248
|
setCollection,
|
|
244
249
|
initialValues,
|
|
245
250
|
propertyConfigs,
|
|
246
|
-
groups
|
|
251
|
+
groups,
|
|
252
|
+
existingEntities
|
|
247
253
|
}: CollectionEditorDialogProps & {
|
|
248
254
|
handleCancel: () => void,
|
|
249
255
|
setFormDirty: (dirty: boolean) => void,
|
|
@@ -272,6 +278,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
272
278
|
|
|
273
279
|
const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
|
|
274
280
|
const id = updatedCollection.id || updatedCollection.path;
|
|
281
|
+
|
|
275
282
|
return configController.saveCollection({
|
|
276
283
|
id,
|
|
277
284
|
collectionData: updatedCollection,
|
|
@@ -293,7 +300,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
293
300
|
});
|
|
294
301
|
};
|
|
295
302
|
|
|
296
|
-
const setNextMode =
|
|
303
|
+
const setNextMode = () => {
|
|
297
304
|
if (currentView === "details") {
|
|
298
305
|
if (importConfig.inUse) {
|
|
299
306
|
setCurrentView("import_data_saving");
|
|
@@ -314,14 +321,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
314
321
|
setCurrentView("details");
|
|
315
322
|
}
|
|
316
323
|
|
|
317
|
-
}
|
|
324
|
+
};
|
|
318
325
|
|
|
319
|
-
const doCollectionInference =
|
|
326
|
+
const doCollectionInference = (collection: PersistedCollection<any>) => {
|
|
320
327
|
if (!collectionInference) return undefined;
|
|
321
|
-
return collectionInference?.(collection.path, collection.collectionGroup ?? false,
|
|
322
|
-
}
|
|
328
|
+
return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
|
|
329
|
+
};
|
|
323
330
|
|
|
324
|
-
const inferCollectionFromData =
|
|
331
|
+
const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
|
|
325
332
|
|
|
326
333
|
try {
|
|
327
334
|
if (!doCollectionInference) {
|
|
@@ -365,15 +372,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
365
372
|
});
|
|
366
373
|
return newCollection;
|
|
367
374
|
}
|
|
368
|
-
}
|
|
375
|
+
};
|
|
369
376
|
|
|
370
377
|
const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
|
|
371
|
-
console.
|
|
378
|
+
console.debug("Submitting collection", newCollectionState);
|
|
372
379
|
try {
|
|
373
380
|
|
|
374
381
|
if (!isNewCollection) {
|
|
375
382
|
saveCollection(newCollectionState).then(() => {
|
|
376
|
-
formexController.resetForm(
|
|
383
|
+
formexController.resetForm();
|
|
377
384
|
handleClose(newCollectionState);
|
|
378
385
|
});
|
|
379
386
|
return;
|
|
@@ -462,7 +469,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
462
469
|
const formController = useCreateFormex<PersistedCollection<M>>({
|
|
463
470
|
initialValues,
|
|
464
471
|
onSubmit,
|
|
465
|
-
validation
|
|
472
|
+
validation,
|
|
473
|
+
debugId: "COLLECTION_EDITOR"
|
|
466
474
|
});
|
|
467
475
|
|
|
468
476
|
const {
|
|
@@ -480,25 +488,36 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
480
488
|
|
|
481
489
|
const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
|
|
482
490
|
const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
|
|
483
|
-
const getDataWithPath = resolvedPath && getData ? () =>
|
|
491
|
+
const getDataWithPath = resolvedPath && getData ? async () => {
|
|
492
|
+
const data = await getData(resolvedPath, parentPaths ?? []);
|
|
493
|
+
if (existingEntities) {
|
|
494
|
+
const existingData = existingEntities.map(e => e.values);
|
|
495
|
+
data.push(...existingData);
|
|
496
|
+
}
|
|
497
|
+
return data;
|
|
498
|
+
} : undefined;
|
|
484
499
|
|
|
485
500
|
useEffect(() => {
|
|
486
501
|
setFormDirty(dirty);
|
|
487
502
|
}, [dirty]);
|
|
488
503
|
|
|
489
|
-
function onImportDataSet(data: object[]) {
|
|
504
|
+
function onImportDataSet(data: object[], propertiesOrder?: string[]) {
|
|
490
505
|
importConfig.setInUse(true);
|
|
491
506
|
buildEntityPropertiesFromData(data, getInferenceType)
|
|
492
507
|
.then((properties) => {
|
|
493
508
|
const res = cleanPropertiesFromImport(properties);
|
|
494
509
|
|
|
495
|
-
setFieldValue("properties", res.properties);
|
|
496
|
-
setFieldValue("propertiesOrder", Object.keys(res.properties));
|
|
497
|
-
|
|
498
510
|
importConfig.setIdColumn(res.idColumn);
|
|
499
511
|
importConfig.setImportData(data);
|
|
500
512
|
importConfig.setHeadersMapping(res.headersMapping);
|
|
513
|
+
const filteredHeadingsOrder = ((propertiesOrder ?? [])
|
|
514
|
+
.filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
|
|
515
|
+
importConfig.setHeadingsOrder(filteredHeadingsOrder);
|
|
501
516
|
importConfig.setOriginProperties(res.properties);
|
|
517
|
+
|
|
518
|
+
const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
|
|
519
|
+
setFieldValue("properties", res.properties);
|
|
520
|
+
setFieldValue("propertiesOrder", mappedHeadings);
|
|
502
521
|
});
|
|
503
522
|
}
|
|
504
523
|
|
|
@@ -521,7 +540,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
521
540
|
|
|
522
541
|
<>
|
|
523
542
|
{!isNewCollection && <Tabs value={currentView}
|
|
524
|
-
|
|
543
|
+
innerClassName={cls(defaultBorderMixin, "justify-end bg-surface-50 dark:bg-surface-950 border-b")}
|
|
525
544
|
onValueChange={(v) => setCurrentView(v as EditorView)}>
|
|
526
545
|
<Tab value={"details"}>
|
|
527
546
|
Details
|
|
@@ -536,7 +555,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
536
555
|
|
|
537
556
|
<form noValidate
|
|
538
557
|
onSubmit={formController.handleSubmit}
|
|
539
|
-
className={
|
|
558
|
+
className={cls(
|
|
540
559
|
isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
|
|
541
560
|
"flex-grow flex flex-col relative")}>
|
|
542
561
|
|
|
@@ -551,9 +570,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
551
570
|
{currentView === "welcome" &&
|
|
552
571
|
<CollectionEditorWelcomeView
|
|
553
572
|
path={path}
|
|
554
|
-
onContinue={(importData) => {
|
|
573
|
+
onContinue={(importData, propertiesOrder) => {
|
|
574
|
+
// console.log("Import data", importData, propertiesOrder)
|
|
555
575
|
if (importData) {
|
|
556
|
-
onImportDataSet(importData);
|
|
576
|
+
onImportDataSet(importData, propertiesOrder);
|
|
557
577
|
setCurrentView("import_data_mapping");
|
|
558
578
|
} else {
|
|
559
579
|
setCurrentView("details");
|
|
@@ -733,7 +753,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
733
753
|
}
|
|
734
754
|
|
|
735
755
|
function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
|
|
736
|
-
const {
|
|
756
|
+
const {
|
|
757
|
+
properties,
|
|
758
|
+
...rest
|
|
759
|
+
} = collection;
|
|
737
760
|
const propertiesResult: PropertiesOrBuilders<any> = {};
|
|
738
761
|
if (properties) {
|
|
739
762
|
Object.keys(properties).forEach((key) => {
|
|
@@ -741,7 +764,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
|
|
|
741
764
|
});
|
|
742
765
|
}
|
|
743
766
|
|
|
744
|
-
return {
|
|
767
|
+
return {
|
|
768
|
+
...rest,
|
|
769
|
+
properties: propertiesResult
|
|
770
|
+
};
|
|
745
771
|
}
|
|
746
772
|
|
|
747
773
|
function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
|
|
@@ -761,7 +787,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
|
|
|
761
787
|
Object.keys(internalProperty.properties).forEach((key) => {
|
|
762
788
|
properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
|
|
763
789
|
});
|
|
764
|
-
internalProperty = {
|
|
790
|
+
internalProperty = {
|
|
791
|
+
...internalProperty,
|
|
792
|
+
properties
|
|
793
|
+
};
|
|
765
794
|
}
|
|
766
795
|
|
|
767
796
|
}
|
|
@@ -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,8 +1,11 @@
|
|
|
1
1
|
import { useCustomizationController } from "@firecms/core";
|
|
2
|
-
import { Button, Dialog, DialogActions, DialogContent, Typography } from "@firecms/ui";
|
|
2
|
+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
|
-
export function EntityCustomViewsSelectDialog({
|
|
5
|
+
export function EntityCustomViewsSelectDialog({
|
|
6
|
+
open,
|
|
7
|
+
onClose
|
|
8
|
+
}: { open: boolean, onClose: (selectedViewKey?: string) => void }) {
|
|
6
9
|
const {
|
|
7
10
|
entityViews,
|
|
8
11
|
} = useCustomizationController();
|
|
@@ -10,10 +13,8 @@ export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean
|
|
|
10
13
|
return <Dialog
|
|
11
14
|
maxWidth={"md"}
|
|
12
15
|
open={open}>
|
|
16
|
+
<DialogTitle>Select custom view</DialogTitle>
|
|
13
17
|
<DialogContent className={"flex flex-col gap-4"}>
|
|
14
|
-
<Typography variant={"h6"}>
|
|
15
|
-
Select view
|
|
16
|
-
</Typography>
|
|
17
18
|
{entityViews?.map((view) => {
|
|
18
19
|
return <Button
|
|
19
20
|
key={view.key}
|
|
@@ -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,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Dialog,
|
|
12
12
|
DialogActions,
|
|
13
13
|
DialogContent,
|
|
14
|
+
DialogTitle,
|
|
14
15
|
IconButton,
|
|
15
16
|
ListIcon,
|
|
16
17
|
Paper,
|
|
@@ -121,7 +122,10 @@ function EnumFormFields({
|
|
|
121
122
|
const inferredValuesRef = React.useRef(new Set());
|
|
122
123
|
const inferredValues = inferredValuesRef.current;
|
|
123
124
|
|
|
124
|
-
const buildEntry = (
|
|
125
|
+
const buildEntry = ({
|
|
126
|
+
index,
|
|
127
|
+
internalId
|
|
128
|
+
}:ArrayEntryParams) => {
|
|
125
129
|
const justAdded = lastInternalIdAdded === internalId;
|
|
126
130
|
const entryError = errors?.enumValues && errors?.enumValues[index];
|
|
127
131
|
return <EnumEntry index={index}
|
|
@@ -324,7 +328,7 @@ function EnumEntryDialog({
|
|
|
324
328
|
open={open}
|
|
325
329
|
onOpenChange={(open) => !open ? onClose() : undefined}
|
|
326
330
|
>
|
|
327
|
-
|
|
331
|
+
<DialogTitle hidden>Enum form dialog</DialogTitle>
|
|
328
332
|
<DialogContent>
|
|
329
333
|
{index !== undefined &&
|
|
330
334
|
<div>
|