@firecms/collection_editor 3.0.0-canary.11 → 3.0.0-canary.111

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 (54) 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 +4680 -3442
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +6699 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +14 -2
  9. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  10. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  11. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  12. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +4 -3
  13. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  14. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  15. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  16. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  17. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  18. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  19. package/dist/useCollectionEditorPlugin.d.ts +15 -9
  20. package/dist/utils/collections.d.ts +6 -0
  21. package/package.json +21 -35
  22. package/src/ConfigControllerProvider.tsx +75 -63
  23. package/src/index.ts +1 -0
  24. package/src/types/collection_editor_controller.tsx +14 -4
  25. package/src/ui/CollectionViewHeaderAction.tsx +9 -4
  26. package/src/ui/EditorCollectionAction.tsx +10 -63
  27. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  28. package/src/ui/HomePageEditorCollectionAction.tsx +16 -11
  29. package/src/ui/NewCollectionButton.tsx +12 -10
  30. package/src/ui/NewCollectionCard.tsx +3 -3
  31. package/src/ui/PropertyAddColumnComponent.tsx +9 -4
  32. package/src/ui/collection_editor/CollectionDetailsForm.tsx +67 -7
  33. package/src/ui/collection_editor/CollectionEditorDialog.tsx +56 -31
  34. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +6 -5
  35. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +33 -27
  36. package/src/ui/collection_editor/GetCodeDialog.tsx +15 -3
  37. package/src/ui/collection_editor/PropertyEditView.tsx +15 -7
  38. package/src/ui/collection_editor/PropertyFieldPreview.tsx +3 -6
  39. package/src/ui/collection_editor/PropertySelectItem.tsx +3 -3
  40. package/src/ui/collection_editor/PropertyTree.tsx +7 -5
  41. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +6 -4
  42. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +25 -9
  43. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +9 -7
  44. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +14 -8
  45. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +1 -1
  46. package/src/ui/collection_editor/properties/MapPropertyField.tsx +5 -5
  47. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  48. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  49. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +21 -1
  50. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  51. package/src/useCollectionEditorPlugin.tsx +37 -27
  52. package/src/utils/collections.ts +30 -0
  53. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  54. package/src/ui/RootCollectionSuggestions.tsx +0 -63
@@ -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"
@@ -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
 
@@ -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();
@@ -1,21 +1,33 @@
1
- import { convertDataToEntity, getPropertiesMapping, ImportConfig } from "@firecms/data_import_export";
2
- import { EntityCollectionTable, Properties, useSelectionController } from "@firecms/core";
3
- import { useEffect } from "react";
1
+ import { convertDataToEntity, ImportConfig } from "@firecms/data_import_export";
2
+ import { CircularProgressCenter, EntityCollectionTable, Properties, useSelectionController } from "@firecms/core";
3
+ import { useEffect, useState } from "react";
4
4
  import { Typography } from "@firecms/ui";
5
5
 
