@firecms/collection_editor 3.0.0-canary.102 → 3.0.0-canary.103

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.
@@ -33,5 +33,14 @@ export interface ConfigControllerProviderProps {
33
33
  getUser?: (uid: string) => User | null;
34
34
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
35
35
  onAnalyticsEvent?: (event: string, params?: object) => void;
36
+ components?: {
37
+ /**
38
+ * Custom component to render the database field
39
+ */
40
+ DatabaseField?: React.ComponentType<{
41
+ databaseId?: string;
42
+ onDatabaseIdUpdate: (databaseId: string) => void;
43
+ }>;
44
+ };
36
45
  }
37
46
  export declare const ConfigControllerProvider: React.NamedExoticComponent<React.PropsWithChildren<ConfigControllerProviderProps>>;
package/dist/index.es.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { FieldCaption, SearchIconsView, toSnakeCase, singular, IconForView, ArrayContainer, serializeRegExp, useSnackbarController, resolveEnumValues, isPropertyBuilder, useCustomizationController, getFieldConfig, ErrorBoundary, PropertyConfigBadge, unslugify, useNavigationController, mergeDeep, DEFAULT_FIELD_CONFIGS, isValidRegExp, getFieldId, DeleteConfirmationDialog, useLargeLayout, makePropertiesEditable, resolveEntityView, useSelectionController, CircularProgressCenter, EntityCollectionTable, slugify, useAuthController, randomString, removeUndefined, ErrorView, removeInitialAndTrailingSlashes, getDefaultPropertiesOrder, joinCollectionLists } from "@firecms/core";
3
3
  import * as React from "react";
4
- import React__default, { useState, useEffect, useMemo, useRef, useDeferredValue, useCallback, useContext } from "react";
4
+ import React__default, { useContext, useState, useEffect, useMemo, useRef, useDeferredValue, useCallback } from "react";
5
5
  import equal from "react-fast-compare";
6
6
  import { useAutoComplete, Container, Typography, Tooltip, IconButton, Chip, TextField, cls, DebouncedTextField, Autocomplete, AutocompleteItem, ExpandablePanel, SettingsIcon, ClearIcon, Select, SelectItem, BooleanSwitchWithLabel, Dialog, AutoAwesomeIcon, Badge, ListIcon, Button, CircularProgress, Paper, DialogContent, DialogActions, RuleIcon, FileUploadIcon, MultiSelect, MultiSelectItem, Checkbox, cardMixin, cardClickableMixin, cardSelectedMixin, FunctionsIcon, RemoveCircleIcon, defaultBorderMixin, RemoveIcon, DragHandleIcon, AddIcon, SelectGroup, InfoLabel, DeleteIcon, ContentCopyIcon, CodeIcon, Table, TableBody, TableRow, TableCell, Alert, Icon, Card, coolIconKeys, Tabs, Tab, ArrowBackIcon, LoadingButton, DoneIcon, Menu, MoreVertIcon, MenuItem, SaveIcon, UndoIcon } from "@firecms/ui";
7
7
  import * as Yup from "yup";
@@ -17,6 +17,7 @@ const YupSchema = Yup.object().shape({
17
17
  name: Yup.string().required("Required"),
18
18
  path: Yup.string().required("Required")
19
19
  });
