@firecms/collection_editor 3.0.0-canary.24 → 3.0.0-canary.241

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 (76) hide show
  1. package/LICENSE +114 -21
  2. package/README.md +165 -1
  3. package/dist/ConfigControllerProvider.d.ts +1 -2
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.es.js +10109 -4774
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +10795 -3
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/collection_editor_controller.d.ts +3 -2
  10. package/dist/types/collection_inference.d.ts +1 -1
  11. package/dist/types/config_permissions.d.ts +2 -2
  12. package/dist/types/persisted_collection.d.ts +1 -1
  13. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  14. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  15. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  16. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
  17. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
  18. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  19. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  20. package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
  21. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  22. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  23. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  24. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  25. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  26. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  27. package/dist/useCollectionEditorPlugin.d.ts +8 -11
  28. package/dist/utils/collections.d.ts +6 -0
  29. package/package.json +24 -35
  30. package/src/ConfigControllerProvider.tsx +64 -66
  31. package/src/index.ts +1 -0
  32. package/src/types/collection_editor_controller.tsx +6 -5
  33. package/src/types/collection_inference.ts +1 -1
  34. package/src/types/config_permissions.ts +1 -1
  35. package/src/types/persisted_collection.ts +2 -3
  36. package/src/ui/CollectionViewHeaderAction.tsx +10 -5
  37. package/src/ui/EditorCollectionAction.tsx +10 -63
  38. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  39. package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
  40. package/src/ui/NewCollectionButton.tsx +1 -1
  41. package/src/ui/NewCollectionCard.tsx +3 -3
  42. package/src/ui/PropertyAddColumnComponent.tsx +11 -6
  43. package/src/ui/collection_editor/CollectionDetailsForm.tsx +112 -18
  44. package/src/ui/collection_editor/CollectionEditorDialog.tsx +101 -34
  45. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +13 -28
  46. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +41 -39
  47. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
  48. package/src/ui/collection_editor/EnumForm.tsx +11 -7
  49. package/src/ui/collection_editor/GetCodeDialog.tsx +56 -26
  50. package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
  51. package/src/ui/collection_editor/PropertyEditView.tsx +257 -79
  52. package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
  53. package/src/ui/collection_editor/PropertyTree.tsx +9 -7
  54. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  55. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
  56. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +33 -9
  57. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
  58. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
  59. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
  60. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
  61. package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
  62. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  63. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -0
  64. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  65. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
  66. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  67. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
  68. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
  69. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  70. package/src/ui/collection_editor/utils/strings.ts +13 -6
  71. package/src/useCollectionEditorPlugin.tsx +32 -32
  72. package/src/utils/collections.ts +37 -0
  73. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  74. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  75. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  76. package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { EntityCollection, unslugify, } from "@firecms/core";