6
- export function CollectionEditorImportDataPreview({ importConfig, properties, propertiesOrder }: {
6
+ export function CollectionEditorImportDataPreview({
7
+ importConfig,
8
+ properties,
9
+ propertiesOrder
10
+ }: {
7
11
  importConfig: ImportConfig,
8
12
  properties: Properties,
9
13
  propertiesOrder: string[]
10
14
  }) {
11
15
 
12
- useEffect(() => {
13
- const propertiesMapping = getPropertiesMapping(importConfig.originProperties, properties);
14
- const mappedData = importConfig.importData.map(d => convertDataToEntity(d, importConfig.idColumn, importConfig.headersMapping, properties, propertiesMapping, "TEMP_PATH"));
16
+ const [loading, setLoading] = useState<boolean>(false);
17
+
18
+ async function loadEntities() {
19
+ // const propertiesMapping = getPropertiesMapping(importConfig.originProperties, properties, importConfig.headersMapping);
20
+ const mappedData = importConfig.importData.map(d => convertDataToEntity(d, importConfig.idColumn, importConfig.headersMapping, properties, "TEMP_PATH", importConfig.defaultValues));
15
21
  importConfig.setEntities(mappedData);
22
+ }
23
+
24
+ useEffect(() => {
25
+ loadEntities().finally(() => setLoading(false));
16
26
  }, []);
17
27
 
18
28
  const selectionController = useSelectionController();
29
+ if (loading)
30
+ return <CircularProgressCenter/>
19
31
 
20
32
  return <EntityCollectionTable
21
33
  title={<div>
@@ -31,7 +43,11 @@ export function CollectionEditorImportDataPreview({ importConfig, properties, pr
31
43
  filterable={false}
32
44
  sortable={false}
33
45
  selectionController={selectionController}
34
- displayedColumnIds={propertiesOrder.map(p => ({ key: p, disabled: false }))}
35
- properties={properties}/>
46
+ displayedColumnIds={propertiesOrder.map(p => ({
47
+ key: p,
48
+ disabled: false
49
+ }))}
50
+ properties={properties}
51
+ enablePopupIcon={false}/>
36
52
 
37
53
  }
@@ -6,7 +6,7 @@ import {
6
6
  } from "@firecms/data_import_export";
7
7
  import { getIn, useFormex } from "@firecms/formex";
8
8
 
9
- import { PropertyConfigBadge, getFieldConfig, getFieldId, Properties, Property, PropertyConfig, } from "@firecms/core";
9
+ import { getFieldConfig, getFieldId, Properties, Property, PropertyConfig, PropertyConfigBadge, } from "@firecms/core";
10
10
  import { Container, Select, Tooltip, Typography } from "@firecms/ui";
11
11
  import React, { useState } from "react";
12
12
  import { OnPropertyChangedParams, PropertyFormDialog, PropertyWithId } from "../PropertyEditView";
@@ -143,18 +143,20 @@ export function CollectionEditorImportMapping({
143
143
  <div className={"overflow-auto my-auto"}>
144
144
  <Container maxWidth={"6xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
145
145
 
146
- <Typography variant="h6" className={"mt-4"}>Data property mapping</Typography>
146
+ <Typography variant="h6" className={"my-4 ml-3.5"}>Data property mapping</Typography>
147
147
 
148
- <DataNewPropertiesMapping headersMapping={importConfig.headersMapping}
149
- idColumn={importConfig.idColumn}
150
- originProperties={importConfig.originProperties}
148
+ <DataNewPropertiesMapping importConfig={importConfig}
151
149
  destinationProperties={values.properties as Properties}
152
- onIdPropertyChanged={(value) => importConfig.setIdColumn(value ?? undefined)}
153
150
  buildPropertyView={({
154
151
  property,
155
152
  propertyKey,
156
- importKey
153
+ importKey,
154
+ isIdColumn
157
155
  }) => {
156
+ if (isIdColumn) {
157
+ return <Typography> This column will be used as ID</Typography>
158
+ }
159
+
158
160
  return <ImportNewPropertyFieldPreview
159
161
  property={property}
160
162
  propertyKey={propertyKey}
@@ -1,12 +1,18 @@
1
- import React, { useCallback, useState } from "react";
1
+ import React, { useState } from "react";
2
2
  import { AddIcon, Button, Paper, Typography } from "@firecms/ui";
3
3
  import { getIn, useFormex } from "@firecms/formex";
4
4
  import { PropertyFormDialog } from "../PropertyEditView";
5
- import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath } from "../util";
5
+ import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath, namespaceToPropertiesPath } from "../util";
6
6
  import { PropertyTree } from "../PropertyTree";
7
7
  import { ArrayProperty, Property, PropertyConfig } from "@firecms/core";
8
8
 
9
- export function BlockPropertyField({ disabled, getData, allowDataInference, propertyConfigs, collectionEditable }: {
9
+ export function BlockPropertyField({
10
+ disabled,
11
+ getData,
12
+ allowDataInference,
13
+ propertyConfigs,
14
+ collectionEditable
15
+ }: {
10
16
  disabled: boolean;
11
17
  getData?: () => Promise<object[]>;
12
18
  allowDataInference: boolean;
@@ -43,20 +49,20 @@ export function BlockPropertyField({ disabled, getData, allowDataInference, prop
43
49
  const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined;
44
50
  const selectedProperty = selectedPropertyFullId ? getIn(values.oneOf?.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : undefined;
45
51
 
46
- const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
52
+ const deleteProperty = (propertyKey?: string, namespace?: string) => {
47
53
  const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
48
54
  if (!fullId)
49
55
  throw Error("collection editor miss config");
50
56
 
51
57
  setFieldValue(`oneOf.${idToPropertiesPath(fullId)}`, undefined, false);
52
58
  const propertiesOrderPath = `oneOf.${namespaceToPropertiesOrderPath(namespace)}`;
53
- const currentPropertiesOrder: string[] = getIn(values, propertiesOrderPath);
59
+ const currentPropertiesOrder: string[] = getIn(values, propertiesOrderPath) ?? Object.keys(getIn(values, namespaceToPropertiesPath(namespace)));
54
60
  setFieldValue(propertiesOrderPath, currentPropertiesOrder.filter((p) => p !== propertyKey), false);
55
61
 
56
62
  setPropertyDialogOpen(false);
57
63
  setSelectedPropertyKey(undefined);
58
64
  setSelectedPropertyNamespace(undefined);
59
- }, [setFieldValue, values]);
65
+ };
60
66
 
61
67
  const addChildButton = <Button
62
68
  autoFocus
@@ -68,9 +74,9 @@ export function BlockPropertyField({ disabled, getData, allowDataInference, prop
68
74
  Add property to {values.name ?? "this block"}
69
75
  </Button>;
70
76
 
71
- const onPropertyMove = useCallback((propertiesOrder: string[], namespace?: string) => {
77
+ const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
72
78
  setFieldValue(`oneOf.${namespaceToPropertiesOrderPath(namespace)}`, propertiesOrder, false);
73
- }, []);
79
+ };
74
80
 
75
81
  return (
76
82
  <>
@@ -71,7 +71,7 @@ export function EnumPropertyField({
71
71
  }}
72
72
  getData={getData
73
73
  ? () => getData()
74
- .then(res => res.map(d => values.id && getIn(d, values.id)).filter(Boolean))
74
+ .then(res => res.map(entry => values.id && getIn(entry, values.id)).filter(Boolean))
75
75
  : undefined}
76
76
  onValuesChanged={onValuesChanged}/>
77
77
  </div>
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState } from "react";
1
+ import React, { useState } from "react";
2
2
  import { FieldCaption, MapProperty, Property, PropertyConfig, } from "@firecms/core";
3
3
  import { AddIcon, BooleanSwitchWithLabel, Button, Paper, Typography } from "@firecms/ui";
4
4
  import { PropertyFormDialog } from "../PropertyEditView";
@@ -42,7 +42,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
42
42
  setPropertyDialogOpen(false);
43
43
  };
44
44
 
45
- const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
45
+ const deleteProperty = (propertyKey?: string, namespace?: string) => {
46
46
  const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
47
47
  if (!fullId)
48
48
  throw Error("collection editor miss config");
@@ -58,16 +58,16 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
58
58
  setPropertyDialogOpen(false);
59
59
  setSelectedPropertyKey(undefined);
60
60
  setSelectedPropertyNamespace(undefined);
61
- }, [setFieldValue, values]);
61
+ };
62
62
 
63
63
  const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined;
64
64
  const selectedProperty = selectedPropertyFullId ? getIn(values.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : undefined;
65
65
 
66
66
  const empty = !propertiesOrder || propertiesOrder.length < 1;
67
67
 
68
- const onPropertyMove = useCallback((propertiesOrder: string[], namespace?: string) => {
68
+ const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
69
69
  setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
70
- }, []);
70
+ };
71
71
 
72
72
  return (
73
73
  <>
@@ -0,0 +1,139 @@
1
+ import React from "react";
2
+ import { StringPropertyValidation } from "./validation/StringPropertyValidation";
3
+ import { ValidationPanel } from "./validation/ValidationPanel";
4
+ import { Field, getIn, useFormex } from "@firecms/formex";
5
+
6
+ import { DebouncedTextField, ExpandablePanel, FileUploadIcon, TextField, Typography } from "@firecms/ui";
7
+
8
+ export function MarkdownPropertyField({
9
+ disabled,
10
+ showErrors
11
+ }: {
12
+ disabled: boolean;
13
+ showErrors: boolean;
14
+ }) {
15
+
16
+ const {
17
+ values,
18
+ setFieldValue
19
+ } = useFormex();
20
+
21
+ const baseStoragePath = "storage";
22
+
23
+ const metadata = `${baseStoragePath}.metadata`;
24
+ const fileName = `${baseStoragePath}.fileName`;
25
+ const maxSize = `${baseStoragePath}.maxSize`;
26
+ const storagePath = `${baseStoragePath}.storagePath`;
27
+
28
+ const fileNameValue = getIn(values, fileName) ?? "{rand}_{file}";
29
+ const storagePathValue = getIn(values, storagePath) ?? "/";
30
+ const maxSizeValue = getIn(values, maxSize);
31
+
32
+ const hasFilenameCallback = typeof fileNameValue === "function";
33
+ const hasStoragePathCallback = typeof storagePathValue === "function";
34
+
35
+ return (
36
+ <>
37
+ <div className={"col-span-12"}>
38
+
39
+ <ValidationPanel>
40
+
41
+ <StringPropertyValidation disabled={disabled}
42
+ length={true}
43
+ lowercase={true}
44
+ max={true}
45
+ min={true}
46
+ trim={true}
47
+ uppercase={true}
48
+ showErrors={showErrors}/>
49
+
50
+ </ValidationPanel>
51
+
52
+ </div>
53
+
54
+ <div className={"col-span-12"}>
55
+ <ExpandablePanel
56
+ title={
57
+ <div className="flex flex-row text-gray-500">
58
+ <FileUploadIcon/>
59
+ <Typography variant={"subtitle2"}
60
+ className="ml-2">
61
+ File upload config
62
+ </Typography>
63
+ </div>
64
+ }>
65
+
66
+ <div className={"grid grid-cols-12 gap-2 p-4"}>
67
+
68
+
69
+ <div className={"col-span-12"}>
70
+ <Field name={fileName}
71
+ as={DebouncedTextField}
72
+ label={"File name"}
73
+ size={"small"}
74
+ disabled={hasFilenameCallback || disabled}
75
+ value={hasFilenameCallback ? "-" : fileNameValue}
76
+ />
77
+ </div>
78
+ <div className={"col-span-12"}>
79
+ <Field name={storagePath}
80
+ as={DebouncedTextField}
81
+ label={"Storage path"}
82
+ disabled={hasStoragePathCallback || disabled}
83
+ size={"small"}
84
+ value={hasStoragePathCallback ? "-" : storagePathValue}
85
+ />
86
+ <Typography variant={"caption"} className={"ml-3.5 mt-1 mb-2"}>
87
+ <p>You can use the following placeholders in
88
+ the file name
89
+ and storage path values:</p>
90
+ <ul>
91
+ <li>{"{file} - Full name of the uploaded file"}</li>
92
+ <li>{"{file.name} - Name of the uploaded file without extension"}</li>
93
+ <li>{"{file.ext} - Extension of the uploaded file"}</li>
94
+ <li>{"{entityId} - ID of the entity"}</li>
95
+ <li>{"{propertyKey} - ID of this field"}</li>
96
+ <li>{"{path} - Path of this entity"}</li>
97
+ <li>{"{rand} - Random value used to avoid name collisions"}</li>
98
+ </ul>
99
+ </Typography>
100
+
101
+ <Typography variant={"caption"} className={"ml-3.5 mt-1 mb-2"}>
102
+ When using Markdown, the URL of the uploaded files are always saved in the text value
103
+ (not
104
+ the path).
105
+ </Typography>
106
+ </div>
107
+
108
+ <div className={"col-span-12"}>
109
+ <DebouncedTextField name={maxSize}
110
+ type={"number"}
111
+ label={"Max size (in bytes)"}
112
+ size={"small"}
113
+ value={maxSizeValue !== undefined && maxSizeValue !== null ? maxSizeValue.toString() : ""}
114
+ onChange={(e) => {
115
+ const value = e.target.value;
116
+ if (value === "") setFieldValue(maxSize, undefined);
117
+ else setFieldValue(maxSize, parseInt(value));
118
+ }}
119
+ />
120
+ </div>
121
+
122
+ </div>
123
+ </ExpandablePanel>
124
+ </div>
125
+
126
+ <div className={"col-span-12"}>
127
+
128
+ <TextField name={"defaultValue"}
129
+ disabled={disabled}
130
+ onChange={(e: any) => {
131
+ setFieldValue("defaultValue", e.target.value === "" ? undefined : e.target.value);
132
+ }}
133
+ label={"Default value"}
134
+ value={getIn(values, "defaultValue") ?? ""}/>
135
+
136
+ </div>
137
+ </>
138
+ );
139
+ }
@@ -39,7 +39,6 @@ export function RepeatPropertyField({
39
39
 
40
40
  const onPropertyChanged = ({ id, property, namespace }:
41
41
  { id?: string, property: Property, namespace?: string }) => {
42
- console.log("onPropertyChanged", id, property, namespace);
43
42
  setFieldValue("of", property);
44
43
  };
45
44
 
@@ -44,11 +44,13 @@ export function StoragePropertyField({
44
44
 
45
45
  const metadata = `${baseStoragePath}.metadata`;
46
46
  const fileName = `${baseStoragePath}.fileName`;
47
+ const maxSize = `${baseStoragePath}.maxSize`;
47
48
  const storagePath = `${baseStoragePath}.storagePath`;
48
49
  const storeUrl = `${baseStoragePath}.storeUrl`;
49
50
 
50
51
  const fileNameValue = getIn(values, fileName) ?? "{rand}_{file}";
51
52
  const storagePathValue = getIn(values, storagePath) ?? "/";
53
+ const maxSizeValue = getIn(values, maxSize);
52
54
 
53
55
  const storedValue = getIn(values, acceptedFiles);
54
56
  const fileTypesValue: string[] | undefined = Array.isArray(storedValue) ? storedValue : undefined;
@@ -161,7 +163,10 @@ export function StoragePropertyField({
161
163
 
162
164
  <Field name={storeUrl}
163
165
  type="checkbox">
164
- {({ field, form }: FormexFieldProps) => {
166
+ {({
167
+ field,
168
+ form
169
+ }: FormexFieldProps) => {
165
170
  return <SwitchControl
166
171
  label={"Save URL instead of storage path"}
167
172
  disabled={existing || disabled}
@@ -178,6 +183,21 @@ export function StoragePropertyField({
178
183
  You can only change this prop upon creation.
179
184
  </Typography>
180
185
  </div>
186
+
187
+ <div className={"col-span-12"}>
188
+ <DebouncedTextField name={maxSize}
189
+ type={"number"}
190
+ label={"Max size (in bytes)"}
191
+ size={"small"}
192
+ value={maxSizeValue !== undefined && maxSizeValue !== null ? maxSizeValue.toString() : ""}
193
+ onChange={(e) => {
194
+ const value = e.target.value;
195
+ if (value === "") setFieldValue(maxSize, undefined);
196
+ else setFieldValue(maxSize, parseInt(value));
197
+ }}
198
+ />
199
+ </div>
200
+
181
201
  </div>
182
202
  </ExpandablePanel>
183
203
 
@@ -10,7 +10,7 @@ export function StringPropertyField({
10
10
  disabled,
11
11
  showErrors
12
12
  }: {
13
- widgetId: "text_field" | "multiline" | "markdown" | "email";
13
+ widgetId: "text_field" | "multiline" | "email";
14
14
  disabled: boolean;
15
15
  showErrors: boolean;
16
16
  }) {
@@ -42,15 +42,6 @@ export function StringPropertyField({
42
42
  trim={true}
43
43
  uppercase={true}
44
44
  showErrors={showErrors}/>}
45
- {widgetId === "markdown" &&
46
- <StringPropertyValidation disabled={disabled}
47
- length={true}
48
- lowercase={true}
49
- max={true}
50
- min={true}
51
- trim={true}
52
- uppercase={true}
53
- showErrors={showErrors}/>}
54
45
 
55
46
  {widgetId === "email" &&
56
47
  <StringPropertyValidation disabled={disabled}
@@ -4,16 +4,16 @@ import { ConfigControllerProvider } from "./ConfigControllerProvider";
4
4
  import { CollectionEditorPermissionsBuilder } from "./types/config_permissions";
5
5
  import { EditorCollectionAction } from "./ui/EditorCollectionAction";
6
6
  import { HomePageEditorCollectionAction } from "./ui/HomePageEditorCollectionAction";
7
- import { NewCollectionCard } from "./ui/NewCollectionCard";
8
7
  import { PersistedCollection } from "./types/persisted_collection";
9
8
  import { CollectionInference } from "./types/collection_inference";
10
9
  import { CollectionsConfigController } from "./types/config_controller";
11
- import { RootCollectionSuggestions } from "./ui/RootCollectionSuggestions";
12
10
  import { CollectionViewHeaderAction } from "./ui/CollectionViewHeaderAction";
13
11
  import { PropertyAddColumnComponent } from "./ui/PropertyAddColumnComponent";
14
12
  import { NewCollectionButton } from "./ui/NewCollectionButton";
15
- import { AddIcon, Button, Typography } from "@firecms/ui";
13
+ import { AddIcon, Button, Paper, Typography } from "@firecms/ui";
16
14
  import { useCollectionEditorController } from "./useCollectionEditorController";
15
+ import { EditorCollectionActionStart } from "./ui/EditorCollectionActionStart";
16
+ import { NewCollectionCard } from "./ui/NewCollectionCard";
17
17
 
18
18
  export interface CollectionConfigControllerProps<EC extends PersistedCollection = PersistedCollection, UserType extends User = User> {
19
19
 
@@ -32,7 +32,7 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
32
32
  * names when creating collections.
33
33
  * e.g. ["admin"]
34
34
  */
35
- reservedGroups: string[];
35
+ reservedGroups?: string[];
36
36
 
37
37
  extraView?: {
38
38
  View: React.ComponentType<{
@@ -41,17 +41,22 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
41
41
  icon: React.ReactNode
42
42
  };
43
43
 
44
- pathSuggestions?: (path: string) => Promise<string[]>;
44
+ getPathSuggestions?: (path?: string) => Promise<string[]>;
45
45
 
46
46
  collectionInference?: CollectionInference;
47
47
 
48
48
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
49
49
 
50
- getUser: (uid: string) => UserType | null;
50
+ getUser?: (uid: string) => UserType | null;
51
51
 
52
52
  onAnalyticsEvent?: (event: string, params?: object) => void;
53
53
 
54
- introMode?: "new_project" | "existing_project";
54
+ components?: {
55
+ /**
56
+ * Custom component to render the database field
57
+ */
58
+ DatabaseField?: React.ComponentType<{ databaseId?: string, onDatabaseIdUpdate: (databaseId:string) => void }>;
59
+ };
55
60
 
56
61
  }
57
62
 
@@ -62,22 +67,22 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
62
67
  * @param configPermissions
63
68
  * @param reservedGroups
64
69
  * @param extraView
65
- * @param pathSuggestions
70
+ * @param getData
66
71
  * @param getUser
67
72
  * @param collectionInference
68
73
  */
69
74
  export function useCollectionEditorPlugin<EC extends PersistedCollection = PersistedCollection, UserType extends User = User>
70
75
  ({
71
76
  collectionConfigController,
72
- introMode,
73
77
  configPermissions,
74
78
  reservedGroups,
75
79
  extraView,
76
- pathSuggestions,
80
+ getPathSuggestions,
77
81
  getUser,
78
82
  collectionInference,
79
83
  getData,
80
- onAnalyticsEvent
84
+ onAnalyticsEvent,
85
+ components
81
86
  }: CollectionConfigControllerProps<EC, UserType>): FireCMSPlugin<any, any, PersistedCollection> {
82
87
 
83
88
  return {
@@ -91,20 +96,22 @@ export function useCollectionEditorPlugin<EC extends PersistedCollection = Persi
91
96
  collectionInference,
92
97
  reservedGroups,
93
98
  extraView,
94
- pathSuggestions,
99
+ getPathSuggestions,
95
100
  getUser,
96
101
  getData,
97
- onAnalyticsEvent
102
+ onAnalyticsEvent,
103
+ components
98
104
  }
99
105
  },
100
106
  homePage: {
101
107
  additionalActions: <NewCollectionButton/>,
102
- additionalChildrenStart: introMode ? <IntroWidget introMode={introMode}/> : undefined,
103
- additionalChildrenEnd: <RootCollectionSuggestions introMode={introMode}/>,
108
+ additionalChildrenStart: <IntroWidget/>,
109
+ // additionalChildrenEnd: <RootCollectionSuggestions introMode={introMode}/>,
104
110
  CollectionActions: HomePageEditorCollectionAction,
105
- AdditionalCards: introMode ? undefined : NewCollectionCard,
111
+ AdditionalCards: NewCollectionCard,
106
112
  },
107
113
  collectionView: {
114
+ CollectionActionsStart: EditorCollectionActionStart,
108
115
  CollectionActions: EditorCollectionAction,
109
116
  HeaderAction: CollectionViewHeaderAction,
110
117
  AddColumnComponent: PropertyAddColumnComponent
@@ -112,9 +119,7 @@ export function useCollectionEditorPlugin<EC extends PersistedCollection = Persi
112
119
  };
113
120
  }
114
121
 
115
- export function IntroWidget({ introMode }: {
116
- introMode?: "new_project" | "existing_project";
117
- }) {
122
+ export function IntroWidget({}: {}) {
118
123
 
119
124
  const navigation = useNavigationController();
120
125
  if (!navigation.topLevelNavigation)
@@ -129,17 +134,19 @@ export function IntroWidget({ introMode }: {
129
134
  }).createCollections
130
135
  : true;
131
136
 
137
+ if (!navigation.initialised || (navigation.collections ?? []).length > 0) {
138
+ return null;
139
+ }
140
+
132
141
  return (
133
- <div className={"mt-8 flex flex-col mt-8 p-2"}>
134
- <Typography variant={"h4"} className="mb-4">Welcome</Typography>
135
- <Typography paragraph={true}>Your admin panel is ready ✌️</Typography>
136
- <Typography paragraph={true}>
142
+ <Paper
143
+ className={"my-4 px-4 py-6 flex flex-col bg-white dark:bg-slate-800 gap-2"}>
144
+ <Typography variant={"subtitle2"} className={"uppercase"}>No collections found</Typography>
145
+ <Typography>
137
146
  Start building collections in FireCMS easily. Map them to your existing
138
- database data, import from files, or use our templates. Simplify your data management process
139
- now.
147
+ database data, import from files, or use our templates.
140
148
  </Typography>
141
149
  {canCreateCollections && <Button
142
- className={"mt-4"}
143
150
  onClick={collectionEditorController && canCreateCollections
144
151
  ? () => collectionEditorController.createCollection({
145
152
  parentCollectionIds: [],
@@ -149,6 +156,9 @@ export function IntroWidget({ introMode }: {
149
156
  : undefined}>
150
157
  <AddIcon/>Create your first collection
151
158
  </Button>}
152
- </div>
159
+ <Typography color={"secondary"}>
160
+ You can also define collections programmatically.
161
+ </Typography>
162
+ </Paper>
153
163
  );
154
164
  }