20
+ const useCollectionEditorController = () => useContext(CollectionEditorContext);
20
21
  function CollectionDetailsForm({
21
22
  isNewCollection,
22
23
  reservedGroups,
@@ -36,8 +37,12 @@ function CollectionDetailsForm({
36
37
  isSubmitting,
37
38
  submitCount
38
39
  } = useFormex();
40
+ const collectionEditor = useCollectionEditorController();
39
41
  const [iconDialogOpen, setIconDialogOpen] = useState(false);
40
42
  const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
43
+ const updateDatabaseId = (databaseId) => {
44
+ setFieldValue("databaseId", databaseId ?? void 0);
45
+ };
41
46
  const updateName = (name) => {
42
47
  setFieldValue("name", name);
43
48
  const pathTouched = getIn(touched, "path");
@@ -58,6 +63,7 @@ function CollectionDetailsForm({
58
63
  setAdvancedPanelExpanded(true);
59
64
  }
60
65
  }, [errors.id]);
66
+ const DatabaseField = collectionEditor.components?.DatabaseField ?? DefaultDatabaseField;
61
67
  const collectionIcon = /* @__PURE__ */ jsx(IconForView, { collectionOrView: values });
62
68
  const groupOptions = groups?.filter((group) => !reservedGroups?.includes(group));
63
69
  const {
@@ -84,9 +90,16 @@ function CollectionDetailsForm({
84
90
  /* @__PURE__ */ jsxs(
85
91
  "div",
86
92
  {
87
- className: "flex flex-row py-2 pt-3 items-center",
93
+ className: "flex flex-row gap-2 py-2 pt-3 items-center",
88
94
  children: [
89
95
  /* @__PURE__ */ jsx(Typography, { variant: !isNewCollection ? "h5" : "h4", className: "flex-grow", children: isNewCollection ? "New collection" : `${values?.name} collection` }),
96
+ /* @__PURE__ */ jsx(
97
+ DatabaseField,
98
+ {
99
+ databaseId: values.databaseId,
100
+ onDatabaseIdUpdate: updateDatabaseId
101
+ }
102
+ ),
90
103
  /* @__PURE__ */ jsx(Tooltip, { title: "Change icon", children: /* @__PURE__ */ jsx(
91
104
  IconButton,
92
105
  {
@@ -165,7 +178,7 @@ function CollectionDetailsForm({
165
178
  })
166
179
  }
167
180
  ),
168
- /* @__PURE__ */ jsx(FieldCaption, { children: showErrors && Boolean(errors.group) ? errors.group : "Group of the collection" })
181
+ /* @__PURE__ */ jsx(FieldCaption, { children: showErrors && Boolean(errors.group) ? errors.group : "Group in the home page" })
169
182
  ] }),
170
183
  /* @__PURE__ */ jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsx(
171
184
  ExpandablePanel,
@@ -369,6 +382,30 @@ function CollectionDetailsForm({
369
382
  )
370
383
  ] }) });
371
384
  }
385
+ function DefaultDatabaseField({
386
+ databaseId,
387
+ onDatabaseIdUpdate
388
+ }) {
389
+ return /* @__PURE__ */ jsx(
390
+ Tooltip,
391
+ {
392
+ title: "Database ID",
393
+ side: "top",
394
+ align: "start",
395
+ children: /* @__PURE__ */ jsx(
396
+ TextField,
397
+ {
398
+ size: "smallest",
399
+ invisible: true,
400
+ inputClassName: "text-end",
401
+ value: databaseId ?? "",
402
+ onChange: (e) => onDatabaseIdUpdate(e.target.value),
403
+ placeholder: "(default)"
404
+ }
405
+ )
406
+ }
407
+ );
408
+ }
372
409
  function idToPropertiesPath(id) {
373
410
  return "properties." + id.replaceAll(".", ".properties.");
374
411
  }
@@ -1953,19 +1990,6 @@ function StringPropertyField({
1953
1990
  showErrors
1954
1991
  }
1955
1992
  ),
1956
- widgetId === "markdown" && /* @__PURE__ */ jsx(
1957
- StringPropertyValidation,
1958
- {
1959
- disabled,
1960
- length: true,
1961
- lowercase: true,
1962
- max: true,
1963
- min: true,
1964
- trim: true,
1965
- uppercase: true,
1966
- showErrors
1967
- }
1968
- ),
1969
1993
  widgetId === "email" && /* @__PURE__ */ jsx(
1970
1994
  StringPropertyValidation,
1971
1995
  {
@@ -2776,7 +2800,7 @@ function PropertySelectItem({ value, optionDisabled, propertyConfig, existing })
2776
2800
  "div",
2777
2801
  {
2778
2802
  className: cls(
2779
- "flex flex-row items-center text-base min-h-[52px]",
2803
+ "flex flex-row items-center text-base min-h-[48px]",
2780
2804
  optionDisabled ? "w-full" : ""
2781
2805
  ),
2782
2806
  children: [
@@ -2886,6 +2910,121 @@ const supportedFieldsIds = [
2886
2910
  "block"
2887
2911
  ];
2888
2912
  const supportedFields = Object.entries(DEFAULT_FIELD_CONFIGS).filter(([id]) => supportedFieldsIds.includes(id)).map(([id, config]) => ({ [id]: config })).reduce((a, b) => ({ ...a, ...b }), {});
2913
+ function MarkdownPropertyField({
2914
+ disabled,
2915
+ showErrors
2916
+ }) {
2917
+ const {
2918
+ values,
2919
+ setFieldValue
2920
+ } = useFormex();
2921
+ const baseStoragePath = "storage";
2922
+ const fileName = `${baseStoragePath}.fileName`;
2923
+ const maxSize = `${baseStoragePath}.maxSize`;
2924
+ const storagePath = `${baseStoragePath}.storagePath`;
2925
+ const fileNameValue = getIn(values, fileName) ?? "{rand}_{file}";
2926
+ const storagePathValue = getIn(values, storagePath) ?? "/";
2927
+ const maxSizeValue = getIn(values, maxSize);
2928
+ const hasFilenameCallback = typeof fileNameValue === "function";
2929
+ const hasStoragePathCallback = typeof storagePathValue === "function";
2930
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2931
+ /* @__PURE__ */ jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsx(ValidationPanel, { children: /* @__PURE__ */ jsx(
2932
+ StringPropertyValidation,
2933
+ {
2934
+ disabled,
2935
+ length: true,
2936
+ lowercase: true,
2937
+ max: true,
2938
+ min: true,
2939
+ trim: true,
2940
+ uppercase: true,
2941
+ showErrors
2942
+ }
2943
+ ) }) }),
2944
+ /* @__PURE__ */ jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsx(
2945
+ ExpandablePanel,
2946
+ {
2947
+ title: /* @__PURE__ */ jsxs("div", { className: "flex flex-row text-gray-500", children: [
2948
+ /* @__PURE__ */ jsx(FileUploadIcon, {}),
2949
+ /* @__PURE__ */ jsx(
2950
+ Typography,
2951
+ {
2952
+ variant: "subtitle2",
2953
+ className: "ml-2",
2954
+ children: "File upload config"
2955
+ }
2956
+ )
2957
+ ] }),
2958
+ children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-2 p-4", children: [
2959
+ /* @__PURE__ */ jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsx(
2960
+ Field,
2961
+ {
2962
+ name: fileName,
2963
+ as: DebouncedTextField,
2964
+ label: "File name",
2965
+ size: "small",
2966
+ disabled: hasFilenameCallback || disabled,
2967
+ value: hasFilenameCallback ? "-" : fileNameValue
2968
+ }
2969
+ ) }),
2970
+ /* @__PURE__ */ jsxs("div", { className: "col-span-12", children: [
2971
+ /* @__PURE__ */ jsx(
2972
+ Field,
2973
+ {
2974
+ name: storagePath,
2975
+ as: DebouncedTextField,
2976
+ label: "Storage path",
2977
+ disabled: hasStoragePathCallback || disabled,
2978
+ size: "small",
2979
+ value: hasStoragePathCallback ? "-" : storagePathValue
2980
+ }
2981
+ ),
2982
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "ml-3.5 mt-1 mb-2", children: [
2983
+ /* @__PURE__ */ jsx("p", { children: "You can use the following placeholders in the file name and storage path values:" }),
2984
+ /* @__PURE__ */ jsxs("ul", { children: [
2985
+ /* @__PURE__ */ jsx("li", { children: "{file} - Full name of the uploaded file" }),
2986
+ /* @__PURE__ */ jsx("li", { children: "{file.name} - Name of the uploaded file without extension" }),
2987
+ /* @__PURE__ */ jsx("li", { children: "{file.ext} - Extension of the uploaded file" }),
2988
+ /* @__PURE__ */ jsx("li", { children: "{entityId} - ID of the entity" }),
2989
+ /* @__PURE__ */ jsx("li", { children: "{propertyKey} - ID of this field" }),
2990
+ /* @__PURE__ */ jsx("li", { children: "{path} - Path of this entity" }),
2991
+ /* @__PURE__ */ jsx("li", { children: "{rand} - Random value used to avoid name collisions" })
2992
+ ] })
2993
+ ] }),
2994
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "ml-3.5 mt-1 mb-2", children: "When using Markdown, the URL of the uploaded files are always saved in the text value (not the path)." })
2995
+ ] }),
2996
+ /* @__PURE__ */ jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsx(
2997
+ DebouncedTextField,
2998
+ {
2999
+ name: maxSize,
3000
+ type: "number",
3001
+ label: "Max size (in bytes)",
3002
+ size: "small",
3003
+ value: maxSizeValue !== void 0 && maxSizeValue !== null ? maxSizeValue.toString() : "",
3004
+ onChange: (e) => {
3005
+ const value = e.target.value;
3006
+ if (value === "") setFieldValue(maxSize, void 0);
3007
+ else setFieldValue(maxSize, parseInt(value));
3008
+ }
3009
+ }
3010
+ ) })
3011
+ ] })
3012
+ }
3013
+ ) }),
3014
+ /* @__PURE__ */ jsx("div", { className: "col-span-12", children: /* @__PURE__ */ jsx(
3015
+ TextField,
3016
+ {
3017
+ name: "defaultValue",
3018
+ disabled,
3019
+ onChange: (e) => {
3020
+ setFieldValue("defaultValue", e.target.value === "" ? void 0 : e.target.value);
3021
+ },
3022
+ label: "Default value",
3023
+ value: getIn(values, "defaultValue") ?? ""
3024
+ }
3025
+ ) })
3026
+ ] });
3027
+ }
2889
3028
  const PropertyForm = React__default.memo(
2890
3029
  function PropertyForm2(props) {
2891
3030
  const {
@@ -2946,7 +3085,10 @@ const PropertyForm = React__default.memo(
2946
3085
  } = newPropertyWithId;
2947
3086
  doOnPropertyChanged({
2948
3087
  id,
2949
- property: { ...property2, editable: property2.editable ?? true }
3088
+ property: {
3089
+ ...property2,
3090
+ editable: property2.editable ?? true
3091
+ }
2950
3092
  });
2951
3093
  if (!existingProperty)
2952
3094
  controller.resetForm({ values: initialValue });
@@ -3151,7 +3293,7 @@ function PropertyEditFormFields({
3151
3293
  }, 0);
3152
3294
  };
3153
3295
  let childComponent;
3154
- if (selectedFieldConfigId === "text_field" || selectedFieldConfigId === "multiline" || selectedFieldConfigId === "markdown" || selectedFieldConfigId === "email") {
3296
+ if (selectedFieldConfigId === "text_field" || selectedFieldConfigId === "multiline" || selectedFieldConfigId === "email") {
3155
3297
  childComponent = /* @__PURE__ */ jsx(
3156
3298
  StringPropertyField,
3157
3299
  {
@@ -3168,6 +3310,14 @@ function PropertyEditFormFields({
3168
3310
  showErrors
3169
3311
  }
3170
3312
  );
3313
+ } else if (selectedFieldConfigId === "markdown") {
3314
+ childComponent = /* @__PURE__ */ jsx(
3315
+ MarkdownPropertyField,
3316
+ {
3317
+ disabled,
3318
+ showErrors
3319
+ }
3320
+ );
3171
3321
  } else if (selectedFieldConfigId === "select" || selectedFieldConfigId === "number_select") {
3172
3322
  childComponent = /* @__PURE__ */ jsx(
3173
3323
  EnumPropertyField,
@@ -5795,7 +5945,8 @@ const ConfigControllerProvider = React__default.memo(
5795
5945
  getPathSuggestions,
5796
5946
  getUser,
5797
5947
  getData,
5798
- onAnalyticsEvent
5948
+ onAnalyticsEvent,
5949
+ components
5799
5950
  }) {
5800
5951
  const navigation = useNavigationController();
5801
5952
  const navigate = useNavigate();
@@ -5894,7 +6045,8 @@ const ConfigControllerProvider = React__default.memo(
5894
6045
  createCollection,
5895
6046
  editProperty,
5896
6047
  configPermissions: configPermissions ?? defaultConfigPermissions,
5897
- getPathSuggestions
6048
+ getPathSuggestions,
6049
+ components
5898
6050
  },
5899
6051
  children: [
5900
6052
  children,
@@ -6004,7 +6156,6 @@ const ConfigControllerProvider = React__default.memo(
6004
6156
  },
6005
6157
  equal
6006
6158
  );
6007
- const useCollectionEditorController = () => useContext(CollectionEditorContext);
6008
6159
  function EditorCollectionAction({
6009
6160
  path: fullPath,
6010
6161
  parentCollectionIds,
@@ -6328,7 +6479,8 @@ function useCollectionEditorPlugin({
6328
6479
  getUser,
6329
6480
  collectionInference,
6330
6481
  getData,
6331
- onAnalyticsEvent
6482
+ onAnalyticsEvent,
6483
+ components
6332
6484
  }) {
6333
6485
  return {
6334
6486
  key: "collection_editor",
@@ -6344,7 +6496,8 @@ function useCollectionEditorPlugin({
6344
6496
  getPathSuggestions,
6345
6497
  getUser,
6346
6498
  getData,
6347
- onAnalyticsEvent
6499
+ onAnalyticsEvent,
6500
+ components
6348
6501
  }
6349
6502
  },
6350
6503
  homePage: {