@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.
Files changed (62) hide show
  1. package/LICENSE +114 -21
  2. package/dist/ConfigControllerProvider.d.ts +11 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.es.js +4898 -3532
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +6827 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +14 -2
  9. package/dist/types/collection_inference.d.ts +1 -1
  10. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  11. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  12. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  13. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +4 -3
  14. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  15. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  16. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  17. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  18. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  19. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  20. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  21. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  22. package/dist/useCollectionEditorPlugin.d.ts +15 -9
  23. package/dist/utils/collections.d.ts +6 -0
  24. package/package.json +21 -35
  25. package/src/ConfigControllerProvider.tsx +75 -63
  26. package/src/index.ts +1 -0
  27. package/src/types/collection_editor_controller.tsx +14 -4
  28. package/src/types/collection_inference.ts +1 -1
  29. package/src/ui/CollectionViewHeaderAction.tsx +9 -4
  30. package/src/ui/EditorCollectionAction.tsx +10 -63
  31. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  32. package/src/ui/HomePageEditorCollectionAction.tsx +18 -13
  33. package/src/ui/NewCollectionButton.tsx +12 -10
  34. package/src/ui/NewCollectionCard.tsx +3 -3
  35. package/src/ui/PropertyAddColumnComponent.tsx +9 -4
  36. package/src/ui/collection_editor/CollectionDetailsForm.tsx +69 -8
  37. package/src/ui/collection_editor/CollectionEditorDialog.tsx +57 -32
  38. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +6 -5
  39. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +34 -27
  40. package/src/ui/collection_editor/GetCodeDialog.tsx +15 -3
  41. package/src/ui/collection_editor/PropertyEditView.tsx +252 -78
  42. package/src/ui/collection_editor/PropertyFieldPreview.tsx +3 -6
  43. package/src/ui/collection_editor/PropertyTree.tsx +7 -5
  44. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  45. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +25 -9
  46. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +40 -9
  47. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +31 -19
  48. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +50 -47
  49. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +1 -1
  50. package/src/ui/collection_editor/properties/MapPropertyField.tsx +5 -5
  51. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  52. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  53. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +31 -16
  54. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  55. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +1 -1
  56. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  57. package/src/useCollectionEditorPlugin.tsx +37 -27
  58. package/src/utils/collections.ts +30 -0
  59. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  60. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  61. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  62. 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
- cn,
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
- Select,
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: { ...property, editable: property.editable ?? true }
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, onPropertyChanged, propertyNamespace]);
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, onError, propertyNamespace, values?.id]);
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
- <Select
485
- // className={"w-full"}
486
- error={Boolean(selectedWidgetError)}
487
- value={selectedFieldConfigId ?? ""}
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={setSelectOpen}
491
- position={"item-aligned"}
492
- disabled={disabled}
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
- const key = value as PropertyConfigId;
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
- onValueChange={(value) => {
527
- onWidgetSelectChanged(value as PropertyConfigId);
528
- }}>
529
- {displayedWidgets.map(([key, propertyConfig]) => {
530
- const baseProperty = propertyConfig.property;
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
- <DeleteConfirmationDialog open={deleteDialogOpen}
579
- onAccept={() => onDelete(values?.id, propertyNamespace)}
580
- onCancel={() => setDeleteDialogOpen(false)}
581
- title={<div>Delete this property?</div>}
582
- body={
583
- <div> This will <b>not delete any
584
- data</b>, only modify the
585
- collection.</div>
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
- cn,
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={cn(
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={cn(
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, { useCallback } from "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 = useCallback((result: any) => {
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
- }, [namespace, onPropertyMove, propertiesOrder])
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
- DeleteConfirmationDialog,
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: (uid: string) => User | null;
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/customization_quickstart"}
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
- <DeleteConfirmationDialog open={Boolean(subcollectionToDelete)}
213
- onAccept={() => {
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
- setSubcollectionToDelete(undefined);
221
+ configController.deleteCollection(props).then(() => {
222
+ setSubcollectionToDelete(undefined);
223
+ setSubcollections(subcollections?.filter(e => e.id !== subcollectionToDelete))
224
+ });
221
225
  }}
222
- onCancel={() => setSubcollectionToDelete(undefined)}
223
- title={<>Delete this subcollection?</>}
224
- body={<> This will <b>not
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
- <DeleteConfirmationDialog open={Boolean(viewToDelete)}
229
- onAccept={() => {
232
+ <ConfirmationDialog open={Boolean(viewToDelete)}
233
+ onAccept={() => {
230
234
  setFieldValue("entityViews", values.entityViews?.filter(e => e !== viewToDelete));
231
235
  setViewToDelete(undefined);
232
236
  }}
233
- onCancel={() => setViewToDelete(undefined)}
234
- title={<>Remove this view?</>}
235
- body={<>This will <b>not
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