3
- import { Button, Card, Chip, CircularProgress, cn, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
3
+ import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
4
4
 
5
5
  import { productsCollectionTemplate } from "./templates/products_template";
6
6
  import { blogCollectionTemplate } from "./templates/blog_template";
@@ -19,7 +19,7 @@ export function CollectionEditorWelcomeView({
19
19
  path: string;
20
20
  pathSuggestions?: (path: string) => Promise<string[]>;
21
21
  parentCollection?: EntityCollection;
22
- onContinue: (importData?: object[]) => void;
22
+ onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
23
23
  existingCollectionPaths?: string[];
24
24
  }) {
25
25
 
@@ -37,18 +37,6 @@ export function CollectionEditorWelcomeView({
37
37
  }
38
38
  }, [existingCollectionPaths, path, pathSuggestions]);
39
39
 
40
- // const {
41
- // values,
42
- // setFieldValue,
43
- // setValues,
44
- // handleChange,
45
- // touched,
46
- // errors,
47
- // setFieldTouched,
48
- // isSubmitting,
49
- // submitCount
50
- // } = useFormex<EntityCollection>();
51
-
52
40
  const {
53
41
  values,
54
42
  setFieldValue,
@@ -56,6 +44,11 @@ export function CollectionEditorWelcomeView({
56
44
  submitCount
57
45
  } = useFormex<EntityCollection>();
58
46
 
47
+ const noSuggestions = !loadingPathSuggestions && (filteredPathSuggestions ?? [])?.length === 0;
48
+ if (!noSuggestions) {
49
+ return null;
50
+ }
51
+
59
52
  return (
60
53
  <div className={"overflow-auto my-auto"}>
61
54
  <Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
@@ -97,12 +90,6 @@ export function CollectionEditorWelcomeView({
97
90
  </Chip>
98
91
  ))}
99
92
 
100
- {!loadingPathSuggestions && (filteredPathSuggestions ?? [])?.length === 0 &&
101
- <Typography variant={"caption"}>
102
- No suggestions
103
- </Typography>
104
- }
105
-
106
93
  </div>
107
94
 
108
95
  </div>
@@ -154,7 +141,7 @@ export function CollectionEditorWelcomeView({
154
141
  ● Create a collection from a file (csv, json, xls, xslx...)
155
142
  </Typography>
156
143
 
157
- <ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
144
+ <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
158
145
 
159
146
  </div>}
160
147
 
@@ -185,14 +172,15 @@ export function TemplateButton({
185
172
  }) {
186
173
 
187
174
  return (
188
- <Tooltip title={subtitle}>
175
+ <Tooltip title={subtitle}
176
+ asChild={true}>
189
177
  <Card
190
178
  onClick={onClick}
191
- className={cn(
179
+ className={cls(
192
180
  "my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
193
- "text-gray-700 dark:text-slate-300",
181
+ "text-surface-700 dark:text-surface-accent-300",
194
182
  "hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
195
- "border-gray-400 dark:border-gray-600 "
183
+ "border-surface-400 dark:border-surface-600 "
196
184
  )}
197
185
  >
198
186
  {icon}
@@ -201,9 +189,6 @@ export function TemplateButton({
201
189
  <Typography variant={"subtitle1"}>
202
190
  {title}
203
191
  </Typography>
204
- {/*<Typography>*/}
205
- {/* {subtitle}*/}
206
- {/*</Typography>*/}
207
192
 
208
193
  </div>
209
194
  </Card>
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from "react";
1
+ import React, { useEffect, useMemo, useState } from "react";
2
2
 
3
3
  import { Field, getIn, useFormex } from "@firecms/formex";
4
4
  import {
@@ -16,10 +16,10 @@ import {
16
16
  } from "@firecms/core";
17
17
  import {
18
18
  AddIcon,
19
- AutoAwesomeIcon,
19
+ AutorenewIcon,
20
20
  Button,
21
21
  CircularProgress,
22
- cn,
22
+ cls,
23
23
  CodeIcon,
24
24
  DebouncedTextField,
25
25
  defaultBorderMixin,
@@ -43,7 +43,7 @@ type CollectionEditorFormProps = {
43
43
  setDirty?: (dirty: boolean) => void;
44
44
  reservedGroups?: string[];
45
45
  extraIcon: React.ReactNode;
46
- getUser: (uid: string) => User | null;
46
+ getUser?: (uid: string) => User | null;
47
47
  getData?: () => Promise<object[]>;
48
48
  doCollectionInference: (collection: PersistedCollection) => Promise<Partial<EntityCollection> | null> | undefined;
49
49
  propertyConfigs: Record<string, PropertyConfig>;
@@ -158,20 +158,20 @@ export function CollectionPropertiesEditorForm({
158
158
  }
159
159
  : undefined;
160
160
 
161
- const getCurrentPropertiesOrder = useCallback((namespace?: string) => {
162
- if (!namespace) return currentPropertiesOrderRef.current[""];
161
+ const getCurrentPropertiesOrder = (namespace?: string) => {
162
+ if (!namespace) return currentPropertiesOrderRef.current[""] ?? getIn(values, namespaceToPropertiesOrderPath());
163
163
  return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
164
- }, [values]);
164
+ };
165
165
 
166
- const updatePropertiesOrder = useCallback((newPropertiesOrder: string[], namespace?: string) => {
166
+ const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
167
167
  const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
168
168
 
169
169
  setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
170
170
  currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
171
171
 
172
- }, [setFieldValue]);
172
+ };
173
173
 
174
- const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
174
+ const deleteProperty = (propertyKey?: string, namespace?: string) => {
175
175
  const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
176
176
  if (!fullId)
177
177
  throw Error("collection editor miss config");
@@ -179,15 +179,17 @@ export function CollectionPropertiesEditorForm({
179
179
  setFieldValue(idToPropertiesPath(fullId), undefined, false);
180
180
 
181
181
  const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
182
- const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
183
- updatePropertiesOrder(newPropertiesOrder, namespace);
182
+ if (currentPropertiesOrder) {
183
+ const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
184
+ updatePropertiesOrder(newPropertiesOrder, namespace);
185
+ }
184
186
 
185
187
  setNewPropertyDialogOpen(false);
186
188
 
187
189
  setSelectedPropertyIndex(undefined);
188
190
  setSelectedPropertyKey(undefined);
189
191
  setSelectedPropertyNamespace(undefined);
190
- }, [getCurrentPropertiesOrder, setFieldValue, updatePropertiesOrder]);
192
+ };
191
193
 
192
194
  const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
193
195
  setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
@@ -207,8 +209,8 @@ export function CollectionPropertiesEditorForm({
207
209
  ...(values.properties ?? {}),
208
210
  [id]: property
209
211
  }, false);
210
- const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
211
212
 
213
+ const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
212
214
  updatePropertiesOrder(newPropertiesOrder);
213
215
 
214
216
  setNewPropertyDialogOpen(false);
@@ -231,12 +233,6 @@ export function CollectionPropertiesEditorForm({
231
233
 
232
234
  // If the id has changed we need to a little cleanup
233
235
  if (previousId && previousId !== id) {
234
- console.debug("onPropertyChanged, id change", {
235
- id,
236
- property,
237
- previousId,
238
- namespace
239
- })
240
236
 
241
237
  const previousFullId = getFullId(previousId, namespace);
242
238
  const previousPropertyPath = idToPropertiesPath(previousFullId);
@@ -273,7 +269,7 @@ export function CollectionPropertiesEditorForm({
273
269
 
274
270
  };
275
271
 
276
- const onPropertyErrorInternal = useCallback((id: string, namespace?: string, error?: Record<string, any>) => {
272
+ const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
277
273
  const propertyPath = id ? getFullId(id, namespace) : undefined;
278
274
  console.debug("onPropertyErrorInternal", {
279
275
  id,
@@ -286,7 +282,7 @@ export function CollectionPropertiesEditorForm({
286
282
  onPropertyError(id, namespace, hasError ? error : undefined);
287
283
  setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
288
284
  }
289
- }, [])
285
+ }
290
286
 
291
287
  const closePropertyDialog = () => {
292
288
  setSelectedPropertyIndex(undefined);
@@ -301,9 +297,9 @@ export function CollectionPropertiesEditorForm({
301
297
  ? values.propertiesOrder
302
298
  : Object.keys(values.properties)) as string[];
303
299
 
304
- const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
300
+ const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
305
301
 
306
- const onPropertyClick = useCallback((propertyKey: string, namespace?: string) => {
302
+ const onPropertyClick = (propertyKey: string, namespace?: string) => {
307
303
  console.debug("CollectionEditor: onPropertyClick", {
308
304
  propertyKey,
309
305
  namespace
@@ -311,11 +307,12 @@ export function CollectionPropertiesEditorForm({
311
307
  setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
312
308
  setSelectedPropertyKey(propertyKey);
313
309
  setSelectedPropertyNamespace(namespace);
314
- }, [usedPropertiesOrder]);
310
+ };
315
311
 
316
312
  const body = (
317
- <div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
318
- <div className={cn(
313
+ <div className={"grid grid-cols-12 gap-2 h-full bg-white dark:bg-surface-950"}>
314
+ <div className={cls(
315
+ "bg-surface-50 dark:bg-surface-900",
319
316
  "p-4 md:p-8 pb-20 md:pb-20",
320
317
  "col-span-12 lg:col-span-5 h-full overflow-auto",
321
318
  !asDialog && "border-r " + defaultBorderMixin
@@ -349,7 +346,8 @@ export function CollectionPropertiesEditorForm({
349
346
  </div>}
350
347
 
351
348
  <div className="ml-1 mt-2 flex flex-row gap-2">
352
- <Tooltip title={"Get the code for this collection"}>
349
+ <Tooltip title={"Get the code for this collection"}
350
+ asChild={true}>
353
351
  <IconButton
354
352
  variant={"filled"}
355
353
  disabled={inferringProperties}
@@ -357,15 +355,17 @@ export function CollectionPropertiesEditorForm({
357
355
  <CodeIcon/>
358
356
  </IconButton>
359
357
  </Tooltip>
360
- {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}>
358
+ {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
359
+ asChild={true}>
361
360
  <IconButton
362
361
  variant={"filled"}
363
362
  disabled={inferringProperties}
364
363
  onClick={inferPropertiesFromData}>
365
- {inferringProperties ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
364
+ {inferringProperties ? <CircularProgress size={"small"}/> : <AutorenewIcon/>}
366
365
  </IconButton>
367
366
  </Tooltip>}
368
- <Tooltip title={"Add new property"}>
367
+ <Tooltip title={"Add new property"}
368
+ asChild={true}>
369
369
  <Button
370
370
  variant={"outlined"}
371
371
  onClick={() => setNewPropertyDialogOpen(true)}>
@@ -402,8 +402,8 @@ export function CollectionPropertiesEditorForm({
402
402
 
403
403
  {!asDialog &&
404
404
  <div className={"col-span-12 lg:col-span-7 p-4 md:py-8 md:px-4 h-full overflow-auto pb-20 md:pb-20"}>
405
- <Paper
406
- className="sticky top-8 p-4 min-h-full border border-transparent w-full flex flex-col justify-center ">
405
+ <div
406
+ className="sticky top-8 min-h-full w-full flex flex-col justify-center">
407
407
 
408
408
  {selectedPropertyFullId &&
409
409
  selectedProperty &&
@@ -447,7 +447,7 @@ export function CollectionPropertiesEditorForm({
447
447
  <Typography variant={"label"} className="flex items-center justify-center">
448
448
  {"This property is defined as a property builder in code"}
449
449
  </Typography>}
450
- </Paper>
450
+ </div>
451
451
  </div>}
452
452
 
453
453
  {asDialog && <PropertyFormDialog
@@ -469,6 +469,7 @@ export function CollectionPropertiesEditorForm({
469
469
  getData={getData}
470
470
  propertyConfigs={propertyConfigs}
471
471
  collectionEditable={collectionEditable}
472
+ onCancel={closePropertyDialog}
472
473
  onOkClicked={asDialog
473
474
  ? closePropertyDialog
474
475
  : undefined
@@ -496,11 +497,12 @@ export function CollectionPropertiesEditorForm({
496
497
  collectionEditable={collectionEditable}
497
498
  existingPropertyKeys={values.propertiesOrder as string[]}/>
498
499
 
499
- <GetCodeDialog
500
- collection={values}
501
- open={codeDialogOpen}
502
- onOpenChange={setCodeDialogOpen}/>
503
-
500
+ <ErrorBoundary>
501
+ <GetCodeDialog
502
+ collection={values}
503
+ open={codeDialogOpen}
504
+ onOpenChange={setCodeDialogOpen}/>
505
+ </ErrorBoundary>
504
506
  </>
505
507
  );
506
508
  }
@@ -1,8 +1,11 @@
1
1
  import { useCustomizationController } from "@firecms/core";
2
- import { Button, Dialog, DialogActions, DialogContent, Typography } from "@firecms/ui";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
3
3
  import React from "react";
4
4
 
5
- export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean, onClose: (selectedViewKey?: string) => void }) {
5
+ export function EntityCustomViewsSelectDialog({
6
+ open,
7
+ onClose
8
+ }: { open: boolean, onClose: (selectedViewKey?: string) => void }) {
6
9
  const {
7
10
  entityViews,
8
11
  } = useCustomizationController();
@@ -10,10 +13,8 @@ export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean
10
13
  return <Dialog
11
14
  maxWidth={"md"}
12
15
  open={open}>
16
+ <DialogTitle>Select custom view</DialogTitle>
13
17
  <DialogContent className={"flex flex-col gap-4"}>
14
- <Typography variant={"h6"}>
15
- Select view
16
- </Typography>
17
18
  {entityViews?.map((view) => {
18
19
  return <Button
19
20
  key={view.key}
@@ -1,9 +1,9 @@
1
1
  import React, { useEffect } from "react";
2
2
  import equal from "react-fast-compare"
3
3
 
4
- import { ArrayContainer, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
4
+ import { ArrayContainer, ArrayEntryParams, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
5
5
  import {
6
- AutoAwesomeIcon,
6
+ AutorenewIcon,
7
7
  Badge,
8
8
  Button,
9
9
  CircularProgress,
@@ -11,6 +11,7 @@ import {
11
11
  Dialog,
12
12
  DialogActions,
13
13
  DialogContent,
14
+ DialogTitle,
14
15
  IconButton,
15
16
  ListIcon,
16
17
  Paper,
@@ -121,7 +122,10 @@ function EnumFormFields({
121
122
  const inferredValuesRef = React.useRef(new Set());
122
123
  const inferredValues = inferredValuesRef.current;
123
124
 
124
- const buildEntry = (index: number, internalId: number) => {
125
+ const buildEntry = ({
126
+ index,
127
+ internalId
128
+ }:ArrayEntryParams) => {
125
129
  const justAdded = lastInternalIdAdded === internalId;
126
130
  const entryError = errors?.enumValues && errors?.enumValues[index];
127
131
  return <EnumEntry index={index}
@@ -179,7 +183,7 @@ function EnumFormFields({
179
183
  variant={"text"}
180
184
  size={"small"}
181
185
  onClick={inferValues}>
182
- {inferring ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
186
+ {inferring ? <CircularProgress size={"smallest"}/> : <AutorenewIcon/>}
183
187
  Infer values from data
184
188
  </Button>}
185
189
  </div>
@@ -193,7 +197,7 @@ function EnumFormFields({
193
197
  size={"small"}
194
198
  buildEntry={buildEntry}
195
199
  onInternalIdAdded={setLastInternalIdAdded}
196
- includeAddButton={true}
200
+ canAddElements={true}
197
201
  onValueChange={(value) => setFieldValue(enumValuesPath, value)}
198
202
  newDefaultEntry={{ id: "", label: "" }}/>
199
203
 
@@ -263,7 +267,7 @@ const EnumEntry = React.memo(
263
267
  size="small"
264
268
  autoFocus={autoFocus}
265
269
  autoComplete="off"
266
- endAdornment={inferredEntry && <AutoAwesomeIcon size={"small"}/>}
270
+ endAdornment={inferredEntry && <AutorenewIcon size={"small"}/>}
267
271
  error={Boolean(entryError?.label)}/>
268
272
 
269
273
  {!disabled &&
@@ -324,7 +328,7 @@ function EnumEntryDialog({
324
328
  open={open}
325
329
  onOpenChange={(open) => !open ? onClose() : undefined}
326
330
  >
327
-
331
+ <DialogTitle hidden>Enum form dialog</DialogTitle>
328
332
  <DialogContent>
329
333
  {index !== undefined &&
330
334
  <div>
@@ -1,35 +1,47 @@
1
- import { EntityCollection, useSnackbarController } from "@firecms/core";
2
- import { Button, ContentCopyIcon, Dialog, DialogActions, DialogContent, Typography, } from "@firecms/ui";
1
+ import { EntityCollection, isEmptyObject, useSnackbarController } from "@firecms/core";
2
+ import { Button, ContentCopyIcon, Dialog, DialogActions, DialogContent, DialogTitle, Typography, } from "@firecms/ui";
3
3
  import React from "react";
4
4
  import JSON5 from "json5";
5
5
  import { Highlight, themes } from "prism-react-renderer"
6
6
  import { camelCase } from "./utils/strings";
7
+ import { clone } from "@firecms/formex";
7
8
 
8
- export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
9
+ export function GetCodeDialog({
10
+ collection,
11
+ onOpenChange,
12
+ open
13
+ }: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
9
14
 
10
15
  const snackbarController = useSnackbarController();
11
16
 
12
- const code = "import { EntityCollection } from \"firecms\";\n\nconst " + (collection.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode(collection), null, "\t");
17
+ const code = collection
18
+ ? "import { EntityCollection } from \"@firecms/core\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode({ ...collection }), null, "\t")
19
+ : "No collection selected";
13
20
  return <Dialog open={open}
14
21
  onOpenChange={onOpenChange}
15
22
  maxWidth={"4xl"}>
23
+ <DialogTitle variant={"h6"}>Code for {collection.name}</DialogTitle>
16
24
  <DialogContent>
17
- <Typography variant={"h6"} className={"my-4"}>
18
- Code for {collection.name}
19
- </Typography>
25
+
20
26
  <Typography variant={"body2"} className={"my-4 mb-8"}>
21
27
  If you want to customise the collection in code, you can add this collection code to your CMS
22
28
  app configuration.
23
29
  More info in the <a
24
30
  rel="noopener noreferrer"
25
- href={"https://firecms.co/docs/customization_quickstart"}>docs</a>.
31
+ href={"https://firecms.co/docs/cloud/quickstart"}>docs</a>.
26
32
  </Typography>
27
33
  <Highlight
28
34
  theme={themes.vsDark}
29
35
  code={code}
30
36
  language="typescript"
31
37
  >
32
- {({ className, style, tokens, getLineProps, getTokenProps }) => (
38
+ {({
39
+ className,
40
+ style,
41
+ tokens,
42
+ getLineProps,
43
+ getTokenProps
44
+ }) => (
33
45
  <pre style={style} className={"p-4 rounded text-sm"}>
34
46
  {tokens.map((line, i) => (
35
47
  <div key={i} {...getLineProps({ line })}>
@@ -66,24 +78,40 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
66
78
 
67
79
  function collectionToCode(collection: EntityCollection): object {
68
80
 
69
- const propertyCleanup = (property: any) => {
70
-
71
- const updatedProperty = {
72
- ...property
73
- };
74
-
75
- delete updatedProperty.fromBuilder;
76
- delete updatedProperty.resolved;
77
- delete updatedProperty.propertiesOrder;
78
- delete updatedProperty.editable;
81
+ const propertyCleanup = (value: any): any => {
82
+ if (value === undefined || value === null) {
83
+ return value;
84
+ }
85
+ const valueCopy = clone(value);
86
+ if (typeof valueCopy === "function") {
87
+ return valueCopy;
88
+ }
89
+ if (Array.isArray(valueCopy)) {
90
+ return valueCopy.map((v: any) => propertyCleanup(v));
91
+ }
92
+ if (typeof valueCopy === "object") {
93
+ if (valueCopy === null)
94
+ return valueCopy;
95
+ Object.keys(valueCopy).forEach((key) => {
96
+ if (!isEmptyObject(valueCopy)) {
97
+ const childRes = propertyCleanup(valueCopy[key]);
98
+ if (childRes !== null && childRes !== undefined && childRes !== false && !isEmptyObject(childRes)) {
99
+ valueCopy[key] = childRes;
100
+ } else {
101
+ delete valueCopy[key];
102
+ }
103
+ }
104
+ });
105
+ delete valueCopy.fromBuilder;
106
+ delete valueCopy.resolved;
107
+ delete valueCopy.propertiesOrder;
108
+ delete valueCopy.propertyConfig;
109
+ delete valueCopy.resolvedProperties;
110
+ delete valueCopy.editable;
79
111
 
80
- if (updatedProperty.type === "map") {
81
- return {
82
- ...updatedProperty,
83
- properties: updatedProperty.properties.map(propertyCleanup)
84
- }
85
112
  }
86
- return updatedProperty;
113
+
114
+ return valueCopy;
87
115
  }
88
116
 
89
117
  return {
@@ -99,7 +127,9 @@ function collectionToCode(collection: EntityCollection): object {
99
127
  customId: collection.customId,
100
128
  initialFilter: collection.initialFilter,
101
129
  initialSort: collection.initialSort,
102
- properties: Object.entries(collection.properties ?? {})
130
+ properties: Object.entries({
131
+ ...(collection.properties ?? {})
132
+ })
103
133
  .map(([key, value]) => ({
104
134
  [key]: propertyCleanup(value)
105
135
  }))
@@ -0,0 +1,54 @@
1
+ import { Card, cls, SquareIcon, Tooltip, Typography, VerticalSplitIcon } from "@firecms/ui";
2
+
3
+ export function LayoutModeSwitch({
4
+ value,
5
+ onChange,
6
+ className
7
+ }: {
8
+ value: "side_panel" | "full_screen";
9
+ onChange: (value: "side_panel" | "full_screen") => void;
10
+ className?: string;
11
+ }) {
12
+
13
+ return <div className={cls(className)}>
14
+ <Typography variant={"label"} color={"secondary"} className={"ml-3.5"}>Document view</Typography>
15
+ <div className={cls("flex flex-row gap-4")}>
16
+
17
+ <Tooltip title={"Documents are open in a side panel"}>
18
+ <Card
19
+ onClick={() => onChange("side_panel")}
20
+ className={cls(
21
+ "my-2 rounded-md mx-0 p-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
22
+ "text-surface-700 dark:text-surface-accent-300",
23
+ "hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
24
+ value === "side_panel" ? "border-primary dark:border-primary" : "border-surface-400 dark:border-surface-600",
25
+ )}
26
+ >
27
+ <VerticalSplitIcon/>
28
+ <Typography variant={"label"}>
29
+ Side panel
30
+ </Typography>
31
+ </Card>
32
+ </Tooltip>
33
+
34
+ <Tooltip title={"Documents are open full-screen"}>
35
+ <Card
36
+ onClick={() => onChange("full_screen")}
37
+ className={cls(
38
+ "my-2 rounded-md mx-0 p-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
39
+ "text-surface-700 dark:text-surface-accent-300",
40
+ "hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
41
+ value === "full_screen" ? "border-primary dark:border-primary" : "border-surface-400 dark:border-surface-600",
42
+ )}
43
+ >
44
+ <SquareIcon/>
45
+ <Typography variant={"label"}>
46
+ Full screen
47
+ </Typography>
48
+ </Card>
49
+ </Tooltip>
50
+
51
+ </div>
52
+ <Typography variant={"caption"} color={"secondary"} className={"ml-3.5"}>Should documents be opened full screen or in an inline side dialog</Typography>
53
+ </div>
54
+ }