@firecms/collection_editor 3.0.0-canary.14 → 3.0.0-canary.140
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 +4898 -3532
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +6827 -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/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 +15 -9
- 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/ui/CollectionViewHeaderAction.tsx +9 -4
- 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 +9 -4
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +69 -8
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +57 -32
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +6 -5
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +34 -27
- package/src/ui/collection_editor/GetCodeDialog.tsx +15 -3
- package/src/ui/collection_editor/PropertyEditView.tsx +252 -78
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +3 -6
- 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 +31 -19
- 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 +5 -5
- 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 +31 -16
- package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
- package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +1 -1
- package/src/ui/collection_editor/templates/pages_template.ts +1 -6
- package/src/useCollectionEditorPlugin.tsx +37 -27
- 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
|
@@ -3,9 +3,8 @@ import equal from "react-fast-compare"
|
|
|
3
3
|
|
|
4
4
|
import { Formex, FormexController, getIn, useCreateFormex } from "@firecms/formex";
|
|
5
5
|
import {
|
|
6
|
+
ConfirmationDialog,
|
|
6
7
|
DEFAULT_FIELD_CONFIGS,
|
|
7
|
-
DeleteConfirmationDialog,
|
|
8
|
-
PropertyConfigId,
|
|
9
8
|
getFieldConfig,
|
|
10
9
|
getFieldId,
|
|
11
10
|
isPropertyBuilder,
|
|
@@ -14,18 +13,25 @@ import {
|
|
|
14
13
|
Property,
|
|
15
14
|
PropertyConfig,
|
|
16
15
|
PropertyConfigBadge,
|
|
16
|
+
PropertyConfigId,
|
|
17
17
|
} from "@firecms/core";
|
|
18
18
|
import {
|
|
19
19
|
Button,
|
|
20
|
-
|
|
20
|
+
Card,
|
|
21
|
+
cls,
|
|
21
22
|
DeleteIcon,
|
|
22
23
|
Dialog,
|
|
23
24
|
DialogActions,
|
|
24
25
|
DialogContent,
|
|
26
|
+
DialogTitle,
|
|
27
|
+
fieldBackgroundDisabledMixin,
|
|
28
|
+
fieldBackgroundHoverMixin,
|
|
29
|
+
fieldBackgroundMixin,
|
|
25
30
|
IconButton,
|
|
26
31
|
InfoLabel,
|
|
27
|
-
|
|
28
|
-
Typography
|
|
32
|
+
Tooltip,
|
|
33
|
+
Typography,
|
|
34
|
+
WarningOffIcon
|
|
29
35
|
} from "@firecms/ui";
|
|
30
36
|
import { EnumPropertyField } from "./properties/EnumPropertyField";
|
|
31
37
|
import { StoragePropertyField } from "./properties/StoragePropertyField";
|
|
@@ -42,9 +48,9 @@ import { AdvancedPropertyValidation } from "./properties/advanced/AdvancedProper
|
|
|
42
48
|
import { editableProperty } from "../../utils/entities";
|
|
43
49
|
import { KeyValuePropertyField } from "./properties/KeyValuePropertyField";
|
|
44
50
|
import { updatePropertyFromWidget } from "./utils/update_property_for_widget";
|
|
45
|
-
import { PropertySelectItem } from "./PropertySelectItem";
|
|
46
51
|
import { UrlPropertyField } from "./properties/UrlPropertyField";
|
|
47
52
|
import { supportedFields } from "./utils/supported_fields";
|
|
53
|
+
import { MarkdownPropertyField } from "./properties/MarkdownPropertyField";
|
|
48
54
|
|
|
49
55
|
export type PropertyWithId = Property & {
|
|
50
56
|
id?: string
|
|
@@ -68,6 +74,7 @@ export type PropertyFormProps = {
|
|
|
68
74
|
property?: Property;
|
|
69
75
|
onPropertyChanged?: (params: OnPropertyChangedParams) => void;
|
|
70
76
|
onPropertyChangedImmediate?: boolean;
|
|
77
|
+
onDismiss?: () => void;
|
|
71
78
|
onDelete?: (id?: string, namespace?: string) => void;
|
|
72
79
|
onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
|
|
73
80
|
initialErrors?: Record<string, any>;
|
|
@@ -95,6 +102,7 @@ export const PropertyForm = React.memo(
|
|
|
95
102
|
property,
|
|
96
103
|
onPropertyChanged,
|
|
97
104
|
onPropertyChangedImmediate = true,
|
|
105
|
+
onDismiss,
|
|
98
106
|
onDelete,
|
|
99
107
|
onError,
|
|
100
108
|
initialErrors,
|
|
@@ -148,7 +156,10 @@ export const PropertyForm = React.memo(
|
|
|
148
156
|
} = newPropertyWithId;
|
|
149
157
|
doOnPropertyChanged({
|
|
150
158
|
id,
|
|
151
|
-
property: {
|
|
159
|
+
property: {
|
|
160
|
+
...property,
|
|
161
|
+
editable: property.editable ?? true
|
|
162
|
+
}
|
|
152
163
|
});
|
|
153
164
|
if (!existingProperty)
|
|
154
165
|
controller.resetForm({ values: initialValue });
|
|
@@ -209,6 +220,7 @@ export const PropertyForm = React.memo(
|
|
|
209
220
|
includeIdAndTitle={includeIdAndName}
|
|
210
221
|
propertyNamespace={propertyNamespace}
|
|
211
222
|
onError={onError}
|
|
223
|
+
onDismiss={onDismiss}
|
|
212
224
|
showErrors={forceShowErrors || formexController.submitCount > 0}
|
|
213
225
|
existing={existingProperty}
|
|
214
226
|
autoUpdateId={autoUpdateId}
|
|
@@ -228,6 +240,7 @@ export const PropertyForm = React.memo(
|
|
|
228
240
|
a.includeIdAndName === b.includeIdAndName &&
|
|
229
241
|
a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
|
|
230
242
|
a.autoUpdateId === b.autoUpdateId &&
|
|
243
|
+
a.existingPropertyKeys === b.existingPropertyKeys &&
|
|
231
244
|
a.existingProperty === b.existingProperty
|
|
232
245
|
);
|
|
233
246
|
|
|
@@ -263,6 +276,7 @@ export function PropertyFormDialog({
|
|
|
263
276
|
}}>
|
|
264
277
|
<DialogContent>
|
|
265
278
|
<PropertyForm {...formProps}
|
|
279
|
+
onDismiss={onCancel}
|
|
266
280
|
onPropertyChanged={(params) => {
|
|
267
281
|
onPropertyChanged?.(params);
|
|
268
282
|
onOkClicked?.();
|
|
@@ -307,6 +321,7 @@ function PropertyEditFormFields({
|
|
|
307
321
|
onPropertyChanged,
|
|
308
322
|
onDelete,
|
|
309
323
|
propertyNamespace,
|
|
324
|
+
onDismiss,
|
|
310
325
|
onError,
|
|
311
326
|
showErrors,
|
|
312
327
|
disabled,
|
|
@@ -321,6 +336,7 @@ function PropertyEditFormFields({
|
|
|
321
336
|
autoUpdateId?: boolean;
|
|
322
337
|
autoOpenTypeSelect: boolean;
|
|
323
338
|
propertyNamespace?: string;
|
|
339
|
+
onDismiss?: () => void;
|
|
324
340
|
onPropertyChanged?: (params: OnPropertyChangedParams) => void;
|
|
325
341
|
onDelete?: (id?: string, namespace?: string) => void;
|
|
326
342
|
onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
|
|
@@ -337,12 +353,6 @@ function PropertyEditFormFields({
|
|
|
337
353
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
338
354
|
const [selectedFieldConfigId, setSelectedFieldConfigId] = useState<string | undefined>(values?.dataType ? getFieldId(values) : undefined);
|
|
339
355
|
|
|
340
|
-
const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
|
|
341
|
-
|
|
342
|
-
const displayedWidgets = inArray
|
|
343
|
-
? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property) && propertyConfig.property?.dataType !== "array")
|
|
344
|
-
: allSupportedFields;
|
|
345
|
-
|
|
346
356
|
const deferredValues = useDeferredValue(values);
|
|
347
357
|
const nameFieldRef = useRef<HTMLInputElement>(null);
|
|
348
358
|
|
|
@@ -367,13 +377,13 @@ function PropertyEditFormFields({
|
|
|
367
377
|
}
|
|
368
378
|
}
|
|
369
379
|
}
|
|
370
|
-
}, [deferredValues, includeIdAndTitle,
|
|
380
|
+
}, [deferredValues, includeIdAndTitle, propertyNamespace]);
|
|
371
381
|
|
|
372
382
|
useEffect(() => {
|
|
373
383
|
if (values?.id && onError) {
|
|
374
384
|
onError(values?.id, propertyNamespace, errors);
|
|
375
385
|
}
|
|
376
|
-
}, [errors,
|
|
386
|
+
}, [errors, propertyNamespace, values?.id]);
|
|
377
387
|
|
|
378
388
|
const onWidgetSelectChanged = (newSelectedWidgetId: PropertyConfigId) => {
|
|
379
389
|
setSelectedFieldConfigId(newSelectedWidgetId);
|
|
@@ -387,7 +397,6 @@ function PropertyEditFormFields({
|
|
|
387
397
|
let childComponent;
|
|
388
398
|
if (selectedFieldConfigId === "text_field" ||
|
|
389
399
|
selectedFieldConfigId === "multiline" ||
|
|
390
|
-
selectedFieldConfigId === "markdown" ||
|
|
391
400
|
selectedFieldConfigId === "email") {
|
|
392
401
|
childComponent =
|
|
393
402
|
<StringPropertyField widgetId={selectedFieldConfigId}
|
|
@@ -397,6 +406,10 @@ function PropertyEditFormFields({
|
|
|
397
406
|
childComponent =
|
|
398
407
|
<UrlPropertyField disabled={disabled}
|
|
399
408
|
showErrors={showErrors}/>;
|
|
409
|
+
} else if (selectedFieldConfigId === "markdown") {
|
|
410
|
+
childComponent =
|
|
411
|
+
<MarkdownPropertyField disabled={disabled}
|
|
412
|
+
showErrors={showErrors}/>;
|
|
400
413
|
} else if (selectedFieldConfigId === "select" ||
|
|
401
414
|
selectedFieldConfigId === "number_select") {
|
|
402
415
|
childComponent = <EnumPropertyField
|
|
@@ -481,62 +494,22 @@ function PropertyEditFormFields({
|
|
|
481
494
|
|
|
482
495
|
<div className="flex mt-2 justify-between">
|
|
483
496
|
<div className={"w-full flex flex-col gap-2"}>
|
|
484
|
-
<
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
placeholder={"Select a property widget"}
|
|
497
|
+
<WidgetSelectView
|
|
498
|
+
initialProperty={values}
|
|
499
|
+
value={selectedFieldConfigId as PropertyConfigId}
|
|
500
|
+
onValueChange={(value) => onWidgetSelectChanged(value as PropertyConfigId)}
|
|
489
501
|
open={selectOpen}
|
|
490
|
-
onOpenChange={
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
renderValue={(value) => {
|
|
494
|
-
if (!value) {
|
|
495
|
-
return <em>Select a property
|
|
496
|
-
widget</em>;
|
|
502
|
+
onOpenChange={(open, hasValue) => {
|
|
503
|
+
if (!hasValue) {
|
|
504
|
+
onDismiss?.();
|
|
497
505
|
}
|
|
498
|
-
|
|
499
|
-
const propertyConfig = DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key];
|
|
500
|
-
const baseProperty = propertyConfig.property;
|
|
501
|
-
const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, propertyConfigs) : undefined;
|
|
502
|
-
const optionDisabled = isPropertyBuilder(baseProperty) || (existing && baseProperty.dataType !== values?.dataType);
|
|
503
|
-
const computedFieldConfig = baseFieldConfig ? mergeDeep(baseFieldConfig, propertyConfig) : propertyConfig;
|
|
504
|
-
return <div
|
|
505
|
-
onClick={(e) => {
|
|
506
|
-
if (optionDisabled) {
|
|
507
|
-
e.stopPropagation();
|
|
508
|
-
e.preventDefault();
|
|
509
|
-
}
|
|
510
|
-
}}
|
|
511
|
-
className={cn(
|
|
512
|
-
"flex items-center",
|
|
513
|
-
optionDisabled ? "w-full pointer-events-none opacity-50" : "")}>
|
|
514
|
-
<div className={"mr-8"}>
|
|
515
|
-
<PropertyConfigBadge propertyConfig={computedFieldConfig}/>
|
|
516
|
-
</div>
|
|
517
|
-
<div className={"flex flex-col items-start text-base text-left"}>
|
|
518
|
-
<div>{computedFieldConfig.name}</div>
|
|
519
|
-
<Typography variant={"caption"}
|
|
520
|
-
color={"disabled"}>
|
|
521
|
-
{optionDisabled ? "You can only switch to widgets that use the same data type" : computedFieldConfig.description}
|
|
522
|
-
</Typography>
|
|
523
|
-
</div>
|
|
524
|
-
</div>
|
|
506
|
+
setSelectOpen(open);
|
|
525
507
|
}}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
{
|
|
530
|
-
|
|
531
|
-
const optionDisabled = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== values?.dataType;
|
|
532
|
-
return <PropertySelectItem
|
|
533
|
-
key={key}
|
|
534
|
-
value={key}
|
|
535
|
-
optionDisabled={optionDisabled}
|
|
536
|
-
propertyConfig={propertyConfig}
|
|
537
|
-
existing={existing}/>;
|
|
538
|
-
})}
|
|
539
|
-
</Select>
|
|
508
|
+
disabled={disabled}
|
|
509
|
+
showError={Boolean(selectedWidgetError)}
|
|
510
|
+
existing={existing}
|
|
511
|
+
propertyConfigs={propertyConfigs}
|
|
512
|
+
inArray={inArray}/>
|
|
540
513
|
|
|
541
514
|
{selectedWidgetError &&
|
|
542
515
|
<Typography variant="caption"
|
|
@@ -575,15 +548,15 @@ function PropertyEditFormFields({
|
|
|
575
548
|
</div>
|
|
576
549
|
|
|
577
550
|
{onDelete &&
|
|
578
|
-
<
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
551
|
+
<ConfirmationDialog open={deleteDialogOpen}
|
|
552
|
+
onAccept={() => onDelete(values?.id, propertyNamespace)}
|
|
553
|
+
onCancel={() => setDeleteDialogOpen(false)}
|
|
554
|
+
title={<div>Delete this property?</div>}
|
|
555
|
+
body={
|
|
556
|
+
<div> This will <b>not delete any
|
|
557
|
+
data</b>, only modify the
|
|
558
|
+
collection.</div>
|
|
559
|
+
}/>}
|
|
587
560
|
|
|
588
561
|
</>
|
|
589
562
|
);
|
|
@@ -613,3 +586,204 @@ function validateName(value: string) {
|
|
|
613
586
|
}
|
|
614
587
|
return error;
|
|
615
588
|
}
|
|
589
|
+
|
|
590
|
+
const WIDGET_TYPE_MAP: Record<PropertyConfigId, string> = {
|
|
591
|
+
text_field: "Text",
|
|
592
|
+
multiline: "Text",
|
|
593
|
+
markdown: "Text",
|
|
594
|
+
url: "Text",
|
|
595
|
+
email: "Text",
|
|
596
|
+
switch: "Boolean",
|
|
597
|
+
select: "Select",
|
|
598
|
+
multi_select: "Select",
|
|
599
|
+
number_input: "Number",
|
|
600
|
+
number_select: "Select",
|
|
601
|
+
multi_number_select: "Select",
|
|
602
|
+
file_upload: "File",
|
|
603
|
+
multi_file_upload: "File",
|
|
604
|
+
reference: "Reference",
|
|
605
|
+
multi_references: "Reference",
|
|
606
|
+
date_time: "Date",
|
|
607
|
+
group: "Group",
|
|
608
|
+
key_value: "Group",
|
|
609
|
+
repeat: "Array",
|
|
610
|
+
custom_array: "Array",
|
|
611
|
+
block: "Group"
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
function WidgetSelectView({
|
|
615
|
+
initialProperty,
|
|
616
|
+
value,
|
|
617
|
+
onValueChange,
|
|
618
|
+
open,
|
|
619
|
+
onOpenChange,
|
|
620
|
+
disabled,
|
|
621
|
+
showError,
|
|
622
|
+
existing,
|
|
623
|
+
propertyConfigs,
|
|
624
|
+
inArray
|
|
625
|
+
}: {
|
|
626
|
+
initialProperty?: PropertyWithId,
|
|
627
|
+
value?: PropertyConfigId,
|
|
628
|
+
onValueChange: (value: string) => void,
|
|
629
|
+
showError: boolean,
|
|
630
|
+
open: boolean,
|
|
631
|
+
onOpenChange: (open: boolean, hasValue: boolean) => void,
|
|
632
|
+
disabled: boolean,
|
|
633
|
+
existing: boolean,
|
|
634
|
+
propertyConfigs: Record<string, PropertyConfig>,
|
|
635
|
+
inArray?: boolean
|
|
636
|
+
}) {
|
|
637
|
+
|
|
638
|
+
const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
|
|
639
|
+
|
|
640
|
+
const displayedWidgets = (inArray
|
|
641
|
+
? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property) && propertyConfig.property?.dataType !== "array")
|
|
642
|
+
: allSupportedFields)
|
|
643
|
+
.map(([key, propertyConfig]) => ({
|
|
644
|
+
[key]: propertyConfig
|
|
645
|
+
}))
|
|
646
|
+
.reduce((a, b) => {
|
|
647
|
+
return {
|
|
648
|
+
...a,
|
|
649
|
+
...b
|
|
650
|
+
}
|
|
651
|
+
}, {});
|
|
652
|
+
|
|
653
|
+
const key = value;
|
|
654
|
+
const propertyConfig = key ? (DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key]) : undefined;
|
|
655
|
+
const baseProperty = propertyConfig?.property;
|
|
656
|
+
const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, propertyConfigs) : undefined;
|
|
657
|
+
const computedFieldConfig = baseFieldConfig && propertyConfig ? mergeDeep(baseFieldConfig, propertyConfig) : propertyConfig;
|
|
658
|
+
|
|
659
|
+
const groups: string[] = [...new Set(Object.keys(displayedWidgets).map(key => {
|
|
660
|
+
const group = WIDGET_TYPE_MAP[key as PropertyConfigId];
|
|
661
|
+
if (group) {
|
|
662
|
+
return group;
|
|
663
|
+
}
|
|
664
|
+
return "Custom/Other"
|
|
665
|
+
}))];
|
|
666
|
+
|
|
667
|
+
return <>
|
|
668
|
+
<div
|
|
669
|
+
onClick={() => {
|
|
670
|
+
if (!disabled) {
|
|
671
|
+
onOpenChange(!open, Boolean(value));
|
|
672
|
+
}
|
|
673
|
+
}}
|
|
674
|
+
className={cls(
|
|
675
|
+
"select-none rounded-md text-sm p-4",
|
|
676
|
+
fieldBackgroundMixin,
|
|
677
|
+
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
678
|
+
"relative flex items-center",
|
|
679
|
+
)}>
|
|
680
|
+
{!value && <em>Select a property widget</em>}
|
|
681
|
+
{value && computedFieldConfig && <div
|
|
682
|
+
className={cls(
|
|
683
|
+
"flex items-center")}>
|
|
684
|
+
<div className={"mr-8"}>
|
|
685
|
+
<PropertyConfigBadge propertyConfig={computedFieldConfig}/>
|
|
686
|
+
</div>
|
|
687
|
+
<div className={"flex flex-col items-start text-base text-left"}>
|
|
688
|
+
<div>{computedFieldConfig.name}</div>
|
|
689
|
+
<Typography variant={"caption"}
|
|
690
|
+
color={"secondary"}>
|
|
691
|
+
{computedFieldConfig.description}
|
|
692
|
+
</Typography>
|
|
693
|
+
</div>
|
|
694
|
+
</div>}
|
|
695
|
+
</div>
|
|
696
|
+
<Dialog open={open}
|
|
697
|
+
onOpenChange={(open) => onOpenChange(open, Boolean(value))}
|
|
698
|
+
maxWidth={"4xl"}>
|
|
699
|
+
<DialogTitle>
|
|
700
|
+
Select a property widget
|
|
701
|
+
</DialogTitle>
|
|
702
|
+
<DialogContent>
|
|
703
|
+
<div>
|
|
704
|
+
{groups.map(group => {
|
|
705
|
+
return <div key={group} className={"mt-4"}>
|
|
706
|
+
<Typography variant={"label"}>{group}</Typography>
|
|
707
|
+
<div className={"grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 mt-4"}>
|
|
708
|
+
{Object.entries(displayedWidgets).map(([key, propertyConfig]) => {
|
|
709
|
+
const groupKey = WIDGET_TYPE_MAP[key as PropertyConfigId];
|
|
710
|
+
if (groupKey === group) {
|
|
711
|
+
return <WidgetSelectViewItem
|
|
712
|
+
key={key}
|
|
713
|
+
initialProperty={initialProperty}
|
|
714
|
+
onClick={() => {
|
|
715
|
+
onValueChange(key);
|
|
716
|
+
onOpenChange(false, true);
|
|
717
|
+
}}
|
|
718
|
+
propertyConfig={propertyConfig}
|
|
719
|
+
existing={existing}/>;
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
})}
|
|
723
|
+
</div>
|
|
724
|
+
</div>;
|
|
725
|
+
})}
|
|
726
|
+
{/*{displayedWidgets.map(([key, propertyConfig]) => {*/}
|
|
727
|
+
{/* return <WidgetSelectViewItem*/}
|
|
728
|
+
{/* key={key}*/}
|
|
729
|
+
{/* initialProperty={initialProperty}*/}
|
|
730
|
+
{/* onClick={() => {*/}
|
|
731
|
+
{/* onValueChange(key);*/}
|
|
732
|
+
{/* onOpenChange(false);*/}
|
|
733
|
+
{/* }}*/}
|
|
734
|
+
{/* propertyConfig={propertyConfig}*/}
|
|
735
|
+
{/* existing={existing}/>;*/}
|
|
736
|
+
{/*})}*/}
|
|
737
|
+
</div>
|
|
738
|
+
</DialogContent>
|
|
739
|
+
</Dialog>
|
|
740
|
+
</>;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export interface PropertySelectItemProps {
|
|
744
|
+
onClick?: () => void;
|
|
745
|
+
initialProperty?: PropertyWithId;
|
|
746
|
+
propertyConfig: PropertyConfig;
|
|
747
|
+
existing: boolean;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export function WidgetSelectViewItem({
|
|
751
|
+
onClick,
|
|
752
|
+
initialProperty,
|
|
753
|
+
// optionDisabled,
|
|
754
|
+
propertyConfig,
|
|
755
|
+
existing
|
|
756
|
+
}: PropertySelectItemProps) {
|
|
757
|
+
const baseProperty = propertyConfig.property;
|
|
758
|
+
const shouldWarnChangingDataType = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== initialProperty?.dataType;
|
|
759
|
+
|
|
760
|
+
return <Card
|
|
761
|
+
onClick={onClick}
|
|
762
|
+
className={"flex flex-row items-center px-4 py-2"}>
|
|
763
|
+
<div
|
|
764
|
+
className={cls(
|
|
765
|
+
"flex flex-row items-center text-base min-h-[48px]",
|
|
766
|
+
)}>
|
|
767
|
+
<div className={"mr-8"}>
|
|
768
|
+
<PropertyConfigBadge propertyConfig={propertyConfig} disabled={shouldWarnChangingDataType}/>
|
|
769
|
+
</div>
|
|
770
|
+
<div>
|
|
771
|
+
<div className={"flex flex-row gap-2 items-center"}>
|
|
772
|
+
{shouldWarnChangingDataType && <Tooltip
|
|
773
|
+
title={"This widget uses a different data type than the initially selected widget. This can cause errors with existing data."}>
|
|
774
|
+
<WarningOffIcon size="smallest" className={"w-4"}/>
|
|
775
|
+
</Tooltip>}
|
|
776
|
+
<Typography
|
|
777
|
+
color={shouldWarnChangingDataType ? "secondary" : undefined}>{propertyConfig.name}</Typography>
|
|
778
|
+
</div>
|
|
779
|
+
|
|
780
|
+
<Typography variant={"caption"}
|
|
781
|
+
color={"secondary"}
|
|
782
|
+
className={"max-w-sm"}>
|
|
783
|
+
{propertyConfig.description}
|
|
784
|
+
</Typography>
|
|
785
|
+
|
|
786
|
+
</div>
|
|
787
|
+
</div>
|
|
788
|
+
</Card>
|
|
789
|
+
}
|
|
@@ -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,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
|
<>
|
|
@@ -227,7 +227,8 @@ export function PropertyTreeEntry({
|
|
|
227
227
|
<AutoAwesomeIcon size="small" className={"p-2"}/>
|
|
228
228
|
</Tooltip>}
|
|
229
229
|
|
|
230
|
-
{onPropertyRemove && <Tooltip title={"Remove"}
|
|
230
|
+
{onPropertyRemove && <Tooltip title={"Remove"}
|
|
231
|
+
asChild={true}>
|
|
231
232
|
<IconButton size="small"
|
|
232
233
|
color="inherit"
|
|
233
234
|
onClick={() => onPropertyRemove(propertyKey, namespace)}>
|
|
@@ -235,7 +236,8 @@ export function PropertyTreeEntry({
|
|
|
235
236
|
</IconButton>
|
|
236
237
|
</Tooltip>}
|
|
237
238
|
|
|
238
|
-
{onPropertyMove && <Tooltip title={"Move"}
|
|
239
|
+
{onPropertyMove && <Tooltip title={"Move"}
|
|
240
|
+
asChild={true}>
|
|
239
241
|
<IconButton
|
|
240
242
|
component={"span"}
|
|
241
243
|
size="small"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
ConfirmationDialog,
|
|
4
4
|
EntityCollection,
|
|
5
5
|
EntityCustomView,
|
|
6
6
|
resolveEntityView,
|
|
@@ -41,7 +41,7 @@ export function SubcollectionsEditTab({
|
|
|
41
41
|
parentCollection?: EntityCollection,
|
|
42
42
|
configController: CollectionsConfigController;
|
|
43
43
|
collectionInference?: CollectionInference;
|
|
44
|
-
getUser
|
|
44
|
+
getUser?: (uid: string) => User | null;
|
|
45
45
|
parentCollectionIds?: string[];
|
|
46
46
|
}) {
|
|
47
47
|
|
|
@@ -61,7 +61,7 @@ export function SubcollectionsEditTab({
|
|
|
61
61
|
setFieldValue,
|
|
62
62
|
} = useFormex<EntityCollection>();
|
|
63
63
|
|
|
64
|
-
const subcollections = collection.subcollections ?? [];
|
|
64
|
+
const [subcollections, setSubcollections] = React.useState<EntityCollection[]>(collection.subcollections ?? []);
|
|
65
65
|
const resolvedEntityViews = values.entityViews?.filter(e => typeof e === "string")
|
|
66
66
|
.map(e => resolveEntityView(e, contextEntityViews))
|
|
67
67
|
.filter(Boolean) as EntityCustomView[] ?? [];
|
|
@@ -95,7 +95,8 @@ export function SubcollectionsEditTab({
|
|
|
95
95
|
</TableCell>
|
|
96
96
|
<TableCell
|
|
97
97
|
align="right">
|
|
98
|
-
<Tooltip title={"Remove"}
|
|
98
|
+
<Tooltip title={"Remove"}
|
|
99
|
+
asChild={true}>
|
|
99
100
|
<IconButton size="small"
|
|
100
101
|
onClick={(e) => {
|
|
101
102
|
e.preventDefault();
|
|
@@ -135,7 +136,7 @@ export function SubcollectionsEditTab({
|
|
|
135
136
|
{totalEntityViews === 0 &&
|
|
136
137
|
<Alert action={<Button variant="text"
|
|
137
138
|
size={"small"}
|
|
138
|
-
href={"https://firecms.co/docs/
|
|
139
|
+
href={"https://firecms.co/docs/cloud/quickstart"}
|
|
139
140
|
component={"a"}
|
|
140
141
|
rel="noopener noreferrer"
|
|
141
142
|
target="_blank">More info</Button>}>
|
|
@@ -157,7 +158,8 @@ export function SubcollectionsEditTab({
|
|
|
157
158
|
</TableCell>
|
|
158
159
|
<TableCell
|
|
159
160
|
align="right">
|
|
160
|
-
<Tooltip title={"Remove"}
|
|
161
|
+
<Tooltip title={"Remove"}
|
|
162
|
+
asChild={true}>
|
|
161
163
|
<IconButton size="small"
|
|
162
164
|
onClick={(e) => {
|
|
163
165
|
e.preventDefault();
|
|
@@ -209,30 +211,32 @@ export function SubcollectionsEditTab({
|
|
|
209
211
|
<div style={{ height: "52px" }}/>
|
|
210
212
|
|
|
211
213
|
{subcollectionToDelete &&
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
+
<ConfirmationDialog open={Boolean(subcollectionToDelete)}
|
|
215
|
+
onAccept={() => {
|
|
214
216
|
const props = {
|
|
215
217
|
id: subcollectionToDelete,
|
|
216
218
|
parentCollectionIds: [...(parentCollectionIds ?? []), collection.id]
|
|
217
219
|
};
|
|
218
220
|
console.debug("Deleting subcollection", props)
|
|
219
|
-
configController.deleteCollection(props)
|
|
220
|
-
|
|
221
|
+
configController.deleteCollection(props).then(() => {
|
|
222
|
+
setSubcollectionToDelete(undefined);
|
|
223
|
+
setSubcollections(subcollections?.filter(e => e.id !== subcollectionToDelete))
|
|
224
|
+
});
|
|
221
225
|
}}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
226
|
+
onCancel={() => setSubcollectionToDelete(undefined)}
|
|
227
|
+
title={<>Delete this subcollection?</>}
|
|
228
|
+
body={<> This will <b>not
|
|
225
229
|
delete any data</b>, only
|
|
226
230
|
the collection in the CMS</>}/>}
|
|
227
231
|
{viewToDelete &&
|
|
228
|
-
<
|
|
229
|
-
|
|
232
|
+
<ConfirmationDialog open={Boolean(viewToDelete)}
|
|
233
|
+
onAccept={() => {
|
|
230
234
|
setFieldValue("entityViews", values.entityViews?.filter(e => e !== viewToDelete));
|
|
231
235
|
setViewToDelete(undefined);
|
|
232
236
|
}}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
onCancel={() => setViewToDelete(undefined)}
|
|
238
|
+
title={<>Remove this view?</>}
|
|
239
|
+
body={<>This will <b>not
|
|
236
240
|
delete any data</b>, only
|
|
237
241
|
the view in the CMS</>}/>}
|
|
238
242
|
|
|
@@ -245,7 +249,10 @@ export function SubcollectionsEditTab({
|
|
|
245
249
|
isNewCollection={false}
|
|
246
250
|
{...currentDialog}
|
|
247
251
|
getUser={getUser}
|
|
248
|
-
handleClose={() => {
|
|
252
|
+
handleClose={(updatedCollection) => {
|
|
253
|
+
if (updatedCollection && !subcollections.map(e => e.id).includes(updatedCollection.id)) {
|
|
254
|
+
setSubcollections([...subcollections, updatedCollection]);
|
|
255
|
+
}
|
|
249
256
|
setCurrentDialog(undefined);
|
|
250
257
|
}}/>
|
|
251
258
|